mcpField
G
github-sensitive-data-cleanup

github-sensitive-data-cleanup

github.com/daymade安全1.5k360 次安装今天更新
代码仓库

关于 github-sensitive-data-cleanup

GitHub Sensitive Data Cleanup 是一项技能,用于扫描 Git 仓库、从历史记录中移除敏感内容,并安全地将改写后的历史推送到 GitHub。脚本负责扫描、备份、改写和验证;安全检查失败时会请求人工确认。

主要功能

  1. 01扫描密钥和私人上下文
  2. 02改写 Git 历史前先备份
  3. 03使用 git-filter-repo 改写历史
  4. 04改写后验证清理结果
  5. 05推送前检查仓库可见性
  6. 06安全检查失败时请求人工确认

适用场景

  1. 01查找仓库中暴露的密钥或私人信息
  2. 02从 Git 历史中移除敏感内容
  3. 03清理后准备受控的强制推送
  4. 04评估指定暴露位置及残余风险

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daymade/claude-code-skills --skill github-sensitive-data-cleanup

安装后在 Claude Code 中输入/github-sensitive-data-cleanup确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
ai_semantic_review_prompt.md
incident-lessons.md
tooling_notes.md
scripts/
rewrite_history.py
safe_push.py
scan_repo.py
verify_cleanup.py

详细介绍

GitHub 敏感数据清理

概述

本技能将指导你安全地从 Git 仓库历史记录中移除敏感数据,并将清理后的历史记录推送到 GitHub。它总结了真实事件中的宝贵经验:先扫描,再备份,然后重写;重写后进行验证;向公开仓库强制推送前,务必检查其可见性和分叉数量。

随附的脚本可自动完成机械性操作:

  • scripts/scan_repo.py — 扫描仓库中的机密信息和私密上下文。
  • scripts/rewrite_history.py — 创建备份,并使用 git-filter-repo 重写历史记录。
  • scripts/verify_cleanup.py — 确认敏感内容已被移除。
  • scripts/safe_push.py — 验证仓库可见性并安全推送。

本技能的设计原则是谨慎。 如果任何安全检查未通过,它都会停止并请求人工确认,而不会继续操作。

何时使用本技能

当用户出现以下情况时,触发本技能:

  • 说“扫描敏感数据”“扫描敏感信息”或“看看仓库有没有泄露”。
  • 想要“清理 git 历史”“净化历史”“重写历史”或“从历史记录中移除机密信息”。
  • 不小心将机密信息、私有域名、内部 IP 或个人身份信息(PII)推送到了公开仓库。
  • 准备向公开仓库强制推送(即使没有敏感数据)。
  • 提到 git filter-repo、BFG、git-filter-branch 或重写历史记录。

先决条件

在每台机器上安装一次以下工具:

# git-filter-repo (modern replacement for git-filter-branch)
brew install git-filter-repo

# gitleaks (secret scanner)
brew install gitleaks

# GitHub CLI
brew install gh

脚本假定 git-filter-repo 和 gitleaks 位于 PATH 中。运行破坏性操作前,本技能会检查这些工具是否可用。

安全规则(不可妥协)

  1. 先扫描,再做决定。 切勿凭猜测重写历史记录。
  2. 重写前创建备份。 使用 git bundle 或全新裸克隆。
  3. 任何推送前,使用 gh repo view 验证仓库可见性。 不要根据 URL 或目录名称推断仓库是公开还是私有。
  4. 切勿使用 --no-verify 绕过钩子。 如果 PII Guard 钩子未通过,请修复根本问题或添加允许列表;不要绕过检查。
  5. 使用一个明确绑定到已保存远程提交的租约。 租约被拒绝时应停止推送;切勿改用 --force,也不要刷新预期提交以绕过并发工作。
  6. 重写后进行验证。 git log 干净并不足够;请重新运行扫描器并进行 AI 语义审查。
  7. 存在分叉的公开仓库需要格外谨慎。 分叉和克隆可能保留旧历史记录。选择处理方式前,请核实已确认的泄露内容和所有权;仅在授权的接收对象和消息范围内联系所有者。

工作流

步骤 0:确认仓库路径和当前分支
cd /path/to/repo
git status --short
git remote -v

使用准确的仓库根目录。扫描和验证支持普通、关联工作树和裸仓库根目录。重写要求使用独立克隆:关联根目录以及带有关联工作树的普通或裸仓库均会被拒绝。裸镜像没有可检查的工作树状态;请改为验证其 Git 身份。

步骤 0.5:绑定每个暴露面

执行任务的 Agent 会在私有任务证据中记录已知发现的确切公开位置,以及授权的修复范围。根据该发现开展工作,不要无限扩展为对整个仓库的全面清查。请将以下暴露面区分开来:

暴露面判定依据和修复责任方
当前文件和托管发布文本阅读点名的已发布文件、PR 标题/正文或其他受影响文本。常规更正只会关闭这些当前值;托管内容的编辑请使用 github-ops。
Git 对象和引用将文件 blob、提交消息以及受影响的分支/标签历史绑定到不可变对象。本 Skill 负责明确获批的历史重写和确切引用的回读。最新提交干净并不能证明祖先历史干净或旧对象已移除。
PR 正文编辑历史检查指定 PR 的修订记录,即使当前正文已更正。请使用 github-ops 的 Pull Request Operations 参考中的 PR 正文编辑历史清理;该流程负责确切修订版本的选择、私有备份、删除授权和保留情况回读。
缓存视图、PR Git 引用、分叉仓库和克隆副本分别检查已知定位符和所有权。Git 历史重写或分支删除并不能证明这些位置的数据已擦除;第三方副本和由 GitHub 控制的引用可能需要单独处理。

区分保存在私有备份中的对象与仍可公开获取的对象。声明可匿名访问时,应在不进行身份验证的情况下测试确切定位符,并将返回内容与预期的不可变对象进行比对;仅凭经过身份验证的读取、错误页面或重定向,无法证明内容可匿名访问或已移除。只有当发现指向发布版本、附件或日志时,才将它们纳入检查;未检查的暴露面应标记为未验证。

针对每个指定的暴露面,报告检查了什么、结果能证明什么,以及还有什么仍然公开或未经验证。捆绑的 Git 扫描器不会检查托管的 PR 修订历史,也无法证明缓存/分叉仓库中的内容已移除。在已完成的范围内未发现问题时,应停止该项审计;这并不意味着被排除或未知的暴露面也已清理干净。获批的更改验证完毕后,应报告残留问题,而不要悄然扩大范围,进行历史重写、删除或对外发送消息。

步骤 1:扫描敏感数据

运行扫描器,查找需要移除的内容:

uv run --with gitpython scripts/scan_repo.py --repo /path/to/repo --output /tmp/scan-report.json

扫描器会自动从仓库根目录下的 .pii-patterns 加载仓库专用模式。如果该文件包含真实的私有域名,请勿提交该文件——将其添加到 .gitignore,或保存在仓库之外。如果工作树中有未跟踪文件,rewrite_history.py 将会中止。

启用第 3 层(来自 gitleaks 配置和可选身份文件的私有基础设施上下文):

uv run --with gitpython scripts/scan_repo.py \
  --repo /path/to/repo \
  --gitleaks-config ~/scripts/git-pii-guard/gitleaks.toml \
  --identities-file ~/.config/github-sensitive-data-cleanup/identities.txt \
  --output /tmp/scan-report.json

--gitleaks-config 标志会从你的私有 gitleaks 配置中读取 private-domain-context 和 private-ip-context 规则。真实模式保留在你的私有配置中;不会复制到此公开技能中。

检查 /tmp/scan-report.json。其中包括:

  • gitleaks 发现项(机密、API 密钥、令牌)。
  • 自定义模式匹配项(内部 IP、电话号码、PII)。
  • 第 3 层上下文匹配项(配置中的私有域名、IP、身份信息)。
  • 提醒:对正则表达式无法捕获的内容进行 AI 语义审查。

解读扫描结果前,请确认扫描已成功完成。执行错误会导致扫描未完成。即使发现项为零,也仍需继续执行步骤 1.5;这不代表可以认定结果干净,也不构成重写历史的授权。

步骤 1.5:AI 语义审查(第 4 层)

正则扫描器(第 1–3 层)无法发现新出现的私有上下文:真实姓名、 项目代号、转录片段、内部会议引用或架构描述。你必须进行 AI 语义审查。

对本任务冻结的 refs 和文件集使用 references/ai_semantic_review_prompt.md 中的 提示词,包括没有扫描器命中的材料。扫描器的发现用于确定检查优先级,但不能界定审查范围。请在公共代码仓库之外记录已检查的项目、发现和未审查的范围。

如果已完成的扫描和语义审查在该范围内未发现敏感信息,停止。不要重写历史记录。未完整覆盖的部分仍属未经验证,而非已确认安全。

如果跳过此步骤,你可能会推送 gitleaks 根本不知道要查找的私有上下文。

步骤 2:对发现进行分类并选择补救措施

针对每项发现,决定:

  • 轮换凭据(如果是仍有效的密钥,务必优先执行)。
  • 从历史记录中移除(适用于私有域名/IP、PII,或仍会暴露内部上下文的已轮换密钥)。
  • 添加到 .gitignore 或允许列表(仅适用于误报)。

根据发现内容所在的位置选择处理路径:通过 github-ops 修正当前托管内容或清除已获批准的 PR 正文修订;第 3–7 步仅用于单独获准的 Git 重写。批准 PR 修订内容不代表获准重写分支或标签。

必须先轮换仍有效的密钥,再清理历史记录。 从历史记录中移除并不会使已泄露的密钥失效。

选择处理路径前,先核实所有权声明。 处理路径取决于谁控制该资源:对于你控制的 fork/分支/文档,可以自行删除;对于第三方控制的资源,则应通过 GitHub Support / 所有者配合的路径处理。审计报告中关于某个账号、fork 或文档所有者的标注应视为未经核实的声明,即使措辞非常肯定也是如此——请检查 gh api repos/<owner>/<repo>、gh auth status,或询问用户。(2026-09-18: 一份继承而来的报告将外部贡献者的 fork 标注为用户自己的 Agent 账号,悄悄把处理路径从“提交支持工单”降级为“自行删除”——实际执行时就会失败。)

步骤 3:准备替换文件

创建一个文本文件,每行一个替换项,格式遵循 git-filter-repo --replace-text:

literal:internal.example.com==>example.com
literal:private.example.org==>example.org
literal:sk-example-aaaaaaaaaaaaaaaa==>sk-example-REDACTED

将这些内容替换为实际的敏感字符串。不要提交真实值;请将替换文件保存在代码仓库之外。

精确匹配字符串时使用 literal:。正则替换时使用 regex:(仅在你确信模式正确时使用)。

将此文件保存在仓库之外,例如 /tmp/sensitive-replacements.txt。

步骤 4:保存发布目标,然后备份并重写

重写前,请将以下观测到的值保存到仓库之外的现有任务状态中:选定的远程名称及已验证的传输配置、完整限定的 GitHub 目标(HOST/OWNER/REPO)、确切的目标分支引用,以及该分支远程提交的完整 SHA。不要将凭据存入此状态;保留已配置的凭据机制。解析选定远程的唯一推送 URL,包括已配置的推送 URL 和 URL 重写规则。将 URL 传递给其他命令或保存之前,拒绝包含凭据的 URL;身份验证仍使用现有凭据机制。查询分支前,请检查已验证的不含凭据的 URL 解析结果是否保持不变。解析结果发生变化即视为未知,并停止快照操作。普通的 ls-remote origin 可能会采用不同的获取 URL:

git -C /path/to/repo remote get-url --push --all origin
git -C /path/to/repo ls-remote --get-url '<verified-push-url>'
git -C /path/to/repo ls-remote --refs '<verified-push-url>' refs/heads/main

必须有一个已验证的推送 URL,且引用查询成功,并且恰好返回一行与之匹配的完整 SHA/引用。分支不存在或查询失败,都不能作为已保存的改写前状态。不要从已重写的本地分支或过时的远程跟踪引用推导此值。如果此任务在重写后恢复执行,请重用已保存的目标和改写前状态;缺少状态则停止推送。

--yes 确认显示的改写目标和备份路径。它不授权重写共享历史,也不绕过 git-filter-repo 自带的新克隆检查。在原始检出中保留待处理的工作,并在必要时使用下方的独立克隆方案。

uv run scripts/rewrite_history.py --repo /path/to/repo \
  --replacements /tmp/sensitive-replacements.txt \
  --backup /tmp/repo-backup.bundle \
  --yes

# Entity leaks live in commit MESSAGES too, not just file content. Cover both:
uv run scripts/rewrite_history.py --repo /path/to/repo \
  --replacements /tmp/sensitive-replacements.txt \
  --message-replacements /tmp/sensitive-replacements.txt \
  --backup /tmp/repo-backup.bundle \
  --yes

此脚本会:

  1. 验证仓库是独立的,且没有关联的工作树。
  2. 验证已安装 git-filter-repo 且可执行。对于普通克隆,要求成功查询并确认状态干净;对于裸镜像,则使用已验证的无工作树布局。查询失败会停止重写。
  3. 为当前状态创建 git bundle 备份。
  4. 使用 git bundle verify 验证备份包。
  5. 运行 git filter-repo --replace-text,不绕过其自带的安全检查。如果指定了 --message-replacements,还会运行 --replace-message,以便重写提交消息,而不仅仅是文件 Blob——只清理 Blob 可能会让实体名称仍留在提交消息中。
  6. 报告旧提交哈希和新提交哈希。

如果备份或验证步骤失败,脚本会停止。 不要手动继续。

步骤 5:验证清理结果
uv run scripts/verify_cleanup.py --repo /path/to/repo --replacements /tmp/sensitive-replacements.txt

此操作会重新运行扫描器,并检查所有提交中是否仍存在任何原始敏感字符串。如果发现任何内容,请返回步骤 3。在同一任务范围内,对重写后的引用重复步骤 1.5,然后再推送;仅成功通过模式检查并不代表语义验证已完成。两项检查均通过后,解析并保存已验证的选定分支对应的确切本地提交:

git -C /path/to/repo rev-parse refs/heads/main^{commit}

之后的本地更改会使此验证绑定失效。选择新的已验证本地 SHA 前,请重新检查更改后的候选项。

步骤 6:检查可见性并推送

git-filter-repo 可能会移除 origin。如果保存的具名远程不存在,请使用之前已验证的传输配置和现有凭据机制恢复;不要猜测 URL 或凭据。如果远程存在但不一致,请停止并核实目标。基于本地源创建的克隆,并不意味着该本地源 URL 就是获准使用的 GitHub 目标。

提供已保存的发布目标和旧远程 SHA,以及步骤 5 中已验证的本地 SHA。保留远程名称,以便正常运行其钩子:

uv run scripts/safe_push.py --repo /path/to/repo --remote origin --branch main \
  --expected-repository '<saved-host/owner/repo>' \
  --expected-remote-sha '<saved-full-remote-sha>' \
  --verified-local-sha '<verified-full-local-sha>' \
  --yes

此脚本会:

  1. 将选定的推送 URL 绑定到已保存的仓库目标,然后验证该目标的可见性和派生仓库元数据。
  2. 如果仓库是公开的且有派生仓库,会发出明确警告。
  3. 检查本地候选是否与已验证的 SHA 一致。
  4. 使用明确的预期提交租约,向已保存的远程分支执行一次推送。租约或钩子拒绝推送时,操作会停止;不会回退到强制推送。
  5. 拒绝添加 --no-verify。

如果 PII Guard 钩子失败,请修复问题后重新运行。不要绕过钩子。

迁移:现在,只提供 --repo、--remote 和 --branch 的调用方会因默认拒绝而失败。请在重写前捕获远程目标/前置状态,并添加上面的三个绑定参数。--yes 无法补足缺失的证据。现有的备份、替换和可选消息替换参数仍受支持。

步骤 7:推送后验证

推送成功后:

  1. 读取回确切发布的引用和重写涵盖的指定内容。仅验证本地候选并不能证明托管端对象已更改。
  2. 检查受影响的未关闭 PR 是否仍指向有效提交。重写历史可能会破坏它们的分支;不要修复无关 PR,也不要擅自丢弃其工作。
  3. 重新检查步骤 0.5 中记录的暴露面。分别报告 Git 验证结果、PR 修订清理、缓存对象访问和第三方副本情况。对于无法执行的检查,即使 Git 扫描器结果干净,也仍应标记为未验证。

捆绑脚本的作用

scripts/scan_repo.py

运行 gitleaks 和自定义的 bash/grep 层,检查 gitleaks 未覆盖的模式(私有域名、内部 IP、中国电话号码和某些 PII)。 输出 JSON 报告。

uv run --with gitpython scripts/scan_repo.py --repo /path/to/repo --output /tmp/report.json
scripts/rewrite_history.py

创建备份包并运行 git filter-repo --replace-text。传入 --message-replacements <file>,还会通过 --replace-message 重写提交消息(通常同一个替换文件即可用于两者)。

uv run --with gitpython scripts/rewrite_history.py \
  --repo /path/to/repo \
  --replacements /tmp/sensitive-replacements.txt \
  --message-replacements /tmp/sensitive-replacements.txt \
  --backup /tmp/repo-backup.bundle \
  --yes
scripts/verify_cleanup.py

重新运行扫描器,并使用原始敏感字符串 grep 所有提交,涵盖 blob 内容(对每个提交运行 git grep)和提交消息(对所有引用运行 git log,使用带哈希注释的记录格式),因此,如果重写时漏掉了 --replace-message,验证仍会失败。

uv run --with gitpython scripts/verify_cleanup.py \
  --repo /path/to/repo \
  --replacements /tmp/sensitive-replacements.txt
scripts/safe_push.py

检查可见性并安全推送。

uv run --with gitpython scripts/safe_push.py --repo /path/to/repo --remote origin --branch main \
  --expected-repository '<saved-host/owner/repo>' \
  --expected-remote-sha '<saved-full-remote-sha>' \
  --verified-local-sha '<verified-full-local-sha>' \
  --yes

特殊情况处理

仓库中有未关闭的 PR

重写历史会使未关闭 PR 中的提交引用失效。推送后:

  1. 请 PR 作者将其分支变基到新的 main 上。
  2. 如果 PR 是你创建的,请删除本地分支,获取重写后的 main,然后将更改变基为新的提交。
仓库存在复刻仓库

公开复刻仓库在所有者同步之前会保留旧历史记录。对于高风险泄露(有效密钥、生产环境凭据),请考虑:

  1. 立即轮换凭据(必须)。
  2. 联系 GitHub Support,请其移除敏感数据的缓存视图。
  3. 向复刻仓库所有者发送简短、客观的通知。

Support 请求和与复刻仓库所有者的沟通必须遵循 github-ops 的授权约定;仅获准重写并不意味着有权执行这些操作。GitHub Support 是否接受请求取决于具体情况,无法保证彻底清除。请参阅官方移除限制。对于风险较低的问题,请记录任何仍保留或未经核实的暴露情况以及所选的处理边界;不要声称重写就等于彻底清除。

git filter-repo 报告“需要全新克隆”

保留工具的原生拒绝行为。保留原始检出目录,并创建一个全新的独立镜像;--no-local 可避免使用本地克隆的对象共享捷径:

git clone --mirror --no-local /path/to/repo /tmp/repo-mirror.git

克隆成功后,在其 Skill 目录中运行随附的 rewrite_history.py,并指定 --repo /tmp/repo-mirror.git。 执行此操作前,保存获准的发布目标/预映像,并在重写期间保留它。不要添加 --force 来绕过全新克隆保护,也不要为满足该要求而删除其他会话的工作。

gitleaks 误报

如果 gitleaks 将文档示例或测试固件标记为问题,请在仓库的 .gitleaks.toml 或 .gitleaksignore 中添加白名单条目(切勿使用 --no-verify)。白名单模式请参阅 references/tooling_notes.md。

此 Skill 不会做什么

  • 它不会替你轮换有效凭据。先轮换,再清理历史记录。
  • 它不会从 GitHub 自己的备份或复刻仓库中移除数据。它只会清理上游仓库历史记录。
  • 它不会绕过 git 钩子。如果钩子运行失败,请修复根本原因。
  • 它不会让泄露的密钥变得“安全”。一旦推送,就应假定数据已被看到。

参考资料

  • references/incident-lessons.md — 真实清理过程中出现的问题,以及此 Skill 如何防止这些错误。
  • references/tooling_notes.md — 如何在 git-filter-repo 和 BFG 之间进行选择、白名单模式以及常见错误。
  • references/ai_semantic_review_prompt.md — 第 4 层 AI 语义审查提示词,用于查找正则表达式无法捕获的私密上下文。

评论

支持 Markdown