識別容易出錯的 API、危險設定以及會導致安全失誤的陷阱設計。適用於審查 API 設計、設定架構、密碼學函式庫易用性,或評估程式碼是否遵循「預設安全」與「成功之坑」原則。觸發詞:footgun、misuse-resistant、secure defaults、API usability、dangerous configuration。
尖銳邊緣分析
評估 API、設定與介面是否能夠抵抗開發者的誤用。找出那些「簡單路徑」反而導致不安全的情況。
使用時機
- 審查 API 或函式庫的設計決策
- 稽核設定架構中是否有危險選項
- 評估密碼學 API 的易用性
- 評估認證/授權介面
- 審查任何讓開發者接觸到安全相關選擇的程式碼
不適用時機
- 實作錯誤(請使用標準程式碼審查)
- 業務邏輯缺陷(請使用領域特定分析)
- 效能最佳化(屬於不同範疇)
Agent
sharp-edges-analyzer agent 會自動執行完整的尖銳邊緣分析流程。當你需要針對 API、設定或介面進行誤用抵抗性與陷阱潛力的專門分析時,可以使用它。此 agent 遵循四個階段的工作流程(表面識別、邊界案例探測、威脅建模、驗證發現),並會按需讀取語言特定的參考資料。
核心原則
成功之坑:安全的使用方式應該是阻力最小的路徑。如果開發者必須理解密碼學、仔細閱讀文件或記住特殊規則才能避免漏洞,那麼這個 API 就是失敗的。
應拒絕的合理化藉口
| 合理化藉口 | 為什麼是錯的 | 應採取的行動 |
|---|---|---|
| 「文件有寫」 | 開發者在期限壓力下不會讀文件 | 讓安全的選擇成為預設或唯一選項 |
| 「進階使用者需要彈性」 | 彈性會製造陷阱;大多數「進階」用法只是複製貼上 | 提供安全的高階 API;隱藏底層實作 |
| 「這是開發者的責任」 | 推卸責任;是你設計了陷阱 | 移除陷阱或讓它不可能被誤用 |
| 「沒人會真的那樣做」 | 開發者在壓力下什麼事都做得出來 | 假設開發者會極度困惑 |
| 「那只是一個設定選項」 | 設定就是程式碼;錯誤的設定會上線到正式環境 | 驗證設定;拒絕危險的組合 |
| 「我們需要向後相容」 | 不安全的預設值不能因為相容性而保留 | 強烈標記為棄用;強制遷移 |
尖銳邊緣類別
1. 演算法/模式選擇陷阱
讓開發者選擇演算法的 API 容易導致選錯演算法。
JWT 模式(典型範例):
- Header 指定演算法:攻擊者可設定
"alg": "none"繞過簽章 - 演算法混淆:當從 RS256 切換到 HS256 時,RSA 公鑰被當作 HMAC 密鑰使用
- 根本原因:讓不受信任的輸入控制安全關鍵決策
偵測模式:
- 函式參數如
algorithm、mode、cipher、hash_type - 選擇密碼學原語的列舉/字串
- 安全機制的設定選項
範例 - PHP password_hash 允許弱演算法:
// 危險:允許 crc32、md5、sha1
password_hash($password, PASSWORD_DEFAULT); // 好 - 沒有選擇
hash($algorithm, $password); // 壞:接受 "crc32"
2. 危險的預設值
不安全的預設值,或零值/空值會停用安全機制。
OTP 生命週期模式:
# 當 lifetime=0 時會發生什麼事?
def verify_otp(code, lifetime=300): # 預設 300 秒
if lifetime == 0:
return True # 哎呀:0 表示「接受所有」?
# 還是表示「立即過期」?
偵測模式:
- 接受 0 的超時/生命週期(無限?立即過期?)
- 繞過檢查的空字串
- 跳過驗證的 null 值
- 停用安全功能的布林預設值
- 語義未定義的負數值
要問的問題:
timeout=0、max_attempts=0、key=""時會發生什麼事?- 預設值是否為最安全的選項?
- 是否有任何預設值可以完全停用安全機制?
3. 原始 vs. 語義 API
暴露原始位元組而非有意義型別的 API 容易導致型別混淆。
Libsodium vs. Halite 模式:
// Libsodium(原始):位元組就是位元組
sodium_crypto_box($message, $nonce, $keypair);
// 容易:互換 nonce/keypair、重複使用 nonce、使用錯誤的密鑰類型
// Halite(語義):型別強制正確使用
Crypto::seal($message, new EncryptionPublicKey($key));
// 錯誤的密鑰類型 = 型別錯誤,而非靜默失敗
偵測模式:
- 對不同的安全概念使用
bytes、string、[]byte的函式 - 參數可能被互換而不會產生型別錯誤
- 密鑰、nonce、密文、簽章使用相同型別
比較陷阱:
// 時間安全比較看起來與不安全比較一模一樣
if hmac == expected { } // 壞:計時攻擊
if hmac.Equal(mac, expected) { } // 好:常數時間
// 相同型別,不同的安全屬性
4. 設定懸崖
一個錯誤的設定就會造成災難性失敗,而且沒有任何警告。
偵測模式:
- 完全停用安全機制的布林旗標
- 未經驗證的字串設定
- 會產生危險互動的設定組合
- 覆蓋安全設定的環境變數
- 有合理預設值但未經驗證的建構子參數(呼叫者可以傳入不安全的值)
範例:
# 一個拼寫錯誤 = 災難
verify_ssl: fasle # 拼寫錯誤被靜默接受為 truthy?
# 魔術值
session_timeout: -1 # 這表示「永不過期」嗎?
# 危險組合被靜默接受
auth_required: true
bypass_auth_for_health_checks: true
health_check_path: "/" # 哎呀
// 合理的預設值無法防止不良的呼叫者
public function __construct(
public string $hashAlgo = 'sha256', // 好的預設值...
public int $otpLifetime = 120, // ...但接受 md5、0 等
) {}
詳細模式請參閱 config-patterns.md。
5. 靜默失敗
沒有浮現的錯誤,或成功掩蓋了失敗。
偵測模式:
- 回傳布林值而非在安全失敗時拋出例外
- 安全操作周圍的空 catch 區塊
- 解析錯誤時替換為預設值
- 對格式錯誤的輸入「成功」的驗證函式
範例:
# 靜默繞過
def verify_signature(sig, data, key):
if not key:
return True # 沒有密鑰 = 跳過驗證?!
# 忽略回傳值
signature.verify(data, sig) # 失敗時拋出例外
crypto.verify(data, sig) # 失敗時回傳 False
# 開發者忘記檢查回傳值
6. 字串型安全
將安全關鍵值以純字串形式處理會導致注入與混淆。
偵測模式:
- 透過字串串接建立的 SQL/命令
- 以逗號分隔字串表示的權限
- 以任意字串而非列舉表示的角色/範圍
- 透過字串串接建構的 URL
權限累積陷阱:
permissions = "read,write"
permissions += ",admin" # 太容易升級了
# vs. 型別安全
permissions = {Permission.READ, Permission.WRITE}
permissions.add(Permission.ADMIN) # 至少是明確的
分析工作流程
第一階段:表面識別
- 繪製安全相關 API 地圖:認證、授權、密碼學、session 管理、輸入驗證
- 識別開發者選擇點:開發者可以在哪些地方選擇演算法、設定超時、選擇模式?
- 尋找設定架構:環境變數、設定檔、建構子參數
第二階段:邊界案例探測
對每個選擇點,問:
- 零/空/null:
0、""、null、[]時會發生什麼事? - 負數值:
-1是什麼意思?無限?錯誤? - 型別混淆:不同的安全概念可以被互換嗎?
- 預設值:預設值安全嗎?有文件說明嗎?
- 錯誤路徑:無效輸入時會發生什麼事?靜默接受?
第三階段:威脅建模
考慮三種對手:
-
惡徒:惡意開發者或控制設定的攻擊者
- 他們能透過設定停用安全機制嗎?
- 他們能降級演算法嗎?
- 他們能注入惡意值嗎?
-
懶惰開發者:複製貼上範例,跳過文件
- 他們找到的第一個範例是否安全?
- 阻力最小的路徑是否安全?
- 錯誤訊息是否引導他們使用安全的方式?
-
困惑的開發者:誤解 API
- 他們能在不產生型別錯誤的情況下互換參數嗎?
- 他們會意外使用錯誤的密鑰/演算法/模式嗎?
- 失敗模式是明顯的還是靜默的?
第四階段:驗證發現
對每個識別出的尖銳邊緣:
- 重現誤用:撰寫最小程式碼展示陷阱
- 驗證可利用性:誤用是否會造成真實漏洞?
- 檢查文件:危險是否有文件說明?(文件不能成為不良設計的藉口,但會影響嚴重程度)
- 測試緩解措施:API 能否以合理的努力安全使用?
如果某個發現看起來有問題,回到第二階段並探測更多邊界案例。
嚴重程度分類
| 嚴重程度 | 標準 | 範例 |
|---|---|---|
| 嚴重 | 預設或明顯的使用方式不安全 | verify: false 為預設值;允許空密碼 |
| 高 | 簡單的錯誤設定就會破壞安全 | 演算法參數接受 "none" |
| 中 | 不常見但可能的錯誤設定 | 負數超時有意外含義 |
| 低 | 需要刻意誤用 | 晦澀的參數組合 |
參考資料
依類別:
- 密碼學 API:請參閱 references/crypto-apis.md
- 設定模式:請參閱 references/config-patterns.md
- 認證/Session:請參閱 references/auth-patterns.md
- 真實世界案例研究:請參閱 references/case-studies.md(OpenSSL、GMP 等)
依語言(一般陷阱,非密碼學特定):
| 語言 | 指南 |
|---|---|
| C/C++ | references/lang-c.md |
| Go | references/lang-go.md |
| Rust | references/lang-rust.md |
| Swift | references/lang-swift.md |
| Java | references/lang-java.md |
| Kotlin | references/lang-kotlin.md |
| C# | references/lang-csharp.md |
| PHP | references/lang-php.md |
| JavaScript/TypeScript | references/lang-javascript.md |
| Python | references/lang-python.md |
| Ruby | references/lang-ruby.md |
另請參閱 references/language-specific.md 以取得合併的快速參考。
品質檢查清單
在結束分析之前:
- [ ] 探測了所有零/空/null 邊界案例
- [ ] 確認預設值是安全的
- [ ] 檢查了演算法/模式選擇陷阱
- [ ] 測試了安全概念之間的型別混淆
- [ ] 考慮了所有三種對手類型
- [ ] 確認錯誤路徑不會繞過安全機制
- [ ] 檢查了設定驗證
- [ ] 建構子參數已驗證(不僅是預設值) - 請參閱 config-patterns.md






