#!/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" < /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" <> "$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" < /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" < 🚨 **CRITICAL**:发现 secret 泄露,立即: > 1. 撤销泄露的 token / 密钥 > 2. 创新凭据 > 3. 加进 .gitignore(防二次泄露) > 4. 改所有引用 EOR fi fi fi fi # ============================================================================= # 总结 # ============================================================================= cat >> "$REPORT" <> "$REPORT" <> "$REPORT" <> "$REPORT" <> "$REPORT" <