feat: Add Gitleaks integration with pre-commit and pre-push hooks for automated secret detection, including configuration and related documentation.

This commit is contained in:
Haitao Pan 2026-01-30 09:55:16 +08:00
parent 81c689a0f1
commit e1d38f51ab
11 changed files with 487 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.env
models/
*.tsbuildinfo

38
config/gitleaks.toml Normal file
View File

@ -0,0 +1,38 @@
# Gitleaks configuration file
# For more information, see https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml
[allowlist]
description = "Global allowlist"
paths = [
'''vendor/''',
'''node_modules/''',
'''\.env\.example$''',
'''\.env\.test$''',
'''go\.sum$''',
'''package-lock\.json$''',
]
stopwords = [
"example",
"placeholder",
"test-password",
]
[[rules]]
id = "generic-api-key"
description = "Generic API Key"
regex = '''(?i)(api_key|apikey|secret|password|token)[-|_| ]*[=|\:][-|_| ]*['|"]([0-9a-zA-Z]{16,128})['|"]'''
description_id = "potential_secret"
entropy = 3.5
keywords = ["api_key", "apikey", "secret", "password", "token"]
[[rules]]
id = "github-pat"
description = "GitHub Personal Access Token"
regex = '''ghp_[0-9a-zA-Z]{36}'''
keywords = ["ghp_"]
[[rules]]
id = "google-oauth-client-secret"
description = "Google OAuth Client Secret"
regex = '''(?i)client_secret[-|_| ]*[=|\:][-|_| ]*['|"]([0-9a-zA-Z\-_]{24})['|"]'''
keywords = ["client_secret"]

45
scripts/hooks/commit-msg Executable file
View File

@ -0,0 +1,45 @@
#!/bin/bash
# commit-msg hook to enforce Conventional Commits
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# Regex for Conventional Commits (Simplified)
# Types: fix, feat, docs, chore, test, refactor, perf, style, ci, revert
# Scope: optional, inside ()
# Subject: required, no ending dot (optional enforcement)
PATTERN="^(fix|feat|docs|chore|test|refactor|perf|style|ci|revert)(\([a-z0-9\._-]+\))?: .+$"
if [[ ! "$COMMIT_MSG" =~ $PATTERN ]]; then
echo "❌ Error: Invalid commit message format."
echo "----------------------------------------------------------------"
echo "Your commit message must follow the Conventional Commits specification."
echo ""
echo "Format: <type>(<scope>): <subject>"
echo ""
echo "Allowed types:"
echo " fix - Bug fix"
echo " feat - New feature"
echo " docs - Documentation only"
echo " chore - Build process or auxiliary tool changes"
echo " test - Adding or correcting tests"
echo " refactor - Code change that neither fixes a bug nor adds a feature"
echo " perf - A code change that improves performance"
echo " style - Changes that do not affect the meaning of the code"
echo " ci - CI configuration files and scripts changes"
echo " revert - Reverts a previous commit"
echo ""
echo "Example: feat(auth): add login support"
echo "----------------------------------------------------------------"
echo "See skills/git.conventional-commits.v1.md for more details."
exit 1
fi
# Check for length (Subject max 50 chars recommended, but let's say 72 hard limit for header)
HEADER=$(head -n 1 "$COMMIT_MSG_FILE")
LEN=${#HEADER}
if [ "$LEN" -gt 72 ]; then
echo "⚠️ Warning: Commit message header is $LEN characters long (max 72 recommended)."
fi
exit 0

25
scripts/hooks/install-hooks.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# install-hooks.sh - Deploy Git hooks to the current repository
HOOKS_DIR=$(git rev-parse --git-path hooks)
SOURCE_DIR="scripts/hooks"
echo "Installing Git hooks to $HOOKS_DIR..."
install_hook() {
local hook_name=$1
if [ -f "$SOURCE_DIR/$hook_name" ]; then
cp "$SOURCE_DIR/$hook_name" "$HOOKS_DIR/$hook_name"
chmod +x "$HOOKS_DIR/$hook_name"
echo "✅ Installed $hook_name"
else
echo "❌ Hook $hook_name not found in $SOURCE_DIR"
fi
}
install_hook "pre-commit"
install_hook "pre-push"
install_hook "commit-msg"
echo "Done. Gitleaks integration is now active."

3
scripts/hooks/pre-commit Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
# Git pre-commit hook
./scripts/hooks/run-gitleaks.sh staged

3
scripts/hooks/pre-push Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
# Git pre-push hook
./scripts/hooks/run-gitleaks.sh full

48
scripts/hooks/run-gitleaks.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/bash
# run-gitleaks.sh - Wrapper for gitleaks detection modes
# Usage: ./run-gitleaks.sh [staged|full]
MODE=${1:-staged}
GITLEAKS_BIN=${GITLEAKS_BIN:-gitleaks}
GITLEAKS_CONFIG=${GITLEAKS_CONFIG:-config/gitleaks.toml}
GITLEAKS_REPORT_DIR=${GITLEAKS_REPORT_DIR:-.git/gitleaks}
REPORT_PATH="$GITLEAKS_REPORT_DIR/report.json"
# Check if gitleaks is installed
if ! command -v "$GITLEAKS_BIN" &> /dev/null; then
echo "Error: gitleaks is not installed or not in PATH."
echo "Install it from: https://github.com/gitleaks/gitleaks/releases"
exit 1
fi
# Create report directory
mkdir -p "$GITLEAKS_REPORT_DIR"
if [ "$MODE" == "staged" ]; then
echo "Running Gitleaks on staged changes..."
# Scan staged changes via pipe
# -U0 for minimal context, --pipe reads from stdin
git diff --cached -U0 | "$GITLEAKS_BIN" detect -v --no-git --pipe --redact --config "$GITLEAKS_CONFIG" --report-path "$REPORT_PATH"
EXIT_CODE=$?
else
echo "Running full Gitleaks scan on repository..."
# Full repository scan
"$GITLEAKS_BIN" detect -v --redact --config "$GITLEAKS_CONFIG" --report-path "$REPORT_PATH"
EXIT_CODE=$?
fi
if [ $EXIT_CODE -ne 0 ]; then
echo ""
echo "⚠️ Potential secrets detected!"
echo "Report generated at: $REPORT_PATH"
echo ""
echo "Please review the findings and refer to the security playbook:"
echo "skills/git.secret-incident-response.v1.md"
exit $EXIT_CODE
else
echo "✅ No secrets detected."
# Clean up empty report
[ -f "$REPORT_PATH" ] && [ ! -s "$REPORT_PATH" ] && rm "$REPORT_PATH"
exit 0
fi

View File

@ -0,0 +1,94 @@
#!/bin/bash
# secret-incident.sh - Helper script for handling gitleaks incidents
# Usage: ./scripts/security/secret-incident.sh
REPORT_FILE=".git/gitleaks/report.json"
CONFIG_FILE="config/gitleaks.toml"
echo "========================================"
echo "🛡️ Git Secret Incident Response Helper"
echo "========================================"
if [ ! -f "$REPORT_FILE" ]; then
echo "✅ No active report found at $REPORT_FILE."
echo "Running gitleaks to check for issues..."
./scripts/hooks/run-gitleaks.sh full
if [ $? -eq 0 ]; then
echo "No secrets detected. You are safe."
exit 0
fi
fi
echo ""
echo "⚠️ Secrets detected! parsing report..."
echo ""
# Check dependencies
if ! command -v jq &> /dev/null; then
echo "Error: 'jq' is not installed. Please install it to parse JSON reports."
# Fallback to simple cat
cat "$REPORT_FILE"
exit 1
fi
# Summary
COUNT=$(jq length "$REPORT_FILE")
echo "Found $COUNT potential secret(s)."
# List items
jq -r '.[] | "----------------------------------------\nType: \(.Description)\nFile: \(.File)\nLine: \(.StartLine)\nCommit: \(.Commit)\nSecret: \(.Secret | .[0:4])..."' "$REPORT_FILE"
echo "----------------------------------------"
echo ""
echo "Select an action:"
echo "1) 🩹 Fix: I will remove the secret and rotate it (Standard Procedure)"
echo "2) 🧹 History: I pushed it and need to clean git history (Dangerous)"
echo "3) ✅ Allow: This is a false positive (Add to whitelist)"
echo "4) ❌ Exit"
read -p "Enter choice [1-4]: " CHOICE
case "$CHOICE" in
1)
echo ""
echo ">>> ACTION: FIX & ROTATE"
echo "1. Revoke the exposed secret IMMEDIATELY."
echo "2. Remove the secret from the file."
echo "3. Run 'git restore --staged <file>' if it was just staged."
echo "4. Verify with './scripts/hooks/run-gitleaks.sh staged'"
;;
2)
echo ""
echo ">>> ACTION: CLEAN HISTORY"
echo "WARNING: This rewrites history. Notify your team!"
echo ""
echo "Recommended command (install git-filter-repo first):"
echo "git filter-repo --path <path/to/file> --invert-paths --force"
echo ""
echo "Then force push:"
echo "git push --force --all"
;;
3)
echo ""
echo ">>> ACTION: ALLOWLIST"
echo "Editing $CONFIG_FILE..."
echo "Add the file path or regex to the [[allowlist]] section."
echo ""
echo "Example path:"
echo "paths = ['''path/to/false/positive.file''']"
echo ""
echo "Opening config file..."
# Try to open with default editor
${EDITOR:-nano} "$CONFIG_FILE"
;;
4)
echo "Exiting."
exit 0
;;
*)
echo "Invalid choice."
;;
esac
echo ""
echo "For more details, read: skills/git.secret-incident-response.v1.md"

View File

@ -0,0 +1,73 @@
# skillgit commit check (gitleaks) v1
> 目标:在提交/推送前阻止 secrets 进入 git 历史。默认:本地 pre-commit 扫 stagedpre-push 全仓复检CI 再复检一次。
## 适用范围
- 所有代码仓库尤其Cloud Run / Vercel / infra / IaC
- 所有会接触 token、证书、DB 账号、API Key 的变更
## 全局规则
- 默认阻断提交:检测到疑似 secret => 退出码非 0 => commit/push 失败
- 输出必须可操作:指出报告路径、下一步动作
- 误报必须可控:只允许在 `gitleaks.toml` 做最小 allowlist路径/regex
- 误报放行必须由项目负责人审核,且永不把真实 secret 写进 allowlist只 allow “确定无害”的模式)
## 依赖
- gitleaks v8+(必须支持 `detect --pipe`
- bash / sh
- git
## 安装/启用(仓库内)
1) 放置脚本(建议路径):
- `scripts/hooks/run-gitleaks.sh`
- `scripts/hooks/pre-commit`
- `scripts/hooks/pre-push`
- `scripts/hooks/install-hooks.sh`
- `config/gitleaks.toml`
2) 一键安装 hooks
```bash
bash scripts/hooks/install-hooks.sh
```
## Hook 行为设计
### pre-commit
只扫描 staged patch避免扫全仓导致慢
命令(参考):
```bash
git diff --cached -U0 | gitleaks detect -v --no-git --pipe --redact --config config/gitleaks.toml
```
### pre-push
推送前全仓扫描(兜底)
命令(参考):
```bash
gitleaks detect -v --redact --config config/gitleaks.toml
```
## 输出与报告
- 报告目录:`.git/gitleaks/report.json`
- 发现泄露时输出:
- “Potential secrets detected”
- 报告路径
- 处理动作(见 skills/git.secret-incident-response.v1.md
## 可配置项(环境变量)
- GITLEAKS_BIN默认 gitleaks
- GITLEAKS_CONFIG默认 config/gitleaks.toml
- GITLEAKS_REPORT_DIR默认 .git/gitleaks
- GITLEAKS_MODEstaged|full
## CI 复检(必须)
任何人都能绕过本地 hookCI 必须复检:
```bash
gitleaks detect -v --redact --config config/gitleaks.toml
```
## 失败处理入口
统一转交到skills/git.secret-incident-response.v1.md
## 验收标准
✅ 提交包含假 key 时commit 被阻止并生成 report.json
✅ 误报可通过 allowlist 精准放行
✅ push 前全仓复检能挡住“漏网之鱼”
✅ CI 中同样会失败(防绕过)

View File

@ -0,0 +1,54 @@
# skillconventional commits v1
> 目标:统一 commit message 格式,提升可读性、自动化 changelog 生成、语义化版本管理。参考 Peter 的实践规范。
## 适用范围
- 所有代码仓库
- 所有团队成员的提交
## Commit Message 格式
```
<type>(<scope>): <subject>
<body>
<footer>
```
### Type必须
参考统计比例Peter 的实践):
- **fix**: 修复 bug31%,最常用,对应 PATCH 版本)
- **docs**: 仅文档变更14%
- **feat**: 新功能10%,对应 MINOR 版本)
- **chore**: 构建过程或辅助工具的变动9%
- **test**: 添加或修改测试6%
- **refactor**: 代码重构5%,既不修复 bug 也不添加功能)
- **perf**: 性能优化
- **style**: 代码格式调整(不影响代码含义的变更)
- **ci**: CI 配置文件和脚本的变更
- **revert**: 回滚之前的 commit
### Scope可选
影响范围例如api, ui, auth, db, docs, iac, config
### Subject必须
- 简短描述50 字符以内)
- 使用祈使句,现在时态
- 首字母小写
- 结尾不加句号
### Body可选
- 详细描述变更的动机和实现细节
### Footer可选
- BREAKING CHANGES`BREAKING CHANGE:` 开头
- Issue 引用:如 `Closes #123`
## 规则
- 必须基于 `conventional-commits` 规范
- 严禁模糊的描述(如 “update”, “fix”
- 大规模变更必须细化为多个原子提交
## 验收标准
✅ 提交信息格式合规
✅ CI/CD 流程能正确解析类型并触发对应流水线
✅ 自动生成的 Changelog 结构清晰

View File

@ -0,0 +1,103 @@
# skillgit secret incident response (auto-fix playbook) v1
> 目标:当 gitleaks 发现泄露时,提供“自动止血 + 可回滚 + 可审计”的标准处理流程。
> 原则:先撤销/轮换,再清理历史,最后加规则;不要反过来。
## 触发条件
- 本地 hook / CI 中 `gitleaks detect` 失败
- 或发现敏感信息已进入仓库(哪怕没有报警)
## 输入
- gitleaks 报告:`.git/gitleaks/report.json`(或 CI artifact
- 泄露类型API key / token / 私钥 / DB 密码 / 证书
- 泄露范围:仅未提交(staged) / 已提交 but 未推送 / 已推送到远端
## 全局规则(强制)
- 任何真实 secret 一律视为已泄露:必须 rotate/revoke
- 不允许“先 allowlist 让它过”来掩盖问题
- 清理历史是最后手段,但一旦推送过就必须执行
---
## 处理流程(按优先级执行)
### Step 0立即止血30 秒内)
1) 取消暂存/撤回改动(避免误提交):
```bash
git restore --staged .
```
2) 在本地删除/替换泄露内容(改为读取 Secret Manager / env / vault
3) 重新 staged 后再跑 gitleaks确保干净
```bash
git add -A
git diff --cached -U0 | gitleaks detect -v --no-git --pipe --redact --config config/gitleaks.toml
```
### Step 1撤销/轮换密钥(必须)
根据类型选择动作(示例):
- GitHub tokenrevoke + 重新生成
- GCP SA key禁用/删除 key改用 Workload Identity如可
- DB 密码:立即改密码 + 强制最小权限 + 限制来源 IP
- TLS 私钥:重新签发证书
产物:记录 “rotated_at / rotated_by / secret_id / scope”。
### Step 2判断是否进入 git 历史(分支决策)
情况 A仅 staged / 未 commit
- 不需要清理历史,只需修复内容 + rotate 即可
情况 B已 commit 但未 push
- 可以用 reset/squash 直接抹掉 commit
```bash
git reset --soft HEAD~1
# 删除泄露内容后重新 commit
```
仍需 rotate因为本地也可能被复制、日志、shell history 等)
情况 C已 push 到远端(高危)
- 必须清理历史:推荐 git filter-repo比 filter-branch 稳、快)
- 删除文件:
```bash
git filter-repo --path path/to/leaked.file --invert-paths
```
- 或按文本替换(需要准备 replacement rules
```bash
git filter-repo --replace-text replacements.txt
```
- 强制推送并通知协作者重拉:
```bash
git push --force --all
git push --force --tags
```
注意:即便清理了 git 历史,也必须 rotate因为泄露已经发生。
### Step 3误报处理允许但必须最小化
仅当你确认它不是 secret例如测试样例、公开字符串
1) 在 config/gitleaks.toml 里加 allowlist最小范围
2) 优先 path allowlist
3) 其次 regex allowlist要尽量具体
4) 重新运行 gitleaks 验证通过
### Step 4加固防再犯
- CI 中强制 gitleaks不可跳过
- 将 secrets 全部迁移到 Secret Manager或 Vault
- 禁止在 docs/example 中出现真实 key用 EXAMPLE_ 前缀)
## 自动化脚本建议(可选增强)
你可以在仓库提供一个统一入口脚本:
`scripts/security/secret-incident.sh`
功能:
- 自动打开 report.json
- 提示泄露文件/规则
- 输出对应处理命令模板reset/filter-repo/allowlist
- 生成一份 incident 记录markdown
## 验收标准
✅ 泄露发生时:提交/CI 必然失败
✅ 30 秒内可止血:撤回 staged 并定位报告
✅ 轮换动作有记录
✅ 已 push 泄露:历史清理可复现且协作指引明确
✅ 误报 allowlist 不扩大攻击面