Push Guard 是一个本地 Git pre-push 防护工具,用于拦截可能的密钥泄露。
它会扫描正在推送的内容,报告可能的密钥模式,对所有匹配到的值进行脱敏处理,并以非零状态码退出,从而让 Git 阻止此次推送。
由 Dragon Lady 构建和维护 - github.com/Dragon-Lady - X: @answerislove2
0.4.3 版本移除了内联的 # push-guard: ignore 例外机制。升级后,原本能通过的推送和包可能会开始被阻止。在升级共享钩子或 CI 安装之前,请检查每个使用该工具的仓库:
git grep -n -i 'push-guard:[[:space:]]*ignore'
逐一审查每个匹配项。移除机密和私有材料;二者均不可豁免。
对于合法的工作流/IOC 测试夹具,仅将已审查的精确仓库
路径和精确的 workflow.* 规则 ID 添加到 .push-guard-allow。内联标记现在
是普通文本,包括测试或文档中的标记。不要自动将所有标记行转换为
豁免项。将任何新的豁免项视为安全敏感变更进行审查。
pre-push 钩子从目标端通告的基础提交读取例外,绝不从工作副本或候选提交读取。 新的或扩展的豁免项必须单独审查并确立,之后才能豁免 后续推送。对于多引用推送,仅适用于每个目标基础共享的例外。 没有通告基础的新引用不会获得任何例外。
scan 同样从 --base 读取策略。当针对单独审查的策略快照审计较大的历史
范围时,显式传递
--allow-list-ref <reviewed-40-character-commit-SHA>。这是调用方的显式信任
决策;绝不要用未经审查的候选策略替代。
scan-package 继续使用调用方选择的 --repo 策略,位于
工件之外。扫描不受信任的构建时,提供已审查的策略目录。
在升级已安装的钩子之前,针对实际外发的变更和构建
工件运行新版本的 push-guard scan --base <trusted-base> --head HEAD 和
push-guard scan-package dist/*。归档成员路径可能与源路径不同;请单独审查这些精确路径。
在受影响的仓库完成此迁移之前,保留现有的
已安装版本。
公共落地页:push-guard.netlify.app
静态项目落地页位于 docs/,没有运行时
依赖、外部脚本、分析或远程资源。它可以从 docs/ 目录通过 GitHub Pages 提供服务,或使用
包含的 netlify.toml 部署到 Netlify(无构建命令;发布目录为 docs)。
当前 Netlify 项目使用手动部署。仅 Git 推送不会
更新公共页面:每次发布后发布 docs/ 的内容,并
检查线上版本、社交图片和响应头。直接部署该目录时,docs/_headers
文件会保留安全头。
--send 选项会向配置的目标启用聚合
告警;Git 守卫保持离线。git 子进程读取提交差异、推送的树、
配置的钩子路径和忽略元数据。保留现有钩子入口点,并强化了原始内容和外发历史检查。$HOME 内的选定文件,且不执行任何子
进程,包括 Git。$XDG_STATE_HOME/push-guard-sweep/fp.key(默认
~/.local/state/push-guard-sweep/fp.key)创建一个随机 32 字节 HMAC 密钥,用于稳定、不可猜测的指纹。
此密钥是“默认不存储数据”的显式例外。它不包含
任何扫描数据。状态目录权限为 0700,文件创建权限为 0600。--baseline 和 --output 文件包含脱敏的本地元数据;
--send 存储带密钥的去重回执,绝不存储告警凭据或机密。阻止推送是 Git 对建议的响应。Push Guard 保持只读
且不修改文件。当已知匹配值不是机密时,可使用 git push --no-verify
进行覆盖。
push-guard sweep 命令和 push-guard-sweep 别名提供相同的本地检查。它们需要现有包的 Python 3.11 或更高版本,并且没有额外的运行时依赖。开发测试使用可选的 test extra(python -m pip install ".[test]",然后在仓库根目录运行 PYTHONPATH=src python -m pytest -q)。0.4.0 版本新增了 Sweep。CI 安装该开发 extra 并运行整个 pytest 测试套件,包括遗留的 unittest 用例。发布测试在单独的任务中运行;该任务从不向发布流程提供分发产物。产物构建任务不安装测试依赖。Sweep 不会安装或启用调度器。
push-guard sweep --list-targets
push-guard sweep --roots ~/projects --class stray
push-guard-sweep --format json --output ~/sweep-report.json
push-guard-sweep --baseline ~/sweep-baseline.json
push-guard-sweep report --format html --output ~/sweep-report.html
push-guard-sweep print-timer --interval 7d
每次扫描还会考虑以下默认的历史记录、dotfile、配置和日志位置。--roots 会更改 .env* 和自定义包含项的搜索根目录;使用 --exclude 可缩小其他默认集合的范围。重复使用 --include GLOB 会在现有有界搜索范围内添加文件,重复使用 --exclude GLOB 会移除路径。Glob 匹配基名、相对于 HOME 的路径或绝对路径。排除优先;包含项无法覆盖安全排除或深度限制。CLI 路径参数中的 ~ 会被展开。扫描的路径必须保持在 $HOME 内。
.lesshst。SQLite 的 shell 历史记录文本 与数据库文件是分开的。.bashrc、.zshrc、.profile、.bash_profile、.npmrc 以及
.docker/config.json;.config 下选定的 JSON、TOML、YAML、INI 和 CONF 文件,
最多位于其下四层目录。.env* 文件:位于选定的根目录(默认为 HOME)下,最多六层目录。
依赖树、虚拟环境、Git 元数据和缓存会从此搜索中排除。.log 文件以及 logs/ 内的文件,最多位于
.local/state、.cache、.codex、.config/Cursor/logs 和已配置的
log_dirs 下四层目录。已知的凭据存储(.git-credentials、.aws/credentials、.netrc、
.pypirc、.config/gh/hosts.yml、.config/rclone/rclone.conf、.codex/auth.json)
是 仅元数据 检查。其内容永远不会被打开。每个都会报告为
expected-store,私有模式为 info 严重级别,执行/组/其他权限为 warn 严重级别。line 为 null,且 fp_kind=path 明确
标识一个带键的路径指纹,而不是令牌指纹。Reach Ward 负责这些存储中的
凭据清单。这避免了为模式检查编造秘密值或
行号。所有其他匹配项归类为 stray,通常
为 warn;Google 访问令牌为低严重级别,并标记为短期有效、可能
已过期。不会针对任何提供商检查令牌。
Sweep 复用 Push Guard 的提供商定义和通用赋值匹配器,
新增 Slack、Google、npm、PyPI、JWT 和携带凭据的 URL 形态,并检测
扩展的 OpenAI 令牌形态。通用赋值还要求每个字符至少 3.5 比特的熵。
明显的填充值和变量引用会被忽略;包含 test 等单词的随机提供商令牌仍会
被报告。# push-guard: ignore 无法抑制 Sweep 匹配。完整的私钥块
会整体生成指纹;不完整的块会对其头部之后的有界剩余部分生成指纹。不会持久化任何私钥材料。
发现项包含本地路径、行号、规则、类型、类别、严重级别、模式、fp: 加上
16 个 HMAC-SHA256 十六进制字符,以及字面量 <redacted>。.git 目录或
文件仅通过元数据确立可能的 Git 成员身份。跟踪状态始终为 unknown:Sweep 不执行 Git,也不解析其索引。当路径包含发现的秘密值或令牌形态时,路径会被
脱敏;编码的路径标签会被保守地整体替换。报告从不包含源代码
行。提示仅为打印的建议;Sweep 从不删除历史记录、轮换
凭据、编辑 .gitignore 或更改文件模式。
--list-targets 不进行任何扫描目标或配置内容读取,也不创建指纹密钥或
其他文件。它仅使用默认值和 CLI 目标选项,并且不读取
默认配置文件。将其与 --config、--baseline、
--output 或 --send 组合会被拒绝。它会列出预期的文本读取,并明确
将预期存储标记为仅元数据。二进制内容无法在不读取的情况下被识别,因此未命名的二进制文件可能出现在此预览中,并在扫描期间被
跳过。
所有符号链接、硬链接、FIFO、设备、套接字、已知的浏览器配置文件/密钥环路径、数据库文件名以及压缩/二进制扩展名均会被跳过。目录组件以 no-follow 描述符打开,因此符号链接的父目录无法重定向读取。文本读取每个文件上限为 10 MiB;数据库/压缩魔数、NUL 字节、二进制控制字节或无效 UTF-8 会导致跳过。可识别的二进制/数据库/压缩内容在最多读取 8 KiB 头部后被拒绝;配置/状态读取是独立的且保持不变。具有意外二进制/数据库名称的文件需要有限读取以识别其内容;不使用 SQLite 库。发现过程限制为 250,000 个目录条目、10,000 个目标文件和 10,000 个发现项;基线上限为 10 MiB。一次运行有 100 MiB 的文本读取预算和一个共享的 45 秒检查截止时间。预算耗尽会返回部分、不完整的报告并以退出码 2 退出。慢速存储在不可中断的内核系统调用期间仍可能超过截止时间。
退出码:0 无发现项,1 有发现项(包括信息性存储检查),2 用法、运行时、不完整检查或警报传递错误。有意的策略跳过会单独汇总,并不意味着检查失败。缺少标准可选源文件是正常的。文本、JSON、Markdown 和自包含转义 HTML 可通过 --format 获得。
首次 --baseline FILE 运行会报告所有内容并保存指纹、路径、行、规则 ID 和模式。后续运行仅报告先前成功扫描中不存在的条目,然后原子地替换该基线。更改的模式或行是新条目。已移除的条目会被丢弃。不完整的扫描永远不会更新基线。基线是私有的、经过验证的文件,必须使用相同的指纹密钥;密钥缺失或更改需要先恢复原始密钥才能进行比较。输出报告永远不会覆盖现有文件。所有输出和基线文件在创建时为 0600;目标父目录必须已存在。任何源文件都不会被重新用作状态。
report 执行一次全新的本地扫描,因为默认情况下不保留历史扫描结果。print-timer 将单独的 systemd 用户服务/定时器文本打印到 stdout,不安装任何内容。定时器使用默认值或您保存的配置;在目标机器上验证工具后,请自行审查并保存。
发现和内容扫描共享一个 45 秒检查截止时间;它们不再各自获得单独的配额。发现过程最多获得该共享配额中的 20 秒,为已找到的文件预留时间。已知的历史/配置/日志根目录会在广泛的环境发现之前被考虑。单线程 CLI 还使用临时警报来中断缓慢的模式匹配;其他线程中的嵌入式调用保留协作式检查。报告和写入私有结果在检查停止后进行。不可中断的内核 I/O 停顿无法获得硬性墙钟时间保证。因此,避免挂载尤为重要。
发现过程允许最多 250,000 个条目和 10,000 个目标。字节、条目、目标、发现项或时间耗尽会返回目前已收集的发现项,将报告标记为 incomplete: true,汇总预算原因并以退出码 2 退出。人类可读格式也会显示不完整状态。部分运行永远不会更新基线。
繁重的包/缓存树会被修剪,包括 ~/.local/share/Trash、~/snap、.cargo、.rustup、.npm、.gradle、.m2、.yarn、.pnpm-store、~/go/pkg、~/.local/share/Steam、~/.local/share/flatpak、~/.var/app、~/.local/lib 和 ~/.nvm。~/.cache 下的包缓存(uv、pip、pypoetry、huggingface、torch、ms-playwright)以及 ~/.codex/plugins/cache 也会被修剪。环境发现也会修剪缓存目录;有界日志检查仍会考虑配置的日志根目录。即使使用显式根目录或包含模式,硬性安全排除仍然有效。
Sweep 读取 Linux 的 /proc/self/mountinfo 以及描述符挂载元数据。这些是内核元数据,而非目标/配置内容:--list-targets 仍然不读取任何扫描目标或配置内容,也不创建任何状态。HOME 之下的每个挂载点都会在 stat、open 或列出之前 被排除,包括文件绑定挂载和同设备绑定挂载。已打开的描述符在列出或读取之前,会再次对照 HOME 的设备与内核挂载 ID 进行检查。FUSE、rclone、NFS、CIFS 和 autofs 类型的 HOME 文件系统会被拒绝作为扫描根;未知的挂载元数据会产生一份不完整报告,且不读取目标。排除项计入 skipped_mount。不存在任何可遍历已挂载子树的覆盖选项。HOME 内的配置/状态/报告/基线路径在使用前也会拒绝已知的已挂载子树。HOME 之外由工具显式拥有的 XDG/输出路径仍然允许。检查不会针对特权重新挂载锁定挂载命名空间。
Sweep 基于单独审查过的 guard-hardening 提交;它对现有 guard 入口点的唯一改动是 Sweep 分发和一行帮助文本。它没有对 hook 主体或对外 Git 检查做任何改动。
配置:$XDG_CONFIG_HOME/push-guard-sweep/config.toml,默认
~/.config/push-guard-sweep/config.toml,归当前用户所有,权限模式为 0600。
--config FILE 用于选择另一个文件。XDG 配置/状态可以位于 HOME 之外;
被扫描的目标则不可以。示例如下:
roots = ["~/projects"]
include = ["*.trace"]
exclude = ["scratch/*"]
log_dirs = ["~/app-logs"]
alert_channels = ["stdout"]
# alert_channels = ["slack"]
# alert_slack_webhook_env = "SWEEP_SLACK_WEBHOOK"
告警需要 --send,即使已配置通道也是如此。空通道表示
stdout。支持的通道及 Net Ward 兼容键如下:
| 通道 | 配置 |
|---|---|
stdout | 无需凭据;在命令输出中汇总摘要 |
slack | alert_slack_webhook_env 指定一个环境变量 |
ntfy | alert_ntfy_topic,可选 alert_ntfy_token_env |
email | alert_smtp_host、alert_smtp_port、alert_smtp_security(starttls/ssl)、alert_smtp_from、alert_email,可选 alert_smtp_username 和 alert_smtp_password_env |
切勿将 webhook URL、令牌或 SMTP 密码放入配置:只放它们的
环境变量名。除回环 HTTP 外,必须使用 HTTPS;
除回环外,必须使用 SMTP 加密。重定向会被拒绝。连接
使用五秒超时。发送失败仅按通道报告,不会
泄露含凭据的 URL。告警仅携带按规则、
类别和严重性汇总的计数,不含路径、指纹、源代码行或机密值,
并以 run push-guard-sweep report locally for details 结尾。私有密钥
回执会在 24 小时内抑制发往同一目标的重复匹配告警;
目标变更后可再次发送。该软件包未配置任何真实的告警目标。