AI 生成測試的安全缺口
分析 AI 生成的測試套件如何系統性地遺漏安全相關測試案例,製造危險的覆蓋率錯覺。
概述
測試產生是 AI 程式碼生成最熱門的應用之一。相較於功能實作,它所需的創意設計較少,輸出品質可透過覆蓋率指標立即衡量,且生產力的提升相當可觀 — 原本需要數小時手動撰寫的測試,現在幾分鐘內即可完成。
AI 程式碼生成工具不只被用來產生應用程式碼,還被用來產生驗證它的測試。GitHub Copilot、ChatGPT、Claude,以及 CodiumAI、Diffblue Cover 等專用測試工具,都能產生達到令人印象深刻覆蓋率數字的測試套件。表面上看來,這對品質是一大勝利 — 更多測試、更少工夫、更高覆蓋率百分比。
問題在於,AI 生成的測試會系統性地遺漏安全相關的測試案例。它們測試正常路徑,測試明顯的錯誤條件,也測試在訓練資料中出現過的邊界情況。但它們始終無法測試對抗性輸入、競爭條件、授權邊界違規、密碼學正確性屬性,以及構成絕大多數可利用安全漏洞的細微互動錯誤。
這造就了一個特別危險的情境:開發者看到 AI 生成測試達到 85% 的程式碼覆蓋率,便假設程式碼已經獲得良好測試。但實際上,那未被測試的 15% 不成比例地包含了安全關鍵路徑,即便是被測試過的程式碼也只針對良性輸入進行驗證。測試套件提供了信心,卻沒有對應的安全保證。
本文記錄 AI 工具系統性遺漏的各類安全測試,展示覆蓋率指標與安全覆蓋率之間的落差,並提供補強 AI 生成測試的安全導向測試框架。
覆蓋率錯覺
行覆蓋率 vs. 安全覆蓋率
程式碼覆蓋率衡量的是測試執行時哪些程式碼行被執行。它並不衡量這些行是否以安全相關的輸入被測試。考量以下這個認證函式與其 AI 生成的測試:
# Application code
import hashlib
import hmac
import time
def authenticate(username: str, password: str, db) -> dict:
"""Authenticate a user and return a session token."""
if not username or not password:
raise ValueError("Username and password required")
user = db.get_user(username)
if user is None:
raise AuthError("Invalid credentials")
password_hash = hashlib.sha256(password.encode()).hexdigest()
if password_hash != user.password_hash:
raise AuthError("Invalid credentials")
token = hmac.new(
SECRET_KEY, f"{username}:{time.time()}".encode(), hashlib.sha256
).hexdigest()
db.create_session(username, token)
return {"token": token, "user": username}AI 生成的測試通常涵蓋:成功登入、空帳號、空密碼、密碼錯誤、未知使用者等五類案例,這些測試合計可達到約 90% 的行覆蓋率,看起來非常全面。然而它們卻遺漏了所有安全相關屬性。真正重要的安全測試包括:
- 時序攻擊抗性:測試密碼比對是否為常數時間。上述實作使用
!=直接比較 SHA-256 雜湊字串,並非常數時間運算,可能洩漏關於正確密碼前綴的資訊。 - 帳號列舉:驗證帳號存在與不存在時,錯誤訊息與回應時間都必須完全相同,否則攻擊者可藉此列舉系統內存在的帳號。
- 密碼雜湊演算法:確認程式碼未使用裸 SHA-256,而是 bcrypt、scrypt 或 argon2 等適當的金鑰衍生函式 (KDF)。SHA-256 速度太快,不適合用於密碼雜湊。
- token 可預測性:驗證連續產生的 session token 不會因為時間戳與帳號組合而變得可預測,應確保多次產生時所有 token 皆唯一。
- 帳號欄位 SQL 注入:將常見注入載荷(如
' OR '1'='1、admin'--、'; DROP TABLE users; --、null byte、UNION SELECT)傳入帳號欄,確認資料庫驅動程式安全處理並未引發意外行為。
為何 AI 測試會遺漏安全屬性
系統性遺漏安全測試有可辨識的根本原因:
訓練資料偏差:開放原始碼儲存庫中絕大多數的測試程式碼都在測試功能正確性,而非安全屬性。AI 模型學習重現訓練資料的統計分布,因此它們以高機率產生功能測試,以低機率產生安全測試。
規格落差:AI 工具根據程式碼「表面上的行為」而非「安全需求」來產生測試。一個以 SHA-256 雜湊密碼的函式從功能角度來看「運作正常」 — 雜湊被計算、被比對。至於其安全缺陷(使用 SHA-256 而非適當的 KDF),則需要程式碼本身不存在的領域知識才能辨識。
正常路徑最佳化:LLM 在人類撰寫的測試套件上訓練,這些套件絕大多數測試預期的輸入與行為。對抗性思考 — 「如果攻擊者傳入一個 10MB 的帳號會怎樣?」 — 在訓練資料中被大量低估。
Mock 密集的模式:AI 生成的測試大量使用 mocking,這阻止了安全問題浮現。被 mock 的資料庫永遠不會暴露 SQL 注入,被 mock 的 HTTP 用戶端永遠不會暴露 SSRF。測試在隔離下通過,真實整合卻充滿漏洞。
系統性的安全測試缺口
授權與存取控制測試
AI 生成的測試幾乎從不測試授權邊界 — 例如使用者是否能存取屬於其他使用者的資源。典型的 AI 生成測試僅檢查授權使用者是否能成功讀取自己的個人資料,但遺漏了以下關鍵安全測試:
- 橫向權限提升:持有使用者 123 token 的請求嘗試存取使用者 456 的個人資料,應回傳 403。
- 垂直權限提升:一般使用者 token 嘗試存取
/api/admin/users等管理者端點,應回傳 403。 - 缺少 Auth header:未帶授權 header 的請求應回傳 401(拒絕),不得被當作匿名使用者處理。
- 過期 token:已過期的 token 應被拒絕。
- 已刪除使用者的 token:使用者被刪除後,其仍在有效期內的 token 也應立即失效。
競爭條件測試
並行漏洞幾乎從不出現在 AI 生成的測試中。以下為兩個代表性的安全測試,說明 AI 生成測試應補強的對抗性情境:
- 重複花費競爭條件:建立一個餘額 100 的帳戶,同時送出兩筆金額 100 的提款請求。正確實作下最多只能有一筆成功,最終餘額也必須
>= 0。要測試此行為,可用asyncio.gather並行送出請求並檢查成功次數。 - Session fixation 競爭:在使用者重新登入的同時,用舊 token 發出存取敏感資料的請求,確認重新認證後舊 token 立即失效,不得出現「在重新認證完成前舊 token 仍可使用」的視窗。
輸入驗證邊界測試
AI 工具會測試明顯的無效輸入,但遺漏對抗性邊界情況。典型缺失的對抗性測試包括:
- Null byte 注入:
"admin\x00evil"可能在 C 後端程式庫中截斷字串,使得驗證通過但後續處理以admin為準。 - Unicode 正規化混淆:西里爾字母
а與拉丁字母a視覺上相同卻是不同位元組,可能導致帳號欺騙。 - 極端長度輸入:長度一百萬字元的字串應被快速拒絕(< 1 秒),不得發生 ReDoS 或緩衝區溢位。
- 格式字串字元:
%s%s%s%s%s、${7*7}、{{7*7}}等載荷可能觸發格式字串注入或模板注入。 - 路徑遍歷:
../../../etc/passwd、..\\..\\..\\windows\\system32等若被用於檔案路徑應被拒絕。 - CRLF 注入:
"alice\r\nX-Admin: true"可能被用於 HTTP header 注入。 - UTF-8 過長編碼:
b"\xc0\xaf"是/的過長編碼,可能繞過以/為基礎的路徑過濾。
密碼學正確性測試
AI 工具幾乎從不產生驗證密碼學屬性的測試。必要的密碼學屬性測試包括:
- IV 唯一性:以同一金鑰加密 100 次應產生 100 個相異的 IV,否則為 IV 重用漏洞。
- 密文延展性:翻轉密文中的一個位元後,解密應拋出錯誤而非回傳悄悄損壞的明文(說明未使用 AEAD 或未驗證 MAC)。
- 非 ECB 模式:對兩塊相同的 16 位元組明文加密,產生的密文應不同。若相同則表示使用了不安全的 ECB 模式。
衡量安全覆蓋率的缺口
以下框架量化程式碼覆蓋率與安全覆蓋率之間的差異。其核心概念為:定義 SecurityTestCategory 列舉(授權邊界、對抗性輸入、競爭條件、密碼學屬性、錯誤處理、session 管理、注入攻擊、資訊洩漏等),再對測試套件檔案進行正規表示式匹配 — 搜尋 unauthorized、403、privilege、injection、race、concurrent、iv.reuse、constant.time、sqli、xss 等安全指標關鍵字。分析器回傳總測試數、安全測試數、各類別涵蓋數量,以及「完全沒有對應測試」的類別清單,讓團隊清楚看到覆蓋率百分比所掩蓋的安全盲點。
建構安全測試補強策略
組織不該依賴 AI 生成的測試作為安全保證。而是應將 AI 生成的測試視為功能覆蓋的基礎,再透過刻意的、對抗性導向的方法補上安全測試:
屬性式測試:使用 Hypothesis (Python) 或 fast-check (JavaScript) 等工具自動產生對抗性輸入:
from hypothesis import given, strategies as st, settings
@given(
username=st.text(min_size=0, max_size=10000),
password=st.text(min_size=0, max_size=10000),
)
@settings(max_examples=1000)
def test_authenticate_never_crashes(username, password):
"""Authentication should handle any input without crashing."""
db = create_test_db()
try:
authenticate(username, password, db)
except (ValueError, AuthError):
pass # Expected errors
# Any other exception is a bug安全需求矩陣:將每項安全需求對應到具體的測試案例,並在需求層級(而非程式碼層級)追蹤覆蓋率。
對抗性測試生成提示詞:在使用 AI 產生測試時,明確要求安全導向的測試:
為此認證函式產生安全測試,包含:
1. 授權邊界測試(使用者 A 能否存取使用者 B 的資料?)
2. 注入測試(所有字串輸入的 SQL 注入、命令注入)
3. 時序攻擊測試(密碼比對是否為常數時間?)
4. 競爭條件測試(並行登入、並行 session 失效)
5. 密碼學屬性測試(IV 唯一性、演算法強度)
6. 輸入邊界測試(null bytes、Unicode 正規化、極端長度)
不要產生任何功能/正常路徑測試。
這類指定式提示顯著改善安全測試的產生,但並未消除對生成測試進行人工審閱的需要。
縮小缺口的組織策略
安全測試需求矩陣
針對每個應用元件,維護一份將安全需求對應到具體測試案例的矩陣。這份矩陣作為「應測試什麼」的權威紀錄,獨立於任何 AI 工具生成的內容。例如對於 authentication 元件,矩陣可列出如下需求:
AUTH-001:密碼比對為常數時間 — 類別timing_attackAUTH-002:登入失敗不得透露帳號是否存在 — 類別information_disclosureAUTH-003:N 次失敗登入後帳號鎖定 — 類別brute_forceAUTH-004:更改密碼時 session token 失效 — 類別session_managementAUTH-005:並行登入 session 正確處理 — 類別race_condition
對 payment_processing 元件:
PAY-001:並行請求下防止重複花費 — 類別race_conditionPAY-002:付款金額不可在客戶端被竄改 — 類別business_logicPAY-003:付款處理流程中不得記錄 PII — 類別information_disclosure
配套的稽核函式可自動掃描所有需求,驗證對應測試檔案與測試函式皆存在且有效。若缺失則標記為 MISSING,若測試檔存在但函式名稱不對則標記為 ERROR,讓需求層級的覆蓋狀況一目了然。
CI/CD 整合:安全測試門檻
加入一個獨立於整體程式碼覆蓋率的 CI/CD 門檻,用以驗證安全測試的覆蓋情況:
# GitHub Actions: Security test coverage gate
name: Security Test Coverage
on: [pull_request]
jobs:
security-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run security-specific tests
run: |
pytest tests/security/ -v --tb=short \
--junitxml=security-test-results.xml
- name: Verify security test matrix coverage
run: |
python scripts/audit_security_tests.py \
--matrix security_test_matrix.json \
--fail-on-missing
- name: Check for security tests in changed components
run: |
CHANGED=$(git diff --name-only origin/main...HEAD -- 'src/')
for file in $CHANGED; do
component=$(echo "$file" | cut -d'/' -f1-2)
security_tests="tests/security/test_$(basename $component).py"
if [ -f "$security_tests" ]; then
echo "Security tests found for $component"
else
echo "::warning::No security tests found for $component"
fi
done以 Fuzzing 補強輸入驗證
Hypothesis 等屬性式測試涵蓋結構化的對抗性輸入;而 fuzzing 則覆蓋非結構化的邊緣 — 格式不良資料、損壞輸入,以及人類測試者不會想到的位元層級操縱。可使用 Atheris(Python 的覆蓋率導向 fuzzer)對 JSON 解析器、token 驗證函式等進行模糊測試,透過 FuzzedDataProvider 隨機產生 Unicode 字串與位元組序列,對 parse_api_request、validate_auth_token 等函式進行壓測。預期例外(ValueError、KeyError、AuthError)會被捕獲,其他任何例外即代表漏洞。
重點整理
AI 生成的測試套件製造了危險的安全覆蓋率錯覺。高行覆蓋率數字掩蓋了授權測試、對抗性輸入測試、競爭條件測試與密碼學屬性測試的系統性缺口。組織必須將 AI 生成的測試視為功能基線,進而刻意補強安全測試。「這段程式碼有被覆蓋」與「這段程式碼是安全的」之間的落差,正是可利用漏洞藏身之處,而要彌補這個落差需要對抗性思考 — 這是目前的 AI 工具無法可靠提供的。
對紅隊而言,AI 生成測試的缺口是機會:若你知道目標組織依賴 AI 生成的測試,你就能推斷哪些漏洞類別最可能被低估測試。優先鎖定這些類別 — 授權邊界、競爭條件、密碼學屬性 — 因為它們正是組織錯誤信心最高、防禦最薄弱的缺口。
組織能做出最有影響力的改變,就是停止把程式碼覆蓋率當作安全保證的代理指標,轉而採用安全測試需求矩陣,在需求層級追蹤覆蓋。當安全測試需求缺失時,不論整體行覆蓋率數字多高,缺口都會清楚顯示。這把對話從「我們有 90% 的覆蓋率」轉為「付款模組在授權邊界這方面有零個測試」 — 一個更可行動、更能驅動正確投資的框架。
參考文獻
- Lemieux, C., et al. (2023). "CodaMosa: Escaping Coverage Plateaus in Test Generation with Pre-trained Large Language Models." International Conference on Software Engineering (ICSE). 分析 LLM 生成的測試如何在取得高覆蓋率的同時遺漏深層行為屬性。
- Kang, S., et al. (2024). "Large Language Models are Few-Shot Testers: Exploring LLM-based General Bug Reproduction." IEEE/ACM International Conference on Software Engineering. 評估 LLM 測試生成能力及其針對安全相關錯誤的限制。
- OWASP (2025). "Testing Guide v5." https://owasp.org/www-project-web-security-testing-guide/ — 安全測試方法論的業界標準,列舉自動化生成通常會遺漏的測試類別。