014、Hook 实战一:自动代码格式化、pre-commit 检查与敏感信息拦截脚本

014、Hook 实战一:自动代码格式化、pre-commit 检查与敏感信息拦截脚本 014、Hook 实战一自动代码格式化、pre-commit 检查与敏感信息拦截脚本从一次“社死”现场说起上周五下午我正盯着Claude Code生成的PR准备合并隔壁组的小王突然在群里我“兄弟你们仓库的.git/config里怎么有AWS密钥”我心头一紧——那是上周调试时临时写死的测试AK本打算提交前删掉结果Claude Code自动commit时顺手带上了。更尴尬的是这个仓库是公开的。那次之后我花了三天时间把Hook体系彻底重构了一遍。今天这篇笔记就是那次事故的产物——一套能自动格式化、做pre-commit检查、还能拦截敏感信息的Hook脚本。别指望它100%防住所有泄露但至少能让你少几次“社死”时刻。Hook 到底是什么别被概念吓到Git Hook说白了就是“事件触发器”。你执行git commit时Git会去.git/hooks/目录下找对应的脚本文件。有pre-commit、commit-msg、pre-push等等。我们最常用的是pre-commit——在生成commit对象之前执行如果脚本返回非0commit直接中断。这里有个坑.git/hooks/目录不会被提交到仓库。所以团队协作时你得把Hook脚本放在项目根目录的.githooks/里然后让每个人执行gitconfig core.hooksPath .githooks别问我为什么知道这个——第一次写Hook时我把脚本放.git/hooks/里同事拉代码后完全没生效排查了半小时。自动代码格式化别让空格成为吵架理由先写一个最基础的pre-commit脚本用#!/bin/bash开头别用sh——某些系统上sh指向dash语法兼容性会让你崩溃。#!/bin/bash# 这里踩过坑一定要set -e否则脚本中途失败不会中断commitset-e# 只处理暂存区的文件别格式化整个项目STAGED_FILES$(gitdiff--cached--name-only --diff-filterACM|grep-E\.(py|js|ts|jsx|tsx|go)$)if[-z$STAGED_FILES];thenexit0fiecho 正在格式化暂存文件...# Python用black别用autopep8black是“不妥协的格式化器”echo$STAGED_FILES|grep\.py$|xargs-rblack--quiet2/dev/null||true# JS/TS用prettier注意要装--check模式echo$STAGED_FILES|grep-E\.(js|ts|jsx|tsx)$|xargs-rnpx prettier--write2/dev/null||true# 重新将格式化后的文件加入暂存区echo$STAGED_FILES|xargsgitadd注意那个--diff-filterACM——只处理新增、修改、复制操作的文件删除的文件别碰。还有xargs -r如果输入为空就不执行避免报错。pre-commit 检查lint、类型、测试一个都不能少格式化完就该检查代码质量了。别写那种“全部检查”的脚本——大型项目全量lint跑一次要几分钟没人受得了。# 只检查本次修改的行而不是整个文件# 这里用了一个小技巧git diff --cached 拿到变更内容再传给lint工具echo 运行增量lint...# Python用flake8配合--diff参数gitdiff--cached--diff-filterACM --*.py|flake8--diff--max-line-length12021|head-20# 别这样写直接exit 1给开发者留个缓冲只警告不阻断# 但敏感信息检查必须阻断# TypeScript类型检查只检查有改动的文件CHANGED_TS$(gitdiff--cached--name-only --diff-filterACM|grep-E\.(ts|tsx)$)if[-n$CHANGED_TS];thenecho$CHANGED_TS|xargsnpx tsc--noEmit--pretty21|head-30fi这里有个经验lint检查建议只输出警告不要直接exit 1。因为有些格式化问题可以在后续步骤自动修复直接阻断会让开发者烦躁。但敏感信息检查必须硬阻断——那是底线。敏感信息拦截正则匹配 熵检测这是最核心的部分。我踩过的坑包括AWS密钥、GitHub Token、数据库连接串、甚至内网IP。正则匹配能防住大部分但有些随机生成的密钥需要熵检测。# 敏感信息检查函数check_secrets(){localfile$1localcontentcontent$(gitshow:$file2/dev/null||cat$file)# 正则匹配常见密钥模式# AWS Access Key: AKIA开头20位大写字母数字ifecho$content|grep-qPAKIA[0-9A-Z]{16};thenecho❌ 检测到AWS Access Key:$filereturn1fi# GitHub Token: ghp_开头ifecho$content|grep-qPghp_[a-zA-Z0-9]{36};thenecho❌ 检测到GitHub Token:$filereturn1fi# 数据库连接串包含密码的JDBC URLifecho$content|grep-qPjdbc:\w://[^:]:[^];thenecho❌ 检测到数据库连接串含密码:$filereturn1fi# 熵检测对疑似密钥的字符串计算熵值# 这里用了一个简单方法检查连续高熵字符localhigh_entropyhigh_entropy$(echo$content|grep-oP[a-zA-Z0-9/]{40,}|head-5)if[-n$high_entropy];then# 计算每个字符串的熵超过4.5就报警whileIFSread-rtoken;dolocalentropyentropy$(echo$token|python3-c import sys, math s sys.stdin.read().strip() if not s: sys.exit(0) freq {} for c in s: freq[c] freq.get(c, 0) 1 ent -sum((c/len(s))* math.log2(c/len(s)) for c in freq.values()) print(ent) )if[$(echo$entropy 4.5|bc)-eq1];thenecho⚠️ 高熵字符串检测可能是密钥:$fileecho 字符串:${token:0:20}... 熵值:$entropy# 这里只警告不阻断因为可能是正常的base64编码fidone$high_entropyfireturn0}# 遍历所有暂存文件STAGED_ALL$(gitdiff--cached--name-only --diff-filterACM)HAS_SECRET0whileIFSread-rfile;docheck_secrets$file||HAS_SECRET1done$STAGED_ALLif[$HAS_SECRET-eq1];thenecho❌ 敏感信息检查未通过commit已阻止echo 请移除敏感信息后重新git addexit1fi熵检测那段代码是我从一次事故中学到的。当时有个同事把Base64编码的配置文件提交了正则没匹配到任何已知模式但熵检测抓到了——那串字符的熵值高达5.2明显不是普通文本。整合脚本让一切自动化把上面三段拼起来加上一些辅助功能#!/bin/bashset-e# 颜色输出RED\033[0;31mGREEN\033[0;32mYELLOW\033[1;33mNC\033[0mecho-e${YELLOW} 开始pre-commit检查...${NC}# 1. 自动格式化# ...上面格式化代码# 2. 代码检查# ...上面lint代码# 3. 敏感信息拦截# ...上面敏感信息检查代码# 4. 额外检查是否有大文件超过5MBMAX_SIZE$((5*1024*1024))whileIFSread-rfile;dosize$(gitshow:$file2/dev/null|wc-c)if[$size-gt$MAX_SIZE];thenecho-e${RED}❌ 文件过大:$file(${size}bytes)${NC}echo 建议使用git-lfs管理大文件exit1fidone$STAGED_ALLecho-e${GREEN}✅ 所有检查通过${NC}部署到团队别让每个人手动配置写个setup-hooks.sh脚本一键配置#!/bin/bash# 设置git hooks路径gitconfig core.hooksPath .githooks# 安装依赖工具pipinstallblack flake82/dev/null||truenpminstall-gprettier2/dev/null||true# 给hook脚本执行权限chmodx .githooks/pre-commitecho✅ Hook配置完成把这个脚本放在项目根目录README里写一句“新成员请执行bash setup-hooks.sh”。别指望每个人都会主动执行——在CI/CD里加一个检查步骤如果.git/hooks不是指向.githooks就报错。个人经验别过度设计写Hook脚本最容易犯的错误是“想一次解决所有问题”。我见过有人把代码覆盖率检查、性能测试、甚至安全扫描都塞进pre-commit里结果一次commit要跑5分钟团队怨声载道。我的建议是pre-commit只做三件事——格式化、lint、敏感信息检查。其他检查放到CI/CD里。Hook是“守门员”不是“裁判员”。守门员只负责拦截明显违规的球裁判员才负责吹哨。另外敏感信息检查一定要有“白名单”机制。有些测试密钥是故意写死的比如AKIAIOSFODNN7EXAMPLE——这是AWS官方文档里的示例密钥。在脚本里加个白名单# 跳过已知的测试密钥WHITELIST(AKIAIOSFODNN7EXAMPLEwJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY)forwin${WHITELIST[]};doifecho$content|grep-q$w;thenreturn0fidone最后记得把Hook脚本纳入版本管理。.githooks/目录要提交到仓库这样所有人都能共享。别像我第一次那样把脚本写在.git/hooks/里换台电脑就丢了。后记那次密钥泄露事件后我不仅写了这套Hook还在CI里加了git secrets扫描。但最有效的还是pre-commit拦截——因为问题在源头就被解决了。如果你现在还在手动格式化代码、手动检查密钥赶紧写个Hook吧。半小时的投入能省下无数次“社死”的尴尬。