[{"data":1,"prerenderedAt":544},["ShallowReactive",2],{"content:\u002F2026\u002F01\u002Fgit-filter-repo":3,"surround:\u002F2026\u002F01\u002Fgit-filter-repo":533},{"id":4,"title":5,"body":6,"categories":511,"date":513,"description":514,"draft":515,"extension":516,"image":517,"meta":518,"navigation":520,"path":521,"permalink":517,"published":517,"readingTime":522,"recommend":517,"references":517,"seo":527,"sitemap":528,"stem":529,"tags":530,"type":531,"updated":513,"__hash__":532},"content\u002Fposts\u002F2026\u002F01\u002Fgit-filter-repo.md","git-filter-repo 清理敏感信息",{"type":7,"value":8,"toc":489},"minimark",[9,27,30,34,37,50,56,59,63,70,81,85,90,115,119,137,152,155,179,183,187,217,223,232,236,257,260,271,274,282,285,289,296,330,337,340,343,364,367,370,374,385,388,394,397,401,404,415,421,424,427,455,462,465,468,471,474,479,485],[10,11,12,13,17,18,21,22,26],"p",{},"在开发过程中，不小心把 ",[14,15,16],"code",{"code":16},"config.yaml","（包含Mysql，Redis 等配置）提交进了 Git 仓库。\n虽然随后通过提交把文件删除并加入了 ",[14,19,20],{"code":20},".gitignore","，但回过头来看，这个处理方式是",[23,24,25],"strong",{},"不完整的","。",[10,28,29],{},"原因很简单：\nGit 会永久保留历史提交，只要有人 checkout 到旧 commit，配置内容依然是明文可见的。",[31,32,33],"h2",{"id":33},"问题本质",[10,35,36],{},"很多人第一次遇到这个问题，都会下意识做这些操作：",[38,39,40,44,47],"ul",{},[41,42,43],"li",{},"删除配置文件再提交",[41,45,46],{},"新增提交覆盖旧内容",[41,48,49],{},"revert 某个提交",[10,51,52,53],{},"这些方式的共同问题是：\n",[23,54,55],{},"它们只影响当前分支状态，不会影响 Git 历史中的对象。",[10,57,58],{},"只要配置文件曾经被提交过，就必须假设已经泄露。",[31,60,62],{"id":61},"为什么要用-git-filter-repo","为什么要用 git-filter-repo",[10,64,65,66,69],{},"目前官方推荐的是 ",[14,67,68],{"code":68},"git-filter-repo","，专门用于：",[38,71,72,75,78],{},[41,73,74],{},"清理敏感文件",[41,76,77],{},"重写提交历史",[41,79,80],{},"仓库级别维护操作",[31,82,84],{"id":83},"安装-git-filter-repo","安装 git-filter-repo",[86,87,89],"h3",{"id":88},"macos","macOS",[91,92,97],"pre",{"className":93,"code":94,"language":95,"meta":96,"style":96},"language-bash shiki shiki-themes github-light github-dark","brew install git-filter-repo\n","bash","",[14,98,99],{"__ignoreMap":96},[100,101,104,108,112],"span",{"class":102,"data-line":103,"line":103},"line",1,[100,105,107],{"class":106},"sScJk","brew",[100,109,111],{"class":110},"sZZnC"," install",[100,113,114],{"class":110}," git-filter-repo\n",[86,116,118],{"id":117},"linux","Linux",[91,120,122],{"className":93,"code":121,"language":95,"meta":96,"style":96},"pip3 install --user git-filter-repo\n",[14,123,124],{"__ignoreMap":96},[100,125,126,129,131,135],{"class":102,"data-line":103,"line":103},[100,127,128],{"class":106},"pip3",[100,130,111],{"class":110},[100,132,134],{"class":133},"sj4cs"," --user",[100,136,114],{"class":110},[91,138,140],{"className":93,"code":139,"language":95,"meta":96,"style":96},"~\u002F.local\u002Fbin\u002Fgit-filter-repo --help\n",[14,141,142],{"__ignoreMap":96},[100,143,144,148],{"class":102,"data-line":103,"line":103},[100,145,147],{"class":146},"szBVR","~",[100,149,151],{"class":150},"sVt8B","\u002F.local\u002Fbin\u002Fgit-filter-repo --help\n",[10,153,154],{},"如果提示找不到命令，把路径加入 PATH：",[91,156,158],{"className":93,"code":157,"language":95,"meta":96,"style":96},"export PATH=$PATH:~\u002F.local\u002Fbin\n",[14,159,160],{"__ignoreMap":96},[100,161,162,165,168,171,174,176],{"class":102,"data-line":103,"line":103},[100,163,164],{"class":146},"export",[100,166,167],{"class":150}," PATH",[100,169,170],{"class":146},"=",[100,172,173],{"class":150},"$PATH:",[100,175,147],{"class":146},[100,177,178],{"class":150},"\u002F.local\u002Fbin\n",[31,180,182],{"id":181},"如何使用-git-filter-repo-清理敏感信息","如何使用 git-filter-repo 清理敏感信息",[86,184,186],{"id":185},"_1-使用-mirror-方式克隆仓库","1. 使用 mirror 方式克隆仓库",[91,188,190],{"className":93,"code":189,"language":95,"meta":96,"style":96},"git clone --mirror https:\u002F\u002Fgithub.com\u002Fcodepzj\u002Fdemo-repo.git demo-repo-mirror\ncd demo-repo-mirror\n",[14,191,192,209],{"__ignoreMap":96},[100,193,194,197,200,203,206],{"class":102,"data-line":103,"line":103},[100,195,196],{"class":106},"git",[100,198,199],{"class":110}," clone",[100,201,202],{"class":133}," --mirror",[100,204,205],{"class":110}," https:\u002F\u002Fgithub.com\u002Fcodepzj\u002Fdemo-repo.git",[100,207,208],{"class":110}," demo-repo-mirror\n",[100,210,212,215],{"class":102,"data-line":211,"line":211},2,[100,213,214],{"class":133},"cd",[100,216,208],{"class":110},[10,218,219,222],{},[14,220,221],{"code":221},"--mirror"," 的目的不是开发，而是为了：",[224,225,226,229],"ol",{},[41,227,228],{},"包含所有分支、tag、引用",[41,230,231],{},"确保不会遗漏任何历史",[86,233,235],{"id":234},"_2-从所有历史提交中移除配置文件","2. 从所有历史提交中移除配置文件",[91,237,239],{"className":93,"code":238,"language":95,"meta":96,"style":96},"git filter-repo --path config.yaml --invert-paths\n",[14,240,241],{"__ignoreMap":96},[100,242,243,245,248,251,254],{"class":102,"data-line":103,"line":103},[100,244,196],{"class":106},[100,246,247],{"class":110}," filter-repo",[100,249,250],{"class":133}," --path",[100,252,253],{"class":110}," config.yaml",[100,255,256],{"class":133}," --invert-paths\n",[10,258,259],{},"执行完成后，git-filter-repo 会：",[38,261,262,265,268],{},[41,263,264],{},"重写所有提交",[41,266,267],{},"删除旧的 blob 对象",[41,269,270],{},"重新打包仓库对象",[10,272,273],{},"如果看到类似：",[91,275,280],{"className":276,"code":278,"language":279,"meta":96},[277],"language-text","Parsed 15 commits\nNew history written\nRepacking your repo\n","text",[14,281,278],{"__ignoreMap":96},[10,283,284],{},"说明处理是成功的。",[86,286,288],{"id":287},"_3-重新添加远端并覆盖推送","3. 重新添加远端并覆盖推送",[10,290,291,292,295],{},"git-filter-repo 会主动移除 ",[14,293,294],{"code":294},"origin","，这是为了防止误推送。",[91,297,299],{"className":93,"code":298,"language":95,"meta":96,"style":96},"git remote add origin https:\u002F\u002Fgithub.com\u002Fcodepzj\u002Fdemo-repo.git\ngit push --force --mirror\n",[14,300,301,317],{"__ignoreMap":96},[100,302,303,305,308,311,314],{"class":102,"data-line":103,"line":103},[100,304,196],{"class":106},[100,306,307],{"class":110}," remote",[100,309,310],{"class":110}," add",[100,312,313],{"class":110}," origin",[100,315,316],{"class":110}," https:\u002F\u002Fgithub.com\u002Fcodepzj\u002Fdemo-repo.git\n",[100,318,319,321,324,327],{"class":102,"data-line":211,"line":211},[100,320,196],{"class":106},[100,322,323],{"class":110}," push",[100,325,326],{"class":133}," --force",[100,328,329],{"class":133}," --mirror\n",[10,331,332,333,336],{},"这一步会直接用",[23,334,335],{},"新的干净历史","替换远端仓库。",[31,338,339],{"id":339},"如何确认是否真的清理干净",[10,341,342],{},"在任意新 clone 的仓库中执行：",[91,344,346],{"className":93,"code":345,"language":95,"meta":96,"style":96},"git log --all -- config.yaml\n",[14,347,348],{"__ignoreMap":96},[100,349,350,352,355,358,361],{"class":102,"data-line":103,"line":103},[100,351,196],{"class":106},[100,353,354],{"class":110}," log",[100,356,357],{"class":133}," --all",[100,359,360],{"class":133}," --",[100,362,363],{"class":110}," config.yaml\n",[10,365,366],{},"如果没有任何输出，说明该文件已经不存在于历史中。",[31,368,369],{"id":369},"必须做的后续处理",[86,371,373],{"id":372},"_1-永久忽略该配置文件","1. 永久忽略该配置文件",[91,375,379],{"className":376,"code":377,"language":378,"meta":96,"style":96},"language-gitignore shiki shiki-themes github-light github-dark","config.yaml\n","gitignore",[14,380,381],{"__ignoreMap":96},[100,382,383],{"class":102,"data-line":103,"line":103},[100,384,377],{},[10,386,387],{},"同时提供示例文件：",[91,389,392],{"className":390,"code":391,"language":279,"meta":96},[277],"config.example.yaml\n",[14,393,391],{"__ignoreMap":96},[10,395,396],{},"只放结构，不放真实值。",[86,398,400],{"id":399},"_2-重置所有相关密钥","2. 重置所有相关密钥",[10,402,403],{},"无论泄露时间长短，都应视为已暴露：",[38,405,406,409,412],{},[41,407,408],{},"数据库账号密码",[41,410,411],{},"JWT Secret",[41,413,414],{},"Firebase \u002F 云服务凭证",[10,416,417,418,26],{},"历史清理只能解决 Git 层面的问题，",[23,419,420],{},"无法撤回已经泄露的密钥",[31,422,423],{"id":423},"多人协作需要注意的点",[10,425,426],{},"历史被重写后，所有协作者必须同步仓库状态：",[91,428,430],{"className":93,"code":429,"language":95,"meta":96,"style":96},"git fetch --all\ngit reset --hard origin\u002Fmain\n",[14,431,432,442],{"__ignoreMap":96},[100,433,434,436,439],{"class":102,"data-line":103,"line":103},[100,435,196],{"class":106},[100,437,438],{"class":110}," fetch",[100,440,441],{"class":133}," --all\n",[100,443,444,446,449,452],{"class":102,"data-line":211,"line":211},[100,445,196],{"class":106},[100,447,448],{"class":110}," reset",[100,450,451],{"class":133}," --hard",[100,453,454],{"class":110}," origin\u002Fmain\n",[10,456,457,458,461],{},"如果有人直接 ",[14,459,460],{"code":460},"git pull"," 并再次推送，旧历史可能会被带回远端。",[31,463,464],{"id":464},"总结",[10,466,467],{},"这次问题的根源不是 Git 操作失误，而是：",[10,469,470],{},"对 Git 历史不可变性的低估。",[10,472,473],{},"只要敏感信息进入过仓库，正确的处理方式只有一种：",[10,475,476],{},[23,477,478],{},"重写历史，而不是覆盖提交。",[10,480,481,484],{},[14,482,483],{"code":483},"git filter-repo + mirror + force push","是目前最稳妥、最可控的一套方案。",[486,487,488],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":96,"searchDepth":490,"depth":490,"links":491},4,[492,493,494,499,504,505,509,510],{"id":33,"depth":211,"text":33},{"id":61,"depth":211,"text":62},{"id":83,"depth":211,"text":84,"children":495},[496,498],{"id":88,"depth":497,"text":89},3,{"id":117,"depth":497,"text":118},{"id":181,"depth":211,"text":182,"children":500},[501,502,503],{"id":185,"depth":497,"text":186},{"id":234,"depth":497,"text":235},{"id":287,"depth":497,"text":288},{"id":339,"depth":211,"text":339},{"id":369,"depth":211,"text":369,"children":506},[507,508],{"id":372,"depth":497,"text":373},{"id":399,"depth":497,"text":400},{"id":423,"depth":211,"text":423},{"id":464,"depth":211,"text":464},[512],"工程实践","2026-01-21","git-filter-repo清理敏感信息，防止git提交了敏感信息后导致的密钥泄漏",false,"md",null,{"slots":519},{},true,"\u002F2026\u002F01\u002Fgit-filter-repo",{"text":523,"minutes":524,"time":525,"words":526},"4 min read",3.63,217800,726,{"title":5,"description":514},{"loc":521},"posts\u002F2026\u002F01\u002Fgit-filter-repo",[196],"tech","H3rUe9rSA5Vv2dzHOUsGTzu8S_B3FV15Wg4sqc96LaY",[534,539],{"title":535,"path":536,"stem":537,"date":538,"type":531,"children":-1},"gRPC Metadata 的传递与使用","\u002F2026\u002F01\u002Fgrpc-metadata","posts\u002F2026\u002F01\u002Fgrpc-metadata","2026-01-05",{"title":540,"path":541,"stem":542,"date":543,"type":531,"children":-1},"Github CI自动发版","\u002F2026\u002F02\u002Fgit-semantic-release","posts\u002F2026\u002F02\u002Fgit-semantic-release","2026-02-15",1786555054472]