一句話說明
AI 生成的程式碼常常語法正確、邏輯看起來也對,但在權限控管、錯誤處理、機密資訊這幾個地方會留下人類工程師通常不會犯的漏洞,審查時需要針對這些盲點特別檢查。
為什麼 AI 寫的程式碼需要不同的審查方式
飛飛在做企業滲透測試的時候,越來越常遇到一種狀況:客戶的系統是用 Claude Code、Cursor、GitHub Copilot 這類工具在幾天內生出來的,功能都能動,但一測就有洞。關鍵原因在於 AI 犯錯的模式跟人類工程師不一樣,用審查人類程式碼的直覺去看,很容易漏掉。
人類工程師寫程式時,通常會帶著上下文的警覺性。一個有經驗的後端工程師寫一支 API,會下意識想到「這個欄位使用者填了奇怪的東西怎麼辦」「這支 API 誰都能打嗎」。AI 模型沒有這種持續的警覺性,它是根據 prompt 描述的功能去生成最直接能滿足需求的程式碼,安全性不是預設會考慮的維度,除非你明確要求。
幾個 AI 生成程式碼常見的模式:
過度抓取資料(over-fetching)。要求 AI 寫一支「取得使用者資料」的 API,它常常會用 SELECT * 或回傳整個 user 物件,包含密碼雜湊、內部 ID、其他不該給前端看到的欄位。人類工程師通常會習慣性地只選需要的欄位,AI 沒有這個習慣。
錯誤處理缺失或過度。AI 生成的程式碼經常兩極化:要不是完全沒有 try-catch,一有例外就整個服務掛掉;要不是用一個很大的 try-catch 包住所有邏輯,把所有錯誤都吞掉或回傳一樣的訊息,導致除錯困難,或者反過來把內部錯誤訊息直接回傳給使用者。
使用過時或有已知漏洞的寫法。AI 模型的訓練資料有時間點,它學到的「最佳實踐」可能是訓練資料截止時期的做法,之後被發現有問題的函式庫用法、已經棄用的 API,AI 不一定知道要避開。
忽略邊界情況。空字串、null、超長輸入、非 ASCII 字元、負數、超出範圍的日期,這些邊界情況 AI 不會主動處理,除非 prompt 裡有明確要求。
八大面向審查清單
以下是實際審查 AI 生成程式碼時可以照著走的清單,每個項目都是具體可檢查的動作,不是抽象原則。
一、輸入驗證
每一個接收使用者輸入的地方,確認是否做到:型別檢查(字串不能當數字用)、長度限制(避免超長字串塞爆記憶體或資料庫欄位)、格式檢查(email、電話、日期格式是否用正規表達式或驗證函式庫檢查)、白名單而非黑名單(列舉允許的值,而不是列舉禁止的值)。
特別注意 AI 常常只做「前端驗證」,也就是在畫面上用 JavaScript 檔掉不合法輸入,但後端 API 完全沒有重複驗證。前端驗證可以被繞過,任何人開瀏覽器的開發者工具或直接打 API 就能跳過。後端一定要有自己的驗證邏輯。
二、身份驗證與授權
檢查每一支 API 端點是否都掛了身份驗證中介層,不要假設「這支 API 應該不會有人亂打」。接著檢查授權邏輯:驗證使用者身份之後,是不是還要確認這個使用者有沒有權限做這件事。
這裡最常見的漏洞是 IDOR(不安全的直接物件參照)。舉例來說,一支 /api/orders/:id 的 API,AI 很容易只寫「檢查使用者有沒有登入」,卻忘記檢查「這張訂單是不是屬於登入的這個人」。任何登入的使用者只要改網址的 ID 數字,就能看到別人的訂單。這種漏洞在 AI 生成的 CRUD API 裡出現機率非常高,因為 AI 傾向把「有沒有登入」和「有沒有權限」當成同一件事。
角色型存取控制(RBAC)如果系統有分角色(管理員、一般使用者、訪客),要確認每個角色能做的操作是否在後端強制執行,而不是只在前端用 if 判斷要不要顯示按鈕。
三、資料處理
敏感欄位(密碼、身分證字號、信用卡卡號、健康資料)是否有加密儲存,而不是明碼放在資料庫。密碼是否用 bcrypt、argon2 這類慢雜湊演算法處理,而不是 MD5 或明碼比對。
檢查 log 裡有沒有印出 PII(個人可識別資訊)。AI 生成的除錯用 console.log 或 logger.info 很喜歡把整個 request body 印出來,如果 request 裡有密碼或身分證字號,這些資料就會留在 log 檔裡,log 檔案的存取權限如果沒管好,等於敏感資料外流。
資料保留政策也要確認:使用者刪除帳號後,相關資料是不是真的被刪除,還是只是加了一個 deleted 標記但資料還在。
四、依賴套件
檢查 package.json 或 requirements.txt 裡的版本號是否有鎖定(pinned),還是用 ^ 或 * 這種浮動版本,浮動版本代表未來安裝時可能拉到一個有漏洞或行為不同的新版本。
跑一次 npm audit 或 pip-audit,確認目前用的套件有沒有已知漏洞。AI 常常會為了實現一個小功能就建議安裝一整個套件,先檢查這個功能是不是幾行程式碼就能自己寫,減少不必要的依賴,套件越多,攻擊面越大。
五、錯誤處理
確認例外狀況是否被適當捕捉,而不是讓整支服務因為一個未捕捉的例外而崩潰。同時檢查錯誤訊息回傳給使用者的內容,是不是把完整的 stack trace、資料庫查詢語句、內部檔案路徑直接顯示出來。這類資訊對攻擊者來說是很好的偵查素材,正式環境的錯誤訊息應該是通用的(例如「系統發生錯誤,請稍後再試」),詳細錯誤只寫進伺服器端的 log。
六、機密資訊管理
搜尋整個程式碼庫有沒有寫死的 API 金鑰、資料庫密碼、第三方服務的 token。AI 在寫範例程式碼或測試程式碼時很容易直接把金鑰打在程式碼裡「先求能動」,這種程式碼一旦被複製貼上進正式專案,金鑰就永久留在版本控制紀錄裡,即使後來刪除也還在 git 歷史中。
確認 .env 或任何存放機密的檔案是否有列在 .gitignore 裡,並且用 git log 檢查歷史紀錄裡有沒有不小心 commit 過的機密檔案。也要檢查 log 輸出裡有沒有不小心印出環境變數或 token。
七、SQL/NoSQL 查詢
確認資料庫查詢是不是用參數化查詢(parameterized query)或 ORM 提供的安全介面,而不是直接把使用者輸入字串拼接進 SQL 語句。字串拼接是 SQL 注入最典型的成因。
如果使用 ORM(如 Prisma、TypeORM、SQLAlchemy),要注意 AI 有時候會為了「彈性」而使用 ORM 提供的原生查詢(raw query)功能繞過 ORM 本身的保護,這種寫法要額外檢查是否又拼接了使用者輸入。NoSQL 資料庫(如 MongoDB)同樣有注入風險,要確認查詢物件的 key 不會被使用者輸入的內容直接操控。
八、檔案操作
任何跟檔案路徑相關的操作都要檢查路徑穿越(path traversal)風險,也就是使用者能不能透過輸入 ../../etc/passwd 這類路徑跳出預期的資料夾。檔案上傳功能要檢查是否驗證副檔名和實際檔案內容(不能只看副檔名,要驗證檔案的 magic number),是否限制檔案大小,上傳後的檔案是否存放在不能被當成可執行程式碼執行的位置。
也要確認伺服器上的資料夾沒有意外開放目錄列表(directory listing),這會讓任何人瀏覽到伺服器上存放的所有檔案清單。
自動化審查工具
人工審查花時間,可以先用工具把明顯的問題篩掉,把人力留給需要判斷的部分。
Semgrep 是目前最實用的靜態分析工具之一,可以寫自訂規則,也有現成的規則庫涵蓋常見的注入、機密外洩、不安全的函式呼叫。適合放進 CI流程,每次 commit 自動掃描。
ESLint 搭配 eslint-plugin-security 可以在 JavaScript/TypeScript 專案裡抓出常見的不安全模式,例如使用 eval、不安全的正規表達式(可能導致 ReDoS)。
Bandit 是 Python 生態系常用的安全靜態分析工具,能抓出硬編碼密碼、不安全的反序列化、使用弱雜湊演算法等問題。
npm audit 和 Snyk 專門處理依賴套件的已知漏洞掃描,Snyk 額外提供修復建議和持續監控,適合有較多前端依賴的專案長期使用。
這些工具都不是萬能的,它們擅長抓「已知模式」的問題,抓不到需要理解商業邏輯才能發現的漏洞,例如前面提到的 IDOR,工具通常看不出「這支 API 少檢查了訂單的擁有者」,這種還是要靠人工走查。
實際走查:一支 AI 生成的 Express.js API
假設你請 AI 生成一支「使用者更新自己資料」的 API,AI 給出類似這樣的邏輯:
先驗證 JWT token 拿到使用者身份,接著用網址上帶的 userId 參數去更新資料庫裡對應的使用者資料,最後回傳更新後的完整使用者物件。
照清單一項項走:
輸入驗證。程式碼有沒有檢查傳進來的欄位型別和長度?如果 AI 只寫了「把 request body 直接丟進資料庫更新」,任何欄位都可能被塞入異常內容。
授權。這裡是最關鍵的一步,網址上的 userId 是不是跟 JWT 裡解出來的使用者 ID 一致?如果沒有這行檢查,任何登入的使用者都可以把網址的 userId 改成別人的 ID,直接修改別人的資料,這是非常典型的 AI 生成程式碼會漏掉的地方,因為 AI 完成了「驗證身份」這個需求,但沒有主動想到「驗證身份之後還要驗證這是不是他自己的資料」。
資料處理。回傳的使用者物件裡有沒有包含密碼欄位?很多 AI 生成的 ORM 查詢預設會撈出所有欄位。
錯誤處理。如果 userId 不存在,回傳的錯誤訊息是不是洩漏了資料庫結構,例如直接把 Sequelize 或 Prisma 的原始錯誤堆疊回傳給前端。
這個例子看起來只是一支幾十行的 API,但完整走一次清單就能發現至少兩個可以被實際利用的漏洞。這也是為什麼審查 AI 生成的程式碼不能只看「這段邏輯合不合理」,而要拿著清單逐項對照。
容易被忽略的隱藏問題
有一種情況特別值得留意:程式碼讀起來完全合理,變數命名清楚,邏輯順序正確,測試也能通過,但問題藏在「沒寫的地方」。例如一支批次處理的程式,AI 寫了主要邏輯,卻沒有考慮如果處理到一半程式中斷,資料會不會處在不一致的狀態;或是一支呼叫外部 API 的程式碼,只寫了成功的路徑,完全沒處理對方 API 逾時或回傳非預期格式的狀況。
這些問題不會在程式碼審查時「跳出來」,因為程式碼本身沒有錯誤的部分,缺的是不存在的部分。審查者需要主動問「這裡沒寫的分支會發生什麼事」,而不是只看寫出來的邏輯對不對。
安全考量與限制
即使照著清單審查,還是有幾個現實限制需要知道。
清單無法涵蓋商業邏輯漏洞。像是「這個折扣邏輯會不會被疊加濫用」這類跟特定業務規則綁定的問題,需要熟悉該系統的人來判斷,通用清單只能抓技術層面的共通問題。
自動化工具有其限制,前面提過的靜態分析工具主要抓語法層級的模式,對於需要理解資料流向和商業邏輯的漏洞(如 IDOR、競態條件)辨識能力有限,不能因為工具掃描過關就假設程式碼安全。
AI 協助審查本身也有風險。有些團隊會用另一個 AI 模型來審查 AI 生成的程式碼,這種做法可以抓到一部分明顯問題,但如果兩個模型有類似的知識盲點(例如都不擅長判斷授權邏輯的完整性),問題還是會被放過。人工審查,尤其是熟悉安全的人力審查,目前還是無法被完全取代的一環。
最後,審查清單本身需要跟著威脅情境更新。這篇文章列出的是目前常見的問題模式,隨著 AI 模型演進和新的攻擊手法出現,清單也需要定期檢視調整,不建議把它當成一次性、永久有效的檢查表。
知識檢測
讀完文章後,測試一下你對這個主題的理解。
常見問題
AI 生成的程式碼是不是天生就比人寫的不安全?
不一定,差別在於錯誤的模式不同,也在於審查方式需要調整。人類工程師會犯的錯誤(邏輯錯誤、拼字錯誤)AI 反而比較少犯,但 AI 在權限判斷、邊界情況、機密資訊管理上的疏忽比較常見且比較隱蔽,因為程式碼表面看起來很完整。
有沒有辦法在 prompt 裡就讓 AI 少犯這些錯?
可以在 prompt 裡明確要求,例如「這支 API 需要驗證使用者只能操作自己的資料,並且不要在回傳的物件裡包含密碼欄位」。把安全需求寫進 prompt 能明顯降低問題出現的機率,但不能保證完全避免,生成後的審查步驟仍然需要。
小專案或內部工具是不是可以省略這些檢查?
可以依風險調整檢查的嚴謹程度,但不建議完全省略。內部工具常常後來會被擴大使用範圍,或是被串接到其他系統,當初「只是內部用」的假設很容易失效。至少輸入驗證、授權檢查、機密資訊這三項建議任何專案都要做。
多久應該重新審查一次既有的 AI 生成程式碼?
如果程式碼本身沒有變動,主要風險來源是依賴套件出現新漏洞,建議至少每季跑一次依賴掃描。如果程式碼有持續迭代(尤其是用 AI 工具持續加功能),建議每次合併重大功能時都跑一次清單,而不是等到上線前才一次做完。
相關文章
參考資料
- OWASP Top 10 — OWASP 網頁應用程式十大風險,涵蓋注入、權限控管等分類
- Semgrep Registry — 開源與社群維護的靜態分析規則庫
- Bandit Documentation — Python 靜態安全分析工具官方文件