sharp-edges

sharp-edges

热门

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

6276Star
542Fork
更新于 2026/7/20
SKILL.md
readonly只读
name
sharp-edges
description

Identifies error-prone APIs, dangerous configurations, and footgun designs that enable security mistakes. Use when reviewing API designs, configuration schemas, cryptographic library ergonomics, or evaluating whether code follows 'secure by default' and 'pit of success' principles. Triggers: 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