sharp-edges

sharp-edges

热门

识别易出错的API、危险配置以及导致安全失误的陷阱设计。用于审查API设计、配置模式、加密库易用性,或评估代码是否遵循“默认安全”和“成功之坑”原则。触发词:footgun、misuse-resistant、secure defaults、API usability、dangerous configuration。

6276Star
542Fork
更新于 2026/7/20
SKILL.md
只读
名称
sharp-edges
描述

识别易出错的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密钥
  • 根本原因:让不受信任的输入控制安全关键决策

检测模式:

  • 函数参数如 algorithmmodecipherhash_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=0max_attempts=0key="" 时会发生什么?
  • 默认值是最安全的选项吗?
  • 是否有任何默认值可以完全禁用安全功能?

3. 原语API与语义API

暴露原始字节而非有意义的类型的API容易导致类型混淆。

Libsodium vs. Halite 模式:

// Libsodium(原语):字节就是字节
sodium_crypto_box($message, $nonce, $keypair);
// 容易:交换 nonce/keypair、重用 nonce、使用错误的密钥类型

// Halite(语义):类型强制正确使用
Crypto::seal($message, new EncryptionPublicKey($key));
// 错误的密钥类型 = 类型错误,而非静默失败

检测模式:

  • 对不同的安全概念使用 bytesstring[]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:表面识别

  1. 映射安全相关API:认证、授权、密码学、会话管理、输入验证
  2. 识别开发者选择点:开发者可以在哪里选择算法、配置超时、选择模式?
  3. 查找配置模式:环境变量、配置文件、构造函数参数

阶段2:边界情况探测

对于每个选择点,询问:

  • 零/空/null0""null[] 时会发生什么?
  • 负值-1 是什么意思?无限?错误?
  • 类型混淆:不同的安全概念能否互换?
  • 默认值:默认值安全吗?有文档说明吗?
  • 错误路径:无效输入时会发生什么?静默接受?

阶段3:威胁建模

考虑三种对手:

  1. 恶棍:主动恶意的开发者或控制配置的攻击者

    • 他们能否通过配置禁用安全功能?
    • 他们能否降级算法?
    • 他们能否注入恶意值?
  2. 懒惰的开发者:复制粘贴示例,跳过文档

    • 他们找到的第一个示例是否安全?
    • 最小阻力路径是否安全?
    • 错误消息是否引导向安全使用?
  3. 困惑的开发者:误解API

    • 他们能否在不产生类型错误的情况下互换参数?
    • 他们能否意外使用错误的密钥/算法/模式?
    • 失败模式是明显的还是静默的?

阶段4:验证发现

对于每个识别出的锐边:

  1. 重现误用:编写最小代码演示陷阱
  2. 验证可利用性:误用是否造成真实漏洞?
  3. 检查文档:危险是否被记录?(文档不能成为不良设计的借口,但影响严重性)
  4. 测试缓解措施:API能否以合理努力安全使用?

如果某个发现似乎可疑,返回阶段2并探测更多边界情况。

严重性分类

严重性 标准 示例
严重 默认或明显用法不安全 verify: false 默认值;允许空密码
简单错误配置破坏安全 算法参数接受 "none"
不常见但可能的错误配置 负超时具有意外含义
需要故意误用 晦涩的参数组合

参考

按类别:

按语言(通用陷阱,非加密特定):

语言 指南
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