本文目录导读:

Git 钩子脚本(通常是 pre-commit 或 commit-msg 钩子)是执行审计策略检查的理想位置,因为它可以在代码进入仓库之前就阻止违规提交。
以下是如何编写 Git 钩子脚本来检查常见审计策略的详细指南和示例。
核心思路
-
选择合适的钩子:
pre-commit: 检查将要提交的(如代码规范、硬编码密码、IP 地址、文件大小等),这是最常见的审计钩子。commit-msg: 检查提交信息(如是否符合“事务ID JIRA-123”的格式、字数限制、禁止的关键词等)。pre-receive/update: 服务端钩子,在中央仓库拒绝不合规的推送,这是最安全的防线。
-
获取被更改的文件列表: 这是审计的基础,需要区分暂存区(Staging Area)和工作区(Working Directory)的文件。
- 暂存区(当前提交):
git diff --cached --name-only --diff-filter=ACM - 所有文件:
git diff --cached --name-status
- 暂存区(当前提交):
-
执行检查逻辑: 遍历文件列表,对特定类型(如
.py,.env,.txt)使用grep、awk、sed等工具或调用外部脚本(Python, Node.js)进行内容检测。 -
返回状态码:
- 0: 通过检查,允许提交。
- 非0: 拒绝提交,并将错误信息打印给用户。
实践案例:pre-commit 钩子审计策略检查
将此脚本保存为项目根目录下 .git/hooks/pre-commit,并赋予执行权限(chmod +x .git/hooks/pre-commit)。
#!/bin/bash
# 防止未初始化变量
set -euo pipefail
# 颜色定义(可选)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "${YELLOW}开始审计策略检查...${NC}"
# --- 策略 1:检查提交信息是否包含特定的 JIRA 或 Ticket 号(使用 commit-msg 钩子更佳) ---
# 这里演示如何在 pre-commit 中检查(虽然更推荐在 commit-msg 中做)
COMMIT_MSG=$(cat "$1" 2>/dev/null || echo "no msg")
# 简单演示,实际应放在 .git/hooks/commit-msg 中
# if [[ ! "$COMMIT_MSG" =~ \[JIRA-[0-9]+\] ]]; then
# echo -e "${RED}错误:提交信息必须包含 [JIRA-XXX] 格式的 Ticket 号${NC}"
# exit 1
# fi
# --- 策略 2:检查是否提交了敏感信息(如密码、密钥、IP 地址、信用卡号) ---
# 获取本次提交会被包含的暂存区文件列表(排除已删除的文件)
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if [[ -z "$STAGED_FILES" ]]; then
echo "${GREEN}没有要提交的文件。${NC}"
exit 0
fi
echo "正在扫描 ${STAGED_FILES} 文件..."
# 定义需要扫描的模式(正则表达式)
SENSITIVE_PATTERNS=(
"password\s*=\s*['\"][^'\"]+['\"]" # 匹配 password = 'xxx'
"api_key\s*=\s*['\"][^'\"]+['\"]" # 匹配 api_key = 'xxx'
"secret\s*=\s*['\"][^'\"]+['\"]" # 匹配 secret = 'xxx'
"token\s*=\s*['\"][^'\"]+['\"]" # 匹配 token = 'xxx'
"AKIA[0-9A-Z]{16}" # AWS Access Key ID
"-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY-----" # 私钥
"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" # 内网 IP(若策略允许则需排除,此处仅示例)
)
# 排除目录(如 node_modules, .git 等,通常钩子只处理暂存文件,所以只需按需过滤)
# 如果暂存文件列表中包含 generated/ 或 dist/ 文件,可以在这里跳过
EXCLUDE_DIRS="(node_modules|dist|build|\.git|vendor)"
VIOLATION_FOUND=false
for FILE in $STAGED_FILES; do
# 检查文件是否存在于暂存区(未被删除)
if ! git ls-files --error-unmatch "$FILE" >/dev/null 2>&1; then
continue
fi
# 检查文件是否在排除目录中(如果文件名包含排除目录则跳过)
if echo "$FILE" | grep -qE "$EXCLUDE_DIRS"; then
continue
fi
# 只检查文本文件(避免二进制文件误报)
if git show :"$FILE" | file - | grep -q "text"; then
# 对每个敏感模式进行检查
for PATTERN in "${SENSITIVE_PATTERNS[@]}"; do
# 从暂存区读取文件内容进行匹配
if git show :"$FILE" | grep -qE "$PATTERN"; then
echo -e "${RED}审计失败:文件 '$FILE' 包含潜在敏感信息!${NC}"
echo -e "${RED}匹配模式: $PATTERN${NC}"
# 显示所在行(只显示前3行以防泄露过多信息)
git show :"$FILE" | grep -nE "$PATTERN" | head -3
echo ""
VIOLATION_FOUND=true
fi
done
fi
done
if [ "$VIOLATION_FOUND" = true ]; then
echo -e "${RED}审计策略检查未通过,提交已被阻止。${NC}"
echo -e "${YELLOW}请移除敏感信息或使用 .gitignore 忽略相关文件。${NC}"
exit 1
fi
# --- 策略 3:检查文件大小限制(防止大文件被提交) ---
MAX_SIZE_MB=5
for FILE in $STAGED_FILES; do
if ! git ls-files --error-unmatch "$FILE" >/dev/null 2>&1; then
continue
fi
# 获取暂存区文件的大小(字节)
SIZE=$(git cat-file -s ":$(git hash-object -w "$FILE" 2>/dev/null)" 2>/dev/null || git cat-file -s ":$FILE" 2>/dev/null || echo 0)
# 更准确的方法:使用 git ls-files 配合 hash
# SIZE=$(git ls-files --stage "$FILE" | awk '{print $4}' | xargs -I {} git cat-file -s {} 2>/dev/null || echo 0)
if [ "$SIZE" -gt $((MAX_SIZE_MB * 1024 * 1024)) ]; then
echo -e "${RED}审计失败:文件 '$FILE' 大小为 $SIZE 字节,超过限制 ${MAX_SIZE_MB}MB。${NC}"
VIOLATION_FOUND=true
fi
done
# --- 策略 4:检查文件权限(如果策略要求特定权限) ---
# 检查 .sh 文件是否有执行权限
for FILE in $STAGED_FILES; do
if [[ "$FILE" == *.sh ]]; then
# 检查暂存区文件的权限(以 100755 或 100644 开头)
MODE=$(git ls-files --stage "$FILE" | awk '{print $1}')
if [[ "$MODE" == 100644 ]] || [[ "$MODE" == "100755" ]]; then
# 如果策略要求 .sh 文件必须有执行权限
echo -e "${YELLOW}警告:脚本文件 '$FILE' 没有执行权限,建议使用 'git update-index --chmod=+x $FILE' 添加。${NC}"
fi
fi
done
# --- 策略 5:检查文件编码(统一要求 UTF-8) ---
for FILE in $STAGED_FILES; do
if git show :"$FILE" | file - | grep -q "text"; then
ENCODING=$(git show :"$FILE" | file - | grep -oP 'charset=\K\S+')
if [[ -n "$ENCODING" ]] && [[ "$ENCODING" != "utf-8" ]] && [[ "$ENCODING" != "us-ascii" ]]; then
echo -e "${RED}审计失败:文件 '$FILE' 编码为 $ENCODING,要求 UTF-8 编码。${NC}"
VIOLATION_FOUND=true
fi
fi
done
# --- 最终结论 ---
if [ "$VIOLATION_FOUND" = true ]; then
echo -e "${RED}审计策略检查未通过,提交已被阻止。${NC}"
exit 1
else
echo -e "${GREEN}所有审计策略检查通过。${NC}"
exit 0
fi
如何安装此钩子
- 保存为
<你的项目>/.git/hooks/pre-commit。 - 赋予执行权限:
chmod +x .git/hooks/pre-commit。 - 💡 推荐使用 Husky 或 pre-commit 框架:
- Husky (For Node.js): 将钩子脚本提交到仓库中,并自动启用。
npx husky add .husky/pre-commit "npm test" - pre-commit (Python): 更强大的框架,支持多语言钩子,配置文件
.pre-commit-config.yaml。pip install pre-commit
- Husky (For Node.js): 将钩子脚本提交到仓库中,并自动启用。
进阶:使用外部审计脚本
对于复杂的策略(如检查代码规范、运行安全扫描工具),最好在钩子中调用外部脚本,而不是全部写在 Bash 里。
在 pre-commit 中调用 Python 审计脚本:
#!/bin/bash
# .git/hooks/pre-commit
echo "正在运行 Python 审计检查..."
# 执行 audit.py 脚本,传入暂存区文件列表
python3 scripts/audit.py $(git diff --cached --name-only --diff-filter=ACM)
# 检查 Python 脚本的返回值
if [ $? -ne 0 ]; then
echo "审计脚本返回错误,阻止提交。"
exit 1
fi
scripts/audit.py 可以执行诸如检测 SQL 注入、硬编码的 API 密钥、自定义正则匹配等复杂逻辑,并直接输出错误消息。
- 核心钩子:
pre-commit用于内容审计,commit-msg用于信息格式审计,pre-receive用于服务器端强制审计。 - 关键命令:
git diff --cached --name-only --diff-filter=ACM获取暂存区改动文件。 - 核心逻辑: 逐文件使用
git show :<file>获取暂存区文件内容,然后用grep等工具匹配策略模式。 - 返回状态:
exit 1拒绝提交,exit 0允许提交。 - 避免问题: 确保钩子脚本是幂等的、高效的(只扫描被改动的文件),并且能处理二进制文件和特殊情况(如文件删除)。务必先测试钩子。