识别易出错的API、危险配置以及导致安全失误的陷阱设计。用于审查API设计、配置模式、加密库易用性,或评估代码是否遵循“默认安全”和“成功之坑”原则。触发词:footgun、misuse-resistant、secure defaults、API usability、dangerous configuration。
锐边分析
评估API、配置和接口是否能够抵抗开发者的误用。识别那些“简单路径”导致不安全的场景。
何时使用
- 审查API或库的设计决策
- 审计配置模式中是否存在危险选项
- 评估加密API的易用性
- 评估认证/授权接口
- 审查任何向开发者暴露安全相关选择的代码
何时不使用
- 实现错误(使用标准代码审查)
- 业务逻辑缺陷(使用领域特定分析)
- 性能优化(不同关注点)
代理
sharp-edges-analyzer 代理会自动执行完整的锐边分析工作流。当你需要对API、配置或接口进行专门的误用抵抗性和陷阱潜力分析时使用。该代理遵循四阶段工作流(表面识别、边界情况探测、威胁建模、验证发现),并按需读取语言特定的参考文档。
核心原则
成功之坑:安全使用应是最小阻力路径。如果开发者必须理解密码学、仔细阅读文档或记住特殊规则才能避免漏洞,那么这个API就是失败的。
应拒绝的合理化理由
| 合理化理由 | 为什么错误 | 所需行动 |
|---|---|---|
| “文档中有说明” | 开发者在截止日期压力下不会阅读文档 | 让安全选择成为默认或唯一选项 |
| “高级用户需要灵活性” | 灵活性制造陷阱;大多数“高级”用法是复制粘贴 | 提供安全的高级API;隐藏底层原语 |
| “这是开发者的责任” | 推卸责任;你设计了陷阱 | 移除陷阱或使其无法被误用 |
| “没人会真的那样做” | 开发者在压力下会做任何能想到的事 | 假设开发者最大程度的困惑 |
| “这只是一个配置选项” | 配置即代码;错误的配置会部署到生产环境 | 验证配置;拒绝危险组合 |
| “我们需要向后兼容” | 不安全的默认值不能通过祖父条款保留 | 大声弃用;强制迁移 |
锐边类别
1. 算法/模式选择陷阱
允许开发者选择算法的API容易导致选择错误。
JWT模式(典型示例):
- 头部指定算法:攻击者可设置
"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的超时/生存期(无限?立即过期?)
- 绕过检查的空字符串
- 跳过验证的空值
- 禁用安全功能的布尔默认值
- 语义未定义的负值
要问的问题:
timeout=0、max_attempts=0、key=""时会发生什么?- 默认值是最安全的选项吗?
- 是否有任何默认值可以完全禁用安全功能?
3. 原语API与语义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 # 拼写错误被静默接受为真?
# 魔法值
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" # 太容易升级权限
# 与类型安全对比
permissions = {Permission.READ, Permission.WRITE}
permissions.add(Permission.ADMIN) # 至少是显式的
分析工作流
阶段1:表面识别
- 映射安全相关API:认证、授权、密码学、会话管理、输入验证
- 识别开发者选择点:开发者可以在哪里选择算法、配置超时、选择模式?
- 查找配置模式:环境变量、配置文件、构造函数参数
阶段2:边界情况探测
对于每个选择点,询问:
- 零/空/null:
0、""、null、[]时会发生什么? - 负值:
-1是什么意思?无限?错误? - 类型混淆:不同的安全概念能否互换?
- 默认值:默认值安全吗?有文档说明吗?
- 错误路径:无效输入时会发生什么?静默接受?
阶段3:威胁建模
考虑三种对手:
-
恶棍:主动恶意的开发者或控制配置的攻击者
- 他们能否通过配置禁用安全功能?
- 他们能否降级算法?
- 他们能否注入恶意值?
-
懒惰的开发者:复制粘贴示例,跳过文档
- 他们找到的第一个示例是否安全?
- 最小阻力路径是否安全?
- 错误消息是否引导向安全使用?
-
困惑的开发者:误解API
- 他们能否在不产生类型错误的情况下互换参数?
- 他们能否意外使用错误的密钥/算法/模式?
- 失败模式是明显的还是静默的?
阶段4:验证发现
对于每个识别出的锐边:
- 重现误用:编写最小代码演示陷阱
- 验证可利用性:误用是否造成真实漏洞?
- 检查文档:危险是否被记录?(文档不能成为不良设计的借口,但影响严重性)
- 测试缓解措施:API能否以合理努力安全使用?
如果某个发现似乎可疑,返回阶段2并探测更多边界情况。
严重性分类
| 严重性 | 标准 | 示例 |
|---|---|---|
| 严重 | 默认或明显用法不安全 | verify: false 默认值;允许空密码 |
| 高 | 简单错误配置破坏安全 | 算法参数接受 "none" |
| 中 | 不常见但可能的错误配置 | 负超时具有意外含义 |
| 低 | 需要故意误用 | 晦涩的参数组合 |
参考
按类别:
- 加密API:参见 references/crypto-apis.md
- 配置模式:参见 references/config-patterns.md
- 认证/会话:参见 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






