1实现原理 · 为什么它能做到
编排方式是「子进程 + JSON 契约」:safe_pack.py 不导入扫描器,而是用 subprocess 调同目录的 scan_secrets.py 并加 --json,再解析其 stdout 决定是否打包。
cmd = [sys.executable, str(scan_script), str(directory), '--json']
检测能力来自一张 13 项的正则字典(SECRET_PATTERNS),全部是行内正则匹配,无 AST、无熵值分析、无机器学习。
'aws_access_key': r'(?i)AKIA[0-9A-Z]{16}', 'aws_secret_key': r'(?i)(?:aws_secret|aws.{0,20}secret).{0,20}[=:]\s*["\']?([0-9a-zA-Z/+=]{40})["\']?', 'supabase_url': r'https://[a-z]{20}\.supabase\.co', 'supabase_anon_key': r'eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*', 'stripe_key': r'(?:sk|pk)_(live|test)_[0-9a-zA-Z]{24,}',
扫描范围由「扩展名白名单 + .env 文件名特例」双重界定,且代码注释明确解释了为什么必须给 dotenv 开特例(Path('.env').suffix == '')。
# Full filenames that must be scanned regardless of Path.suffix. Dotenv # files break suffix matching: Path('.env').suffix == '' and # Path('.env.local').suffix == '.local', so scanning by extension alone # silently skips the most common places secrets end up. SCANNABLE_FILENAMES = { '.env',
跳过目录是硬编码集合(node_modules/.git/.venv/dist/build 等 15 个),不做 gitignore 解析。
SKIP_DIRS = { 'node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build',
假阳性抑制有两条硬规则:占位符词表命中即跳过,且对整行做 substring 判断(只要该行任意位置出现 'example'/'test-' 等词,本行所有命中都被丢弃)。
placeholders = [ 'your-', 'example', 'placeholder', 'xxx', 'yyy', 'zzz', 'test-', 'demo-', 'sample-', '<YOUR_', '${', 'TODO' ]
注释行一律跳过(行首 // / # / /* / *)。
if re.search(r'^\s*(?://|#|/\*|\*)', line):
默认 fail-closed:命中即拒绝打包并 exit 1,只有显式 --force 才会继续;--force 路径仍打印警告。
print("❌ Cannot pack: Secrets detected!")
打包动作是调用外部 CLI repomix:把目标目录作为 cwd,透传 --config/--output,产物由 repomix 写盘。
result = subprocess.run(cmd, cwd=directory, capture_output=True, text=True)
报告会回显命中的密钥片段与命中行上下文到 stdout(--json 下 match 截断 50 字符、context 截断 100 字符;人类可读报告打印 match/context 前 80 字符),但脚本本身不写任何文件。
print(f" Match: {finding.matched_text[:80]}") print(f" Context: {finding.line_content[:80]}")
退出码即接口:有命中返回 1、干净返回 0,SKILL.md 明确把它当作 pre-commit 钩子用(阻断提交)。
sys.exit(1 if findings else 0)
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | repomix(第三方打包 CLI,需用户自行预装) |
| cli | python3(sys.executable 拉起同目录扫描器;仅标准库 os/re/sys/json/subprocess/pathlib) |
| package | repomix(npm 生态包,本 skill 只调用命令,不负责安装) |
4风险提醒 风险提醒:蓝色 · 知晓即可
- 扫描失败的 fail-open:只承诺『发现即阻断』,不覆盖『扫描没跑成』 — run_secret_scan 不读 result.returncode,stdout 为空即视为无命中,随后照常打包。若 python 解释器异常或扫描器崩溃(stderr 有输出、stdout 为空),用户会看到『✅ Packaging complete!』而实际未经扫描;建议在 CI 中改为同时断言扫描器退出码。
- 检测覆盖与文档不符,存在稳定的漏报面 — 无 Gemini AIza 规则、无 R2 access key 规则,裸 sk- 形式不一定命中;且 *.pem/*.key/id_rsa/*.toml/*.ini 等不在扫描扩展名白名单内,private_key 规则几乎无用武之地。SKILL.md 的检测类型清单会被读者高估。
- 假阳性抑制按整行生效,真密钥可能被同行噪音放过 — should_skip_match 对 line_lower 做 substring 判断,行内出现 'example'/'test-'/'xxx'/'TODO' 之一即整行免检;注释行同样整行跳过。真实代码中『示例值 + 真实值』同行的配置或调试残留会被漏掉。
- 报告回显密钥片段与原始文本,进入 agent 上下文/会话记录 — scan_secrets.print_report 打印 matched_text[:80] 与 line_content[:80],--json 保留 match[:50]/context[:100]。对 agent 会话而言这既是敏感片段留痕,也是把不可信仓库的文件内容带进模型上下文的注入通道(可构造 'Match:' 行干扰阅读)。
- --force 文档措辞误导 — SKILL.md 标题『Force pack (dangerous, skip scan)』暗示免扫描,实际仍完整扫描并报告,只是跳过阻断——使用者若据此认为『--force 更快』会造成误解(功能上其实更慢且危险)。
5第二遍独立确认
- [ok] 外部依赖调用点反查(repomix / python3) — 全目录 grep subprocess 仅 3 处命中:safe_pack.py 的 import、L24 subprocess.run(调 scan_secrets.py,cmd 来自 cmd = [sys.executable, str(scan_script), …])、L74 subprocess.run(cmd, cwd=directory)(cmd 起点为 cmd = ['repomix'])。两个依赖条目均有真实调用点,无第三处。
- [ok] 网络外发反例查找 — grep requests|urllib|urlopen|socket|http|curl|wget 在两个脚本内零命中;SKILL.md 与 common_secrets.md 中的 https:// 全部是占位符(https://your-project-ref.supabase.co)、示例域名(.r2.cloudflarestorage.com 出现在正则 lookahead 文本中)或文档外链。无 DNS/HTTP 客户端、无遥测、无任何外发路径。
- [discrepancy] 扫描器返回码未被校验 → fail-open 反例 — run_secret_scan 只做 result = subprocess.run(...);随后 findings = json.loads(result.stdout) if result.stdout.strip() else [],从不检查 result.returncode。若扫描器因解释器缺失/脚本损坏/进程被杀而 stdout 为空,findings 被判为空列表,main 直接进入 '📦 Packing … with repomix' 分支——即『扫描失败』被当成『无密钥』。这与 SKILL.md『3. Blocks packaging if secrets found』的强承诺存在实现缺口:只承诺了『发现密钥时阻断』,未覆盖『扫描本身失败』。
- [discrepancy] SKILL.md 检测类型清单 vs SECRET_PATTERNS 实际键 — SKILL.md 列出 11 类(AWS Access Keys、R2 Account IDs/Access Keys、Supabase URL/Anon Keys、Stripe、OpenAI sk-、Google Gemini AIza、Generic API Keys、JWT、OAuth Client Secrets、Private Keys、Turnstile)。代码 SECRET_PATTERNS 实为 12 个键,但其中:无 R2 Access Keys 专用规则;无 Google Gemini(AIza…)规则(全目录 grep 'AIza' 零命中);OpenAI sk- 仅在同时出现 api_key/apikey 字样时可被 generic_api_key 捕获,stripe_key 只认 sk_live/sk_test 形式,因此裸的 sk-proj-… 赋值不一定会命中。文档检测类型清单宽于代码能力。
- [discrepancy] 假阳性抑制范围 vs 文档描述 — SKILL.md『Common False Positives』写的是跳过占位符、『Files matching .*test.*, .*example.*, .*sample.*』的文件与注释行。should_skip_match 实际对整行做 substring 包含判断(placeholder in line_lower),且注释判定用 ^\s*(?://|#|/\*|\*)——即抑制粒度是『行』而非『match』或『文件』:真密钥与 'example' 同行即被放过,被注释掉的真实凭证亦不上报。代码抑制面大于文档描述,属漏报向的偏差。
- [discrepancy] 扫描面缺口:不在白名单的高风险文件类型 — SCANNABLE_EXTENSIONS 只含 .ts/.tsx/.js/.jsx/.py/.md/.json/.yaml/.yml/.sh/.bash/.zsh/.sql/.go/.java/.rb/.php/.cs,SCANNABLE_FILENAMES 只补 7 个 dotenv 名。因此 *.pem、*.key、id_rsa、*.ppk、*.p12、*.crt、.envrc、*.toml/*.ini/*.conf/*.properties/*.xml 都不被扫描——private_key 规则(-----BEGIN … PRIVATE KEY-----)写好了却基本没有机会命中这些最常见的私钥载体。SKILL.md 未提示该覆盖边界(仅笼统说 may not catch all credential types)。
- [discrepancy] --force 语义与文档措辞一致性 — SKILL.md 标题写『**Force pack (dangerous, skip scan)**』,暗示跳过扫描;safe_pack.py 中 force = '--force' in sys.argv 之后仍照常执行 run_secret_scan 与 print_findings_report,只是跳过阻断并打印『WARNING: --force flag set, packing anyway despite secrets found!』。实际语义是『扫描照做、仅放行』,措辞『skip scan』不准确。
- [ok] 证据是否指向被扫文件内容/是否落盘 — 两脚本无 open(...,'w')、无 tempfile、无日志写入;匹配结果只经 print/ json.dumps 到 stdout(scan_secrets 的 to_dict 将 match 截断 50、context 截断 100;人类可读报告打印前 80 字符)。『不落盘、不外发』成立;但『不进日志』不成立——终端输出在 agent 会话中会被留存为上下文。
6结论
7847e6113e529f6b…d5c4678cb5