343 lines
11 KiB
Bash
343 lines
11 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# =============================================================================
|
||
|
|
# 安全审计脚本
|
||
|
|
# =============================================================================
|
||
|
|
# 用途: 跑 5 大安全工具,生成审计报告
|
||
|
|
# 1. bandit - Python 代码静态分析
|
||
|
|
# 2. safety - Python 依赖漏洞
|
||
|
|
# 3. pip-audit - Python 依赖漏洞(更准)
|
||
|
|
# 4. npm audit - JS 依赖漏洞
|
||
|
|
# 5. gitleaks - 仓库 secret 扫描
|
||
|
|
#
|
||
|
|
# 用法:
|
||
|
|
# bash scripts/security-audit.sh # 跑全部
|
||
|
|
# bash scripts/security-audit.sh --python # 只跑 Python 套件
|
||
|
|
# bash scripts/security-audit.sh --js # 只跑 JS 套件
|
||
|
|
# bash scripts/security-audit.sh --secrets # 只跑 secret 扫描
|
||
|
|
# bash scripts/security-audit.sh --output FILE # 自定义报告路径
|
||
|
|
#
|
||
|
|
# 退出码:
|
||
|
|
# 0 = 全过 / 仅 INFO
|
||
|
|
# 1 = 有 LOW
|
||
|
|
# 2 = 有 MEDIUM
|
||
|
|
# 3 = 有 HIGH/CRITICAL
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
# 颜色
|
||
|
|
RED='\033[0;31m'
|
||
|
|
GREEN='\033[0;32m'
|
||
|
|
YELLOW='\033[1;33m'
|
||
|
|
BLUE='\033[0;34m'
|
||
|
|
NC='\033[0m'
|
||
|
|
|
||
|
|
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||
|
|
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||
|
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||
|
|
|
||
|
|
# 路径
|
||
|
|
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
|
|
cd "$PROJECT_ROOT"
|
||
|
|
REPORT="docs/审计报告/security_audit_$(date +%Y%m%d).md"
|
||
|
|
LOG_DIR="/tmp/security-audit-$(date +%Y%m%d-%H%M%S)"
|
||
|
|
mkdir -p "$LOG_DIR" "$(dirname "$REPORT")"
|
||
|
|
|
||
|
|
# 参数
|
||
|
|
RUN_PYTHON=true
|
||
|
|
RUN_JS=true
|
||
|
|
RUN_SECRETS=true
|
||
|
|
for arg in "$@"; do
|
||
|
|
case $arg in
|
||
|
|
--python) RUN_PYTHON=true; RUN_JS=false; RUN_SECRETS=false ;;
|
||
|
|
--js) RUN_PYTHON=false; RUN_JS=true; RUN_SECRETS=false ;;
|
||
|
|
--secrets) RUN_PYTHON=false; RUN_JS=false; RUN_SECRETS=true ;;
|
||
|
|
--output) REPORT="$2" ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
# 计数器
|
||
|
|
PASS=0
|
||
|
|
WARN=0
|
||
|
|
FAIL=0
|
||
|
|
CRITICAL=0
|
||
|
|
|
||
|
|
# 报告头
|
||
|
|
cat > "$REPORT" <<EOF
|
||
|
|
# 安全审计报告
|
||
|
|
|
||
|
|
**审计日期**: $(date +%Y-%m-%d)
|
||
|
|
**审计人**: Claude(自动化跑批)
|
||
|
|
**工具**: bandit / safety / pip-audit / npm audit / gitleaks
|
||
|
|
**关联**: [[风险跟踪表]]
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 跑批概览
|
||
|
|
|
||
|
|
| 工具 | 范围 | 结果 |
|
||
|
|
|---|---|---|
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# 1. bandit (Python 静态分析)
|
||
|
|
# =============================================================================
|
||
|
|
if [ "$RUN_PYTHON" = true ]; then
|
||
|
|
info "── 1/5 bandit: Python 静态分析"
|
||
|
|
|
||
|
|
if ! command -v bandit &> /dev/null; then
|
||
|
|
warn "bandit 未安装,跑: pip install bandit"
|
||
|
|
echo "| bandit | Python 静态 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
if bandit -r backend/ -f json -o "$LOG_DIR/bandit.json" 2> "$LOG_DIR/bandit.err" ; then
|
||
|
|
ok "bandit: 无问题"
|
||
|
|
PASS=$((PASS+1))
|
||
|
|
echo "| bandit | Python 静态 | ✅ 无问题 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
# 解析 bandit JSON 报告
|
||
|
|
if command -v jq &> /dev/null; then
|
||
|
|
HIGH=$(jq '[.results[] | select(.issue_severity=="HIGH")] | length' "$LOG_DIR/bandit.json" 2>/dev/null || echo 0)
|
||
|
|
MED=$(jq '[.results[] | select(.issue_severity=="MEDIUM")] | length' "$LOG_DIR/bandit.json" 2>/dev/null || echo 0)
|
||
|
|
LOW=$(jq '[.results[] | select(.issue_severity=="LOW")] | length' "$LOG_DIR/bandit.json" 2>/dev/null || echo 0)
|
||
|
|
else
|
||
|
|
HIGH=0; MED=0; LOW=0
|
||
|
|
fi
|
||
|
|
warn "bandit: HIGH=$HIGH MED=$MED LOW=$LOW"
|
||
|
|
[ $HIGH -gt 0 ] && FAIL=$((FAIL+HIGH)) || true
|
||
|
|
[ $MED -gt 0 ] && WARN=$((WARN+MED)) || true
|
||
|
|
[ $LOW -gt 0 ] && WARN=$((WARN+LOW)) || true
|
||
|
|
echo "| bandit | Python 静态 | ⚠️ HIGH=$HIGH MED=$MED LOW=$LOW |" >> "$REPORT"
|
||
|
|
|
||
|
|
# 列出问题
|
||
|
|
if [ $HIGH -gt 0 ] || [ $MED -gt 0 ]; then
|
||
|
|
cat >> "$REPORT" <<EOR
|
||
|
|
|
||
|
|
### bandit 详情
|
||
|
|
|
||
|
|
| 文件 | 行 | 严重度 | 问题 |
|
||
|
|
|---|---|---|---|
|
||
|
|
EOR
|
||
|
|
jq -r '.results[] | "| \(.filename) | \(.line_number) | \(.issue_severity) | \(.issue_text | gsub("\n"; " ")) |"' "$LOG_DIR/bandit.json" >> "$REPORT" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# 2. safety (Python 依赖漏洞)
|
||
|
|
# =============================================================================
|
||
|
|
if [ "$RUN_PYTHON" = true ]; then
|
||
|
|
info "── 2/5 safety: Python 依赖漏洞"
|
||
|
|
|
||
|
|
if ! command -v safety &> /dev/null; then
|
||
|
|
warn "safety 未安装,跑: pip install safety"
|
||
|
|
echo "| safety | Python 依赖 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
if safety check --file=backend/requirements.txt --output=text > "$LOG_DIR/safety.txt" 2>&1; then
|
||
|
|
ok "safety: 无漏洞"
|
||
|
|
PASS=$((PASS+1))
|
||
|
|
echo "| safety | Python 依赖 | ✅ 无漏洞 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
VULN_COUNT=$(grep -c "VULNERABLE" "$LOG_DIR/safety.txt" 2>/dev/null || echo 0)
|
||
|
|
warn "safety: $VULN_COUNT 个漏洞"
|
||
|
|
[ $VULN_COUNT -gt 0 ] && FAIL=$((FAIL+VULN_COUNT)) || true
|
||
|
|
echo "| safety | Python 依赖 | 🔴 $VULN_COUNT 个漏洞 |" >> "$REPORT"
|
||
|
|
cat >> "$REPORT" <<EOR
|
||
|
|
|
||
|
|
### safety 详情
|
||
|
|
|
||
|
|
\`\`\`
|
||
|
|
$(cat "$LOG_DIR/safety.txt" | head -30)
|
||
|
|
\`\`\`
|
||
|
|
EOR
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# 3. pip-audit (Python 依赖漏洞,更准)
|
||
|
|
# =============================================================================
|
||
|
|
if [ "$RUN_PYTHON" = true ]; then
|
||
|
|
info "── 3/5 pip-audit: Python 依赖漏洞(精确)"
|
||
|
|
|
||
|
|
if ! command -v pip-audit &> /dev/null; then
|
||
|
|
warn "pip-audit 未安装,跑: pip install pip-audit"
|
||
|
|
echo "| pip-audit | Python 依赖 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
if pip-audit -r backend/requirements.txt --format=json > "$LOG_DIR/pip-audit.json" 2>&1; then
|
||
|
|
ok "pip-audit: 无漏洞"
|
||
|
|
PASS=$((PASS+1))
|
||
|
|
echo "| pip-audit | Python 依赖 | ✅ 无漏洞 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
VULN_COUNT=$(python3 -c "import json; d=json.load(open('$LOG_DIR/pip-audit.json')); print(len(d.get('vulnerabilities', [])))" 2>/dev/null || echo 0)
|
||
|
|
warn "pip-audit: $VULN_COUNT 个漏洞"
|
||
|
|
[ $VULN_COUNT -gt 0 ] && FAIL=$((FAIL+VULN_COUNT)) || true
|
||
|
|
echo "| pip-audit | Python 依赖 | 🔴 $VULN_COUNT 个漏洞 |" >> "$REPORT"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# 4. npm audit (JS 依赖漏洞)
|
||
|
|
# =============================================================================
|
||
|
|
if [ "$RUN_JS" = true ]; then
|
||
|
|
info "── 4/5 npm audit: JS 依赖漏洞"
|
||
|
|
|
||
|
|
for d in frontend-admin frontend-agent frontend-h5 frontend-portal; do
|
||
|
|
if [ -d "$d" ]; then
|
||
|
|
info " → $d"
|
||
|
|
if [ -f "$d/package-lock.json" ]; then
|
||
|
|
cd "$d"
|
||
|
|
if npm audit --json > "$LOG_DIR/npm-$d.json" 2>&1; then
|
||
|
|
ok " $d: 无漏洞"
|
||
|
|
PASS=$((PASS+1))
|
||
|
|
else
|
||
|
|
VULN=$(python3 -c "import json; d=json.load(open('$LOG_DIR/npm-$d.json')); m=d.get('metadata',{}).get('vulnerabilities',{}); print(m.get('total', 0))" 2>/dev/null || echo 0)
|
||
|
|
CRIT=$(python3 -c "import json; d=json.load(open('$LOG_DIR/npm-$d.json')); m=d.get('metadata',{}).get('vulnerabilities',{}); print(m.get('critical', 0))" 2>/dev/null || echo 0)
|
||
|
|
HIGH=$(python3 -c "import json; d=json.load(open('$LOG_DIR/npm-$d.json')); m=d.get('metadata',{}).get('vulnerabilities',{}); print(m.get('high', 0))" 2>/dev/null || echo 0)
|
||
|
|
warn " $d: total=$VULN critical=$CRIT high=$HIGH"
|
||
|
|
[ $CRIT -gt 0 ] && CRITICAL=$((CRITICAL+CRIT)) || true
|
||
|
|
[ $HIGH -gt 0 ] && FAIL=$((FAIL+HIGH)) || true
|
||
|
|
[ $VULN -gt 0 ] && WARN=$((WARN+VULN)) || true
|
||
|
|
echo "| npm-audit-$d | JS 依赖 | ⚠️ total=$VULN crit=$CRIT high=$HIGH |" >> "$REPORT"
|
||
|
|
fi
|
||
|
|
cd "$PROJECT_ROOT"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
fi
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# 5. gitleaks (Secret 扫描)
|
||
|
|
# =============================================================================
|
||
|
|
if [ "$RUN_SECRETS" = true ]; then
|
||
|
|
info "── 5/5 gitleaks: Secret 扫描"
|
||
|
|
|
||
|
|
if ! command -v gitleaks &> /dev/null; then
|
||
|
|
warn "gitleaks 未安装,跑(可选):"
|
||
|
|
echo " brew install gitleaks # Mac"
|
||
|
|
echo " scoop install gitleaks # Windows"
|
||
|
|
echo " docker run -v \$(pwd):/repo zricethezav/gitleaks:latest detect --source /repo --no-git -v"
|
||
|
|
echo "| gitleaks | Secret 扫描 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
if gitleaks detect --source . --no-git -v > "$LOG_DIR/gitleaks.txt" 2>&1; then
|
||
|
|
ok "gitleaks: 无 secret 泄露"
|
||
|
|
PASS=$((PASS+1))
|
||
|
|
echo "| gitleaks | Secret 扫描 | ✅ 无泄露 |" >> "$REPORT"
|
||
|
|
else
|
||
|
|
LEAK_COUNT=$(grep -c "Finding:" "$LOG_DIR/gitleaks.txt" 2>/dev/null || echo 0)
|
||
|
|
if [ "$LEAK_COUNT" -gt 0 ]; then
|
||
|
|
warn "gitleaks: 发现 $LEAK_COUNT 个 secret"
|
||
|
|
CRITICAL=$((CRITICAL+LEAK_COUNT))
|
||
|
|
echo "| gitleaks | Secret 扫描 | 🔴 $LEAK_COUNT 个 secret |" >> "$REPORT"
|
||
|
|
|
||
|
|
cat >> "$REPORT" <<EOR
|
||
|
|
|
||
|
|
### gitleaks 详情
|
||
|
|
|
||
|
|
\`\`\`
|
||
|
|
$(head -50 "$LOG_DIR/gitleaks.txt")
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
> 🚨 **CRITICAL**:发现 secret 泄露,立即:
|
||
|
|
> 1. 撤销泄露的 token / 密钥
|
||
|
|
> 2. 创新凭据
|
||
|
|
> 3. 加进 .gitignore(防二次泄露)
|
||
|
|
> 4. 改所有引用
|
||
|
|
EOR
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# 总结
|
||
|
|
# =============================================================================
|
||
|
|
cat >> "$REPORT" <<EOF
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 总结
|
||
|
|
|
||
|
|
| 等级 | 数量 |
|
||
|
|
|---|---|
|
||
|
|
| ✅ PASS | $PASS |
|
||
|
|
| ⚠️ WARN | $WARN |
|
||
|
|
| 🔴 FAIL | $FAIL |
|
||
|
|
| 🚨 CRITICAL | $CRITICAL |
|
||
|
|
|
||
|
|
EOF
|
||
|
|
|
||
|
|
if [ $CRITICAL -gt 0 ]; then
|
||
|
|
cat >> "$REPORT" <<EOF
|
||
|
|
## 🚨 阻断
|
||
|
|
|
||
|
|
发现 **$CRITICAL** 个 CRITICAL 问题,**必须**:
|
||
|
|
1. 撤销所有泄露的 secret(token / 密钥 / 凭据)
|
||
|
|
2. 创新凭据 + 配新引用
|
||
|
|
3. 加进 .gitignore
|
||
|
|
4. 评审所有引用 + 改
|
||
|
|
|
||
|
|
## 下一步
|
||
|
|
|
||
|
|
1. 修所有 CRITICAL(立即)
|
||
|
|
2. 修所有 FAIL(本周末)
|
||
|
|
3. 评估 WARN(下迭代)
|
||
|
|
4. 加 CI 自动化跑(本季度)
|
||
|
|
EOF
|
||
|
|
echo ""
|
||
|
|
error "🚨 CRITICAL: $CRITICAL 个 secret 泄露或 CRITICAL 漏洞,必须立即处理"
|
||
|
|
exit 3
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ $FAIL -gt 0 ]; then
|
||
|
|
cat >> "$REPORT" <<EOF
|
||
|
|
## 🛑 FAIL
|
||
|
|
|
||
|
|
发现 **$FAIL** 个 FAIL(HIGH)级问题,本周末修。
|
||
|
|
|
||
|
|
## 下一步
|
||
|
|
|
||
|
|
1. 修 FAIL(本周末)
|
||
|
|
2. 评估 WARN(下迭代)
|
||
|
|
3. 加 CI 自动化跑
|
||
|
|
EOF
|
||
|
|
echo ""
|
||
|
|
warn "🛑 FAIL: $FAIL 个 HIGH 级问题,本周末修"
|
||
|
|
exit 2
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ $WARN -gt 0 ]; then
|
||
|
|
cat >> "$REPORT" <<EOF
|
||
|
|
## ⚠️ WARN
|
||
|
|
|
||
|
|
发现 **$WARN** 个 MEDIUM/LOW 级问题,下迭代评估。
|
||
|
|
|
||
|
|
## 下一步
|
||
|
|
|
||
|
|
1. 评估 WARN(下迭代)
|
||
|
|
2. 加 CI 自动化跑
|
||
|
|
3. 跑批频率:每周一次
|
||
|
|
EOF
|
||
|
|
echo ""
|
||
|
|
warn "⚠️ WARN: $WARN 个问题,下迭代评估"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
cat >> "$REPORT" <<EOF
|
||
|
|
## ✅ 全部通过
|
||
|
|
|
||
|
|
无 CRITICAL / FAIL / WARN,健康度 100%。
|
||
|
|
|
||
|
|
## 后续
|
||
|
|
|
||
|
|
1. 加 CI 自动化跑(每周 + 推送触发)
|
||
|
|
2. 跑批频率:每周一次 + 重大变更后
|
||
|
|
EOF
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
ok "✅ 全部通过,健康度 100%"
|
||
|
|
ok "报告: $REPORT"
|
||
|
|
exit 0
|