☰
使用 repowise security 扫描 Git 全历史泄露密钥:从 Codex 斜杠命令到底层实现
2026/10/9 7:31:03 网站建设 项目流程

【免费下载链接】repowise

Codebase intelligence for AI and humans: code health scores, auto-generated docs, git analytics, dead code detection, and architectural decisions via MCP.

项目地址:https://gitcode.com/gh_mirrors/re/repowise
点击查看免费下载

本文以repowise-security斜杠命令为入口,系统讲解 Repowise 的本地安全信号扫描能力:如何用repowise security scan --history走查整个 Git 历史、找出那些"已被删除但永远留在仓库历史里"的泄露密钥与危险模式,并解读其底层实现(blob 去重、提交溯源、幂等持久化)与模式注册表、CI 闸门等配套能力。读完你不仅能熟练使用该命令的每一种参数组合,还能理解它"纯本地、无 LLM、可重复运行"的设计从何而来。

为什么需要"全历史"扫描

repowise init与repowise update在索引工作树时,已经用同一套本地模式注册表跑过一次安全扫描——工作树里当前存在的代码,会在初始化/更新时被检查一遍。但有一个致命盲区:一个密钥如果在某次提交中被引入、又在后面的提交中被"删除",工作树扫描永远看不到它。而 Git 的历史是不可变的——只要该提交存在,密钥就永远留在.git对象库里,留在每一个克隆里。真实世界的泄露事件,绝大多数正是这种"提交上去→发现后删除→但历史里已经永久留存"的形态。

repowise-security这个斜杠命令解决的就是这个问题:它触发repowise security scan --history,走查完整 Git 历史(而非当前工作树),找出那些被后续提交移除的泄露密钥,并把结果持久化到本地security_findings表中,随后即可在本地 server 的安全 API 与 UI 中查看。

与文档配套的实现位于 security_cmd.py(CLI 命令层)与 history_scan.py(核心扫描引擎)。该命令的完整 docstring 明确写道:

Walk the entire git history of the repo (not just the working tree) with the same pattern registry the indexer uses, and persist any secrets or risky patterns into the sharedsecurity_findingstable — tagged with the commit that introduced them. Re-runs are idempotent.

这个斜杠命令从哪里来

repowise-security.md是 Repowise 为Codex CLI打包的斜杠命令之一,位于packages/cli/src/repowise/cli/agent_targets/_data/codex_prompts/repowise-security.md。根据 codex.py 的实现说明,Codex 的斜杠命令与 Claude Code 不同:Claude Code 的命令来自插件,而 Codex 的斜杠命令只能来自本地~/.codex/prompts/目录(Codex 插件清单没有承载斜杠命令的槽位),因此由 CLI 安装命令从包数据写入该目录,文件统一使用repowise-前缀避免与用户其他工具冲突。安装后,在 Codex 中输入/repowise-security(或文档语境中的/prompts:repowise-security)即可触发。

该提示词文件的核心是一条执行原则:命令本身是本地模式注册表的"驱动器",Agent 负责判断参数、执行命令、汇总结果,但不参与检测逻辑、不调用 LLM 生成 findings。文档明确强调:

No LLM — pure local scan. Findings persist tosecurity_findingsand show up in the local server security API / UI. Re-runs are idempotent.

执行步骤(Agent 工作流)

提示词文档要求 Agent 按以下步骤执行:

  1. 前置检查:若仓库不存在.repowise/目录,说明仓库尚未索引,应提示 "This repo isn't indexed yet. Run/prompts:repowise-initfirst." 并停止。这是因为历史扫描需要复用索引期的模式注册表与持久化层(security_findings表所属的本地库由.repowise/管理)。
  2. 根据$ARGUMENTS决定模式(详见下一节),执行对应命令。
  3. 呈现简短汇总:报告扫描的文件数、存储的 findings 数,并按严重级别 / 类型(kind)分组;不得打印原始密钥值——如果输出中出现任何疑似原始秘密,只总结种类与路径。

最后一条"不打印原始秘密"是一条硬性安全规则,它不止是提示词纪律,也是底层实现的既定行为:security_gate.py的模块 docstring 说明,离开该模块的只有掩码后的片段(masked snippet),且指纹(fingerprint)也基于掩码片段计算,"so neither a CI log nor the committed baseline holds a raw value"——CI 日志与基线文件都不会留下原始值。

五种模式与参数对照

提示词文档定义了$ARGUMENTS到命令的完整映射:

$ARGUMENTS实际执行命令说明
(默认 / 无有效参数)repowise security scan不重新扫描工作树,仅打印提示并退出:提醒用户工作树扫描已在 init/update 期间完成,建议使用--history
history/full/scan historyrepowise security scan --history走查完整 Git 历史
json+ historyrepowise security scan --history --format json机器可读的 JSON 汇总
all/all-patterns+ historyrepowise security scan --history --all-patterns在历史模式中加入全部 code-smell 模式
一个路径repowise security scan --history --path <dir>扫描指定仓库路径
since <rev>/to <rev>对应传递--since/--to限定 Git 修订范围

文档给出的可直接复制的命令示例:

repowise security scan --history repowise security scan --history --since v1.0.0 --to HEAD repowise security scan --history --all-patterns --format json

这些参数与 CLI 实现完全对应。在 security_cmd.py 中,security scan子命令定义了以下选项:

  • --history:布尔开关,默认关闭。关闭时命令是一个"无操作占位"——它只在 stderr 打印黄色提示 "Working-tree scanning runs automatically duringrepowise init/repowise update",并引导用户传--history;若请求了 JSON 格式,则输出{"scanned": false, "reason": "history-mode-not-requested"}以保持 stdout 可解析。
  • --since <rev>:Git 修订范围的下界(排他),默认扫描全部历史。
  • --to <rev>:上界(包含),默认到 HEAD。
  • --path <dir>:目标仓库路径,默认取当前工作目录 / 工作区主仓库。
  • --all-patterns:历史模式下也报告 code-smell 模式(eval、os.system、弱哈希等)。默认的历史模式只报告泄露密钥模式(硬编码凭据与已知厂商的 key/token/PEM 形状),以降低噪音。
  • --format:输出格式,json为机器可读汇总;另有一个隐藏的--output作为历史遗留别名,显式传入时优先。

非 JSON 模式下,命令输出的摘要包括:Commits scanned、Blobs scanned、Files scanned、Findings stored,以及有数据时的By severity与By kind分组计数。

默认"秘密优先"与--all-patterns的取舍

提示词文档明确说明:默认历史模式只报告泄露密钥模式(hardcoded_password/hardcoded_secret),传--all-patterns才纳入 code-smell 模式(eval、os.system、弱哈希等)。这一设计不是偷懒,而是有明确依据的设计决策。

从 history_scan.py 的模块 docstring 可以看到完整推理:

Most of the 11 patterns are code smells (eval/os.system/weak_hash) rather than leaked credentials; running those across all of history produces mostly noise ("os.system in a two-year-old commit") with little to act on.

也就是说,模式注册表里大部分是代码异味而非凭据泄露;把它们全部跑到两年历史上去,得到的绝大多数是"某次古老提交里有个 os.system"这类无从下手的噪音。因此历史模式被设计为与真正的秘密扫描器(文档原文提及 gitleaks / trufflehog 这类工具)互补而不是噪音化的替代品。实现上,HistorySecurityScanner._passes_gate在secrets_only=True时仅保留SECRET_KINDS集合内的类型。

SECRET_KINDS定义在 security_scan.py(两套扫描面——工作树与全历史——共享同一模式注册表与持久化层),结合security_gate.py中的_RULE_TEXT可见其成员,例如:

  • hardcoded_password:赋给 password 命名变量的引号字面量
  • hardcoded_secret:赋给 key / secret / token 命名变量的引号字面量
  • aws_access_key、github_token、slack_token、google_api_key、stripe_key:按厂商值形状(而非变量名)匹配的密钥
  • private_key_pem:带正文的 PEM 私钥

而--all-patterns会额外纳入eval_call、exec_call、pickle_loads、subprocess_shell_true、os_system、fstring_sql、concat_sql、tls_verify_false、weak_hash、unsafe_inner_html等 code-smell 类型。

底层原理:如何高效扫描全部历史

历史扫描的难点在于"全量":一个中型仓库可能有数万次提交、数十万个文件版本,逐提交逐文件扫描是 O(提交数 × 文件数) 的灾难。HistorySecurityScanner.scan_history的设计对这一点做了三个关键优化,均记录在 history_scan.py 的 docstring 中:

1. 按唯一 blob 扫描,而非按"提交 × 文件"。git rev-list --objects --all会按内容哈希去重枚举每个对象,因此每个不同的 blob 只被扫描一次——同一个文件内容被 1000 个提交引用,也只需扫一遍。_unique_blobs同时记录每个 blob 首次出现的路径,用于溯源归属。

2. 引入提交的溯源来自单次git log --reverse --raw遍历。_blob_introductions用一次反向日志遍历(--format=%H%x1f%aI --raw --no-abbrev),把每个 blob 归属到最早引入它的提交(post-image blob 即新增内容)。docstring 特别标注了--no-abbrev是承重(load-bearing)的:--raw默认把 blob SHA 缩写为 8 字符,而rev-list --objects输出完整 40 位,缩写会让后续查找永远落空。这样避免了对每个提交各跑一次ls-tree的 O(提交数 × 文件数) 循环。

3. 内容读取走单个git cat-file --batch进程流式拉取。_read_blobs_batch把全部 blob SHA 一次性喂给一个批处理进程,避免逐个 fork git 进程。

扫描流程在scan_history中串联:先_list_commits取范围(since..to、to、since..HEAD或--all四种区间语法)与作者时间,再取唯一 blob、做引入归属、批量读内容,然后对每个属于源码扩展名的 blob 调用底层SecurityScanner.scan_file,最后通过persist落库,并附带引入提交的 SHA 与作者时间。测试材料(test/fixtures/mock/example 路径)中的命中会被降级为low严重性——真实密钥依然入库,但不会污染高危报告(见security_scan.py的_is_low_severity_path)。

幂等性由持久化层的唯一约束保证:security_findings表上存在(repository_id, file_path, kind, line_number, commit_sha)唯一约束(迁移 0041),重复运行同一范围只会插入新发现,已存在的行保持不变——这正是提示词文档 "Re-runs are idempotent" 的实现基础。

不要编造 findings:诚实输出是底线

提示词文档特别强调了一条纪律:Never invent findings. If the scan stores zero findings, say so plainly.(绝不编造 findings;若扫描结果为零,就直说为零。)这是 Agent 使用该命令时必须遵守的诚实性要求——命令是确定性本地扫描,Agent 的职责是如实转述汇总结果,而不是为了"显得有用"而夸大或虚构安全问题。对应地,CLI 实现也会如实输出Findings stored: 0这样的计数。

与/prompts:repowise-risk的分工

提示词文档最后给出了一条重要的场景区分:

For live-change review priority (not secret scanning), use/prompts:repowise-risk.

即:repowise-security定位是秘密/危险模式的回溯扫描(尤其是历史泄露);而如果你关心的是当前变更的风险评估优先级(live-change review),应该使用/prompts:repowise-risk。两者关注不同的时间维度——一个是"历史里埋了什么雷",一个是"这次改动带来多大风险"。

延伸:从历史扫描到变更闸门(security check)

虽然提示词文档聚焦security scan --history,同一命令族下还有repowise security check,可把它视为历史扫描的"向前看"镜像:它只读 git、不需要索引和数据库,对一次变更(REVSPEC,如origin/main...HEAD、base..head或单个提交;--staged则检查暂存区,供 pre-commit 钩子使用)做两遍扫描——在变更头部扫描改动文件、在其内部每个提交中按秘密类型检查,因此"在变更里提交了密钥又删掉"依然会失败,因为它永远留在历史中。--fail-on high|med|low决定失败阈值(默认high),--baseline/--write-baseline支持基线管理,输出格式覆盖table、json、github(注解 +$GITHUB_STEP_SUMMARY)、sarif、gitlab。门禁的判定语义在 security_gate.py 中有明确声明:

Pattern matches from a fixed regex registry: a floor, not a scanner. A clean result means no pattern matched a changed line, not that the change is safe.

仓库方还可以通过两个输入定制扫描:在.repowise/config.yaml的security.patterns中追加自定义秘密形状(custom:<name>),或在 finding 所在行写repowise-security-ignore(可只忽略列出的 kind)注释将其静默——被静默的 finding 仍会计数与列出,只是不过闸、不进基线。所有片段在输出中均被掩码。

小结

repowise security给 Agent(尤其是 Codex 斜杠命令场景)提供的是一条完整的安全信号闭环:工作树扫描随 init/update 自动发生,repowise security scan --history补齐"已被删除的历史泄露",repowise security check把守每次变更的准入。全流程纯本地、无 LLM、幂等可重跑,findings 统一落在security_findings表并在本地 server 的 API/UI 可见。底层通过 blob 去重、单趟溯源与批处理读取,让全历史扫描在工程上可负担;通过"默认秘密优先、--all-patterns可选"与"测试路径降级",让输出保持可行动性;通过"掩码片段外泄、指纹基于掩码、绝不打印原始值",守住泄露面不扩大的底线。

【免费下载链接】repowise

Codebase intelligence for AI and humans: code health scores, auto-generated docs, git analytics, dead code detection, and architectural decisions via MCP.

项目地址:https://gitcode.com/gh_mirrors/re/repowise
点击查看免费下载

相关推荐

上一篇:Triton 双引擎时代:CUDA Tile IR 后端与最优软件流水线/Warp Specialization 研究速览
下一篇:如何快速找回遗忘的压缩包密码:ArchivePasswordTestTool完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询