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',
誤検知の抑制には2つの厳格なルールがあります。プレースホルダー辞書に該当した場合はスキップされ、行全体に対する部分一致判定が行われます(行内の任意の位置に '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外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| cli | repomix(第三方打包 CLI,需用户自行预装) |
| cli | python3(sys.executable 拉起同目录扫描器;仅标准库 os/re/sys/json/subprocess/pathlib) |
| package | repomix(npm 生态包,本 skill 只调用命令,不负责安装) |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: python3 scripts/safe_pack.py <directory> [--output file.xml] [--config repo
- 环境变量 / API Key: ベンダーサービス認証情報の読み取り: 設計通り、正規表現マッチングのために対象ディレクトリ内のファイルテキスト(.env/.env.example/.env.local/.env.production/.env.de を含む)を読み取ります
- 本地工作目录: ファイルの読み書きと成果物出力: 両スクリプト自体によるファイル書き込みはゼロ(open(...,'w') なし、一時ファイルなし、ログファイルなし)
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…