关于 sharp-edges
sharp-edges 是一项安全审查技能,用于评估 API、配置和接口是否便于安全使用。它聚焦不安全默认值、类型混淆和静默失败等误用风险,并提供系统化探查与验证流程。
主要功能
- 01审查 API 和配置的误用风险
- 02识别不安全默认值与配置陷阱
- 03探查边界情况和失败路径
- 04通过误用示例验证发现
- 05严重性分级与质量检查清单
适用场景
- 01审查加密 API 面向开发者的设计
- 02评估身份验证与授权接口
- 03审计配置选项中的不安全组合
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ waybarrios/ opencode-power-pack --skill sharp-edges安装后在 Claude Code 中输入/sharp-edges确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/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 的易用性
- 评估身份验证/授权接口
- 审查任何向开发者开放安全相关选项的代码
不适用场景
- 实现错误(使用标准代码审查)
- 业务逻辑缺陷(使用特定领域的分析)
- 性能优化(这是另一个关注点)
核心原则
成功之路:安全用法应该是最省力的选择。如果开发者必须理解密码学、仔细阅读文档或记住特殊规则才能避免漏洞,那么这个 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表示不同安全概念的函数 - 即使没有类型错误也可能被互换的参数
- 密钥、随机数、密文和签名使用相同类型
比较操作陷阱:
// 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