关于 sharp-edges
用于评估 API、配置和接口能否抵御开发者误用的安全分析技能。它通过四阶段流程检查不安全默认值、边界情况、配置陷阱和失败行为;专用 Agent 可自主执行分析。
主要功能
- 01四阶段分析流程
- 02检查危险默认值与配置陷阱
- 03探查边界情况和类型混淆
- 04针对开发者误用进行威胁建模
- 05按语言和安全类别提供参考
- 06自主分析 Agent
适用场景
- 01审查安全敏感的 API 设计
- 02审计配置模式与默认值
- 03评估加密 API 的易用性
- 04检查认证与授权接口
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill sharp-edges安装后在 Claude Code 中输入/sharp-edges确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/trail-of-bits-mark.svgreferences/auth-patterns.mdcase-studies.mdconfig-patterns.mdcrypto-apis.mdlang-c.mdlang-csharp.mdlang-go.mdlang-java.mdlang-javascript.mdlang-kotlin.mdlang-php.mdlang-python.mdlang-ruby.mdlang-rust.mdlang-swift.mdlanguage-specific.md详细介绍
锐边分析
评估 API、配置和接口是否能抵御开发者误用。识别那些会让「最简单的做法」导致安全问题的设计。
适用场景
- 审查 API 或库的设计决策
- 审计包含危险选项的配置架构
- 评估加密 API 的易用性
- 评估身份验证/授权接口
- 审查任何向开发者开放安全相关选项的代码
不适用场景
- 实现缺陷(使用标准代码审查)
- 业务逻辑漏洞(使用特定领域分析)
- 性能优化(这是另一个关注点)
Agent
sharp-edges-analyzer Agent 会自主运行完整的锐边分析工作流。如果你需要专门分析 API、配置或接口的抗误用能力和潜在陷阱,请使用该 Agent。它会遵循四阶段工作流(识别表面、探查边缘情况、威胁建模、验证发现),并按需查阅特定语言的参考资料。
核心原则
成功之路:安全的用法应该是阻力最小的路径。如果开发者必须理解密码学、仔细阅读文档或记住特殊规则才能避免漏洞,那么这个 API 就失败了。
应当驳斥的托词
| 托词 | 为什么不对 | 必须采取的行动 |
|---|---|---|
| 「有文档说明」 | 开发者在截止期限的压力下不会阅读文档 | 将安全选项设为默认选项或唯一选项 |
| 「高级用户需要灵活性」 | 灵活性会制造陷阱;多数所谓的「高级」用法都是复制粘贴 | 提供安全的高级 API;隐藏底层原语 |
| 「这是开发者的责任」 | 推卸责任;陷阱是你设计的 | 移除陷阱,或让误用成为不可能 |
| 「没人会真的那样做」 | 开发者在压力下什么都做得出来 | 假设开发者会陷入最大的困惑 |
| 「这只是一个配置选项」 | 配置就是代码;错误配置会被部署到生产环境 | 验证配置;拒绝危险的组合 |
| 「我们需要向后兼容」 | 不能把不安全的默认值当作祖传包袱 | 明确弃用;强制迁移 |
锐边类别
1. 算法/模式选择陷阱
允许开发者选择算法的 API,会诱使他们选错算法。
JWT 模式(典型示例):
- 标头指定算法:攻击者可以设置
"alg": "none"来绕过签名 - 算法混淆:切换 RS256→HS256 时,将 RSA 公钥用作 HMAC 密钥
- 根本原因:让不可信输入控制安全关键决策
检测模式:
algorithm、mode、cipher、hash_type等函数参数- 用于选择加密原语的枚举/字符串
- 安全机制的配置选项
示例——允许使用弱算法的 PHP password_hash:
// DANGEROUS: allows crc32, md5, sha1
password_hash($password, PASSWORD_DEFAULT); // Good - no choice
hash($algorithm, $password); // BAD: accepts "crc32"
2. 危险的默认值
默认值不安全,或零值/空值会禁用安全机制。
OTP 有效期模式:
# What happens when lifetime=0?
def verify_otp(code, lifetime=300): # 300 seconds default
if lifetime == 0:
return True # OOPS: 0 means "accept all"?
# Or does it mean "expired immediately"?
检测模式:
- 接受 0 的超时/有效期(无限期?立即过期?)
- 绕过检查的空字符串
- 跳过验证的 null 值
- 会禁用安全功能的布尔默认值
- 语义未定义的负值
需要提出的问题:
timeout=0、max_attempts=0、key=""会怎样?- 默认值是否是最安全的选项?
- 是否有任何默认值会彻底禁用安全机制?
3. 原语 API 与语义化 API
暴露原始字节而非有意义类型的 API,会导致类型混淆。
Libsodium 与 Halite 模式:
// Libsodium (primitives): bytes are bytes
sodium_crypto_box($message, $nonce, $keypair);
// Easy to: swap nonce/keypair, reuse nonces, use wrong key type
// Halite (semantic): types enforce correct usage
Crypto::seal($message, new EncryptionPublicKey($key));
// Wrong key type = type error, not silent failure
检测模式:
- 使用
bytes、string、[]byte接收不同安全概念的函数 - 不同参数互换后不会触发类型错误
- 密钥、nonce、密文和签名使用相同类型
比较陷阱:
// Timing-safe comparison looks identical to unsafe
if hmac == expected { } // BAD: timing attack
if hmac.Equal(mac, expected) { } // Good: constant-time
// Same types, different security properties
4. 配置陷阱
一个错误的设置就会导致灾难性故障,而且不会发出警告。
检测模式:
- 会彻底禁用安全功能的布尔标志
- 未经验证的字符串配置
- 会相互作用并造成危险后果的设置组合
- 会覆盖安全设置的环境变量
- 默认值合理但未经验证的构造函数参数(调用方可以将其覆盖为不安全的值)
示例:
# One typo = disaster
verify_ssl: fasle # Typo silently accepted as truthy?
# Magic values
session_timeout: -1 # Does this mean "never expire"?
# Dangerous combinations accepted silently
auth_required: true
bypass_auth_for_health_checks: true
health_check_path: "/" # Oops
// Sensible default doesn't protect against bad callers
public function __construct(
public string $hashAlgo = 'sha256', // Good default...
public int $otpLifetime = 120, // ...but accepts md5, 0, etc.
) {}
有关详细模式,请参阅 config-patterns.md。
5. 静默失败
错误没有显现出来,或成功掩盖了失败。
检测模式:
- 安全操作失败时返回布尔值而非抛出异常的函数
- 安全操作周围的空
catch块 - 解析错误时替换为默认值
- 在输入格式错误时仍然“成功”的验证函数
示例:
# Silent bypass
def verify_signature(sig, data, key):
if not key:
return True # No key = skip verification?!
# Return value ignored
signature.verify(data, sig) # Throws on failure
crypto.verify(data, sig) # Returns False on failure
# Developer forgets to check return value
6. 字符串化安全
将对安全至关重要的值设为普通字符串,会引发注入和混淆问题。
检测模式:
- 通过字符串拼接构造 SQL/命令
- 用逗号分隔的字符串表示权限
- 将角色/作用域设为任意字符串,而非枚举
- 通过拼接字符串构造 URL
权限累积陷阱:
permissions = "read,write"
permissions += ",admin" # Too easy to escalate
# vs. type-safe
permissions = {Permission.READ, Permission.WRITE}
permissions.add(Permission.ADMIN) # At least it's explicit
分析工作流
阶段 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