Community写作与编辑github.com

SebastienElet/agent-skills

编写以拒绝为目的的代码:钩子、守卫、验证器、权限检查、lint 规则、CI 关卡。在添加或更改任何决定通过或失败的控制时使用。确保在检查可能被绕过或无法运行时使用它,即使它被称为包装器或策略。

agent-skills 是什么?

本技能提供了一套系统性的方法论,用于编写和审查所有形式的“控制点”(如守卫、钩子、验证器、权限检查、lint 规则、CI 关卡)。其核心理念是:一个守卫的好坏取决于它拒绝了什么,而不是它通过了什么。技能详细描述了常见的失败模式,例如对值的代理(如命令行字符串)而不是值本身进行检查,或者由于无法运行而静默返回成功。它提出了一个包含7个步骤的流程,从识别被保护的真实值(Sink)开始,到枚举绕过途径并为其编写自动化测试用例结束。该技能适用于任何平台(如 Claude Code、Cursor 等),适用于任何需要确保安全控制不被轻易绕过或失效的场景。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/SebastienElet/agent-skills/tree/HEAD/skills/code-enforcement

Installed? Explore more 写作与编辑 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

代码强制

概述

守卫的好坏取决于当有人试图绕过它时它拒绝什么,而不是它是否通过了快乐路径。能通过审查的两个失败模式是:一个控制检查的是危险值的表示形式而非值本身,以及一个控制因无法运行而返回成功。这两者在基于作者自身假设编写的每一个测试中看起来都是绿色的。

用法

在编写或修改钩子、守卫、验证器、权限检查、lint 规则或 CI 关卡时,请遵循以下步骤 — 在打开文件之前,因为步骤1通常会改变代码所属的位置。

典型场景:“在仓库之外阻止 rm -rf”(步骤1将决策从命令字符串上移开),“当迁移没有回滚时使 CI 失败”(步骤3和4阻止关卡在迁移列表为空时通过),“拒绝没有签名提交的推送”(步骤5枚举了别名、--no-verify 和第二个远程仓库)。

步骤

  1. 命名被保护值(Sink)。 写下实际到达你要保护的对象的确切值:展开的 argv、请求体、SQL 参数、解析后的路径。验证该值。
  2. 检查你能否真正看到它。 如果代码只有该值的表示形式 — 一个 shell 命令字符串、一个 diff、一行日志、一条提交消息 — 那么该决策是一个启发式判断,而不是一个检查。在注释和交付说明中说明这一点,并将该层标记为 advisory(建议性)。不要让一个启发式判断成为你无法撤销的后果的唯一屏障。
  3. 失败时关闭(Fail closed)。 缺少运行时、输入不可读、解析器异常、未解析的变量、空结果:拒绝,并指出缺少了什么。一个因无法运行而退出0的守卫就是一个被禁用的守卫,并且它会静默地在几个月内保持禁用状态。
  4. 保持探测的失败可见。 在决定守卫是否能够运行的命令上不要使用 2>/dev/null,也不要让一个将空结果变成成功的分支。如果允许探测静默失败,步骤3将无法强制执行。
  5. 在编写修复之前枚举绕过途径。 列出控制如何被满足或跳过:复合命令、重复的标志、第二次调用、别名或函数、缺少解释器、另一台主机、子 shell、重命名的文件。将列表保留在提交正文或测试文件中。
  6. 将该列表中的每一项转换为一个测试用例,并接入项目的自动化检查。手动自测不算数:它会随着运行它的会话一起消失。
  7. 当绕过代价低廉且后果严重时,进行对抗性测试。 派遣一个拥有全新上下文的子代理,其任务是打破守卫,而不是审查它;其输出是尝试列表及其结果。对于最坏情况只是警告的控制,跳过此步骤。

陷阱

  • 解析 shell 命令来做决策。 引号、$()、&&、别名和 env 前缀都会改变最终执行的内容。这样的层只是一个减速带;真正的检查应放在命令效果产生的地方。
  • command -v 守卫。 if command -v tool > /dev/null; then check; fi 在任何缺少该工具的机器上都会静默跳过检查 — 恰恰是最需要它的机器。反转它:当工具缺失时拒绝。
  • 发现步骤找不到东西。 git grep -E 不支持 \b,因此这样的模式会匹配零个文件而不报错,并且关卡在读取了空结果后通过。用一个诱饵文件保护该步骤:一个你知道必须出现在其输出中的文件。
  • 仅测试你实现的拒绝情况。 有趣的测试是你没想到的输入;这就是步骤5产生的。
  • 仅通过手工测试的守卫。 它在作者的机器上通过,然后就会腐烂。如果项目没有地方接入测试,那么缺少的测试夹具本身就是工作的一部分。
  • 编写守卫的上下文无法打破它。 它共享了产生漏洞的盲点,并且会确认自己的设计。只有全新的上下文才具有对抗性。

约束

  • 当受保护的值本身可以到达时,永远不要对其代理做决策。
  • 在守卫无法完成其工作的路径上,永远不要退出0;拒绝并指出原因。
  • 永远不要压制确定守卫能否运行的探测的错误。
  • 在交付之前,必须为每一个枚举出的绕过途径编写自动化测试。
  • 明确说明哪一层是 advisory(建议性)的,哪一层是 enforcing(强制性)的;一个伪装成保证的启发式判断比没有守卫更糟糕。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相关技能