欢迎光临
我们一直在努力

Cursor 安全插件链:构建实时 SAST/DAST 辅助分析工作流


在这里插入图片描述


Cursor 安全插件链:构建实时 SAST/DAST 辅助分析工作流


目录

  • 摘要
  • 一、开发安全左移核心理念与 Cursor 集成价值
    • 1.1 什么是"安全左移"
    • 1.2 SAST 与 DAST 基础概念
    • 1.3 Cursor IDE 在安全工作流中的定位
    • 1.4 集成价值与预期收益
  • 二、基础环境搭建与必要安全插件安装
    • 2.1 开发环境准备
      • 2.1.1 操作系统要求
      • 2.1.2 Cursor IDE 安装与基础配置
      • 2.1.3 Node.js / Python 运行时环境
    • 2.2 核心安全插件安装
      • 2.2.1 Semgrep 插件安装与配置
      • 2.2.2 SonarLint 插件集成
      • 2.2.3 ESLint Security 插件
      • 2.2.4 Bandit(Python 安全扫描)
      • 2.2.5 OWASP ZAP 辅助工具
    • 2.3 插件链依赖关系与版本兼容性
  • 三、配置自定义规则实现静态代码审计自动化
    • 3.1 Semgrep 规则文件结构详解
    • 3.2 编写第一条自定义 SAST 规则
      • 3.2.1 规则语法基础
      • 3.2.2 模式匹配与元变量
      • 3.2.3 规则严重等级定义
    • 3.3 多语言规则配置
      • 3.3.1 JavaScript/TypeScript 规则
      • 3.3.2 Python 规则
      • 3.3.3 Java 规则
    • 3.4 Cursor 中自动化触发配置
    • 3.5 规则测试与验证
  • 四、构建动态交互测试流程辅助运行时检测
    • 4.1 DAST 基础原理与适用场景
    • 4.2 OWASP ZAP 本地代理配置
    • 4.3 Cursor 终端集成动态扫描
    • 4.4 API 端点自动化探测
    • 4.5 运行时漏洞报告解析
  • 五、编写提示词引导 AI 识别常见漏洞模式
    • 5.1 Cursor AI 安全审查提示词设计原则
    • 5.2 SQL 注入识别提示词模板
    • 5.3 XSS 漏洞检测提示词模板
    • 5.4 认证与授权缺陷检测
    • 5.5 敏感信息泄露检测
    • 5.6 提示词链组合策略
  • 六、实战演练:从代码编写到风险修复闭环
    • 6.1 项目初始化与场景设定
    • 6.2 编写含漏洞的示例代码
    • 6.3 SAST 扫描触发与结果分析
    • 6.4 AI 辅助漏洞解读
    • 6.5 修复代码编写与验证
    • 6.6 回归扫描确认修复有效
  • 七、误报率优化策略与审计结果精准度提升
    • 7.1 误报产生的常见原因
    • 7.2 规则精细化调优
    • 7.3 上下文感知过滤
    • 7.4 白名单与抑制机制
    • 7.5 持续反馈循环建立
  • 八、团队协作规范与安全检查流程标准化
    • 8.1 安全编码规范文档化
    • 8.2 Git Hooks 集成安全扫描
    • 8.3 Code Review 安全检查清单
    • 8.4 安全事件响应流程
    • 8.5 团队安全培训与知识共享
  • 九、典型报错分析与插件链运行故障排查
    • 9.1 插件安装失败排查
    • 9.2 规则解析错误
    • 9.3 扫描超时与性能问题
    • 9.4 AI 提示词无响应处理
    • 9.5 网络代理与证书问题
    • 9.6 版本冲突解决
  • 十、进阶技巧:扩展私有规则库与持续集成对接
    • 10.1 企业私有规则库搭建
    • 10.2 CI/CD 管道集成
    • 10.3 安全报告自动生成
    • 10.4 与漏洞管理平台对接
    • 10.5 自定义仪表盘与度量
  • 常见陷阱与问题排除
  • 总结
  • 详细参考资料
  • 附录
    • 附录 A:完整规则文件模板
    • 附录 B:提示词速查表
    • 附录 C:故障排查决策树
    • 附录 D:推荐学习资源

摘要

在软件安全领域,“安全左移”(Shift-Left Security)已成为现代开发团队的核心实践理念。其核心思想是将安全检测从传统的上线前测试阶段,前移至代码编写阶段,从而在漏洞产生的第一时间发现并修复,大幅降低修复成本与安全风险。

Cursor 作为新一代 AI 驱动的代码编辑器,凭借其强大的 AI 辅助能力、丰富的插件生态以及高度可定制的扩展机制,为构建"编码即安全"的开发工作流提供了理想平台。本文将手把手带你从零开始,在 Cursor 中搭建一套完整的 SAST(静态应用安全测试)与 DAST(动态应用安全测试)辅助分析工作流。

本教程涵盖环境搭建、插件安装配置、自定义安全规则编写、AI 提示词工程、实战漏洞修复演练、误报优化、团队协作规范、故障排查以及 CI/CD 集成等十大主题。无论你是刚接触安全开发的新手,还是希望提升团队安全实践水平的技术负责人,都能从本文中获得可直接落地的操作指南。

适用读者:前端/后端开发工程师、安全工程师、DevSecOps 从业者、技术团队负责人

前置要求:基本的编程经验(熟悉至少一门编程语言)、了解基本的 Web 安全概念

预计阅读与实践时间:完整阅读约 2-3 小时,动手实践约 1-2 天


一、开发安全左移核心理念与 Cursor 集成价值

1.1 什么是"安全左移"

“安全左移”(Shift-Left Security)是软件工程中的一个重要理念,其名称来源于将软件开发生命周期(SDLC)画成一条从左到右的时间轴:

需求分析 → 设计 → 编码 → 测试 → 部署 → 运维
←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
传统安全检测位置(右侧)
→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→
安全左移后的检测位置(左侧)

传统模式的问题:

在传统开发流程中,安全测试通常在开发完成后的 QA 阶段或上线前进行。这意味着:

  • 漏洞发现时,相关代码可能已经与其他模块深度耦合
  • 修复一个早期引入的漏洞,可能需要修改大量后续代码
  • 修复成本随时间呈指数级增长(IBM 研究显示,生产环境修复成本是设计阶段的 100 倍)
  • 安全团队与开发团队之间存在信息断层

安全左移的核心实践:

阶段安全活动工具示例
需求阶段 威胁建模 STRIDE、DREAD
设计阶段 安全架构评审 安全设计模式
编码阶段 实时代码审计 SAST 工具(本文重点)
测试阶段 动态安全测试 DAST 工具(本文重点)
部署阶段 容器/依赖扫描 Trivy、Snyk
运维阶段 运行时防护 WAF、RASP

1.2 SAST 与 DAST 基础概念

SAST(Static Application Security Testing,静态应用安全测试)

SAST 是一种在不运行程序的情况下,通过分析源代码、字节码或二进制文件来发现安全漏洞的技术。

工作原理:

源代码 → 词法分析 → 语法分析 → 构建 AST/数据流图 → 模式匹配/污点追踪 → 报告漏洞

核心能力:

  • 模式匹配:检测已知危险函数调用(如 eval()、exec())
  • 数据流分析:追踪用户输入从入口到危险函数的传播路径
  • 控制流分析:检查条件分支是否覆盖所有安全场景
  • 污点追踪:标记不可信数据,监控其是否未经净化就进入敏感操作

优势:

  • 无需运行程序,编码阶段即可检测
  • 覆盖率高,可扫描所有代码路径
  • 能精确定位到具体代码行

局限:

  • 存在误报(将安全代码标记为漏洞)
  • 无法检测运行时才暴露的问题
  • 对动态语言支持有限
DAST(Dynamic Application Security Testing,动态应用安全测试)

DAST 是在程序运行状态下,通过模拟攻击者行为来发现安全漏洞的技术。

工作原理:

运行中的应用 ← HTTP请求(含攻击载荷) ← DAST工具
运行中的应用 → HTTP响应 → DAST工具 → 分析响应 → 报告漏洞

核心能力:

  • SQL 注入探测
  • XSS(跨站脚本)检测
  • CSRF(跨站请求伪造)验证
  • 目录遍历测试
  • 认证绕过尝试
  • 安全头部检查

优势:

  • 检测运行时真实漏洞,误报率低
  • 不依赖源代码,可测试任何 Web 应用
  • 能发现配置类问题(如缺少安全头)

局限:

  • 需要应用处于运行状态
  • 覆盖率取决于爬虫能力
  • 无法定位到具体代码行
SAST 与 DAST 互补关系

┌─────────────────────────────────────────────────┐
│ 安全检测覆盖矩阵 │
├──────────────────┬──────────────┬───────────────┤
│ 漏洞类型 │ SAST │ DAST │
├──────────────────┼──────────────┼───────────────┤
│ SQL 注入 │ ✓(代码层) │ ✓(运行时) │
│ XSS │ ✓(代码层) │ ✓(运行时) │
│ 硬编码密钥 │ ✓ │ ✗ │
│ 配置错误 │ ✗ │ ✓ │
│ 业务逻辑漏洞 │ 部分 │ 部分 │
│ 依赖库漏洞 │ ✓(SCA) │ ✗ │
│ 认证绕过 │ 部分 │ ✓ │
└──────────────────┴──────────────┴───────────────┘

1.3 Cursor IDE 在安全工作流中的定位

Cursor 是基于 VS Code 深度定制的 AI 代码编辑器,它在安全工作流中具有独特优势:

AI 原生能力:

  • 内置 AI 对话(Ctrl+L),可实时询问代码安全问题
  • 代码补全时自动考虑安全最佳实践
  • 可选中代码片段让 AI 进行安全审查
  • 支持自定义系统提示词,定制安全审查角色

插件生态兼容:

  • 完全兼容 VS Code 插件市场
  • 支持 Semgrep、SonarLint 等主流安全插件
  • 终端集成,可直接运行命令行安全工具

工作流集成:

  • 支持 .cursorrules 项目级 AI 配置
  • Git 集成,可在提交前触发安全检查
  • 任务系统,可配置自动化安全扫描任务

1.4 集成价值与预期收益

将 SAST/DAST 工具链集成到 Cursor 开发环境中,可以带来以下收益:

对开发者:

  • 编码时即时获得安全反馈,无需等待代码审查
  • AI 辅助理解漏洞原理,提升安全编码能力
  • 减少后期返工,提高开发效率

对团队:

  • 统一安全标准,降低人为遗漏
  • 缩短漏洞修复周期(从周级降至分钟级)
  • 建立可量化的安全度量体系

对组织:

  • 降低安全事件发生概率
  • 减少安全修复成本
  • 满足合规要求(如等保、GDPR、SOC2)

量化预期:

指标集成前集成后改善幅度
漏洞发现阶段 测试/上线后 编码时 左移 2-3 个阶段
平均修复时间 3-7 天 10-30 分钟 降低 95%+
安全相关返工率 15-25% 3-5% 降低 80%+
开发者安全意识 被动接受 主动防御 质变

二、基础环境搭建与必要安全插件安装

2.1 开发环境准备

2.1.1 操作系统要求

本教程支持以下操作系统:

操作系统最低版本推荐版本
Windows Windows 10 (1903+) Windows 11
macOS macOS 11 (Big Sur) macOS 14 (Sonoma)
Linux Ubuntu 20.04 / Fedora 36 Ubuntu 22.04+

硬件建议:

  • 内存:至少 8GB(推荐 16GB,安全扫描工具较耗内存)
  • 磁盘:至少 500MB 可用空间(插件+规则库)
  • CPU:多核处理器有助于并行扫描
2.1.2 Cursor IDE 安装与基础配置

第一步:下载与安装

访问 Cursor 官方网站(cursor.com),根据操作系统下载对应版本:

# macOS (使用 Homebrew)
brew install –cask cursor

# Windows
# 下载安装包后双击运行,按向导完成安装

# Linux (Ubuntu/Debian)
wget https://download.cursor.com/linux/appimage/latest -O cursor.AppImage
chmod +x cursor.AppImage
./cursor.AppImage

第二步:初始配置

安装完成后,打开 Cursor 进行基础配置:

  • 打开设置:Ctrl + ,(macOS: Cmd + ,)
  • 搜索并配置以下选项:
  • // settings.json – 通过 Ctrl+Shift+P → "Open Settings (JSON)" 打开
    {
    // 编辑器基础配置
    "editor.fontSize": 14,
    "editor.tabSize": 2,
    "editor.formatOnSave": true,

    // 终端配置(安全工具需要)
    "terminal.integrated.defaultProfile.linux": "bash",
    "terminal.integrated.defaultProfile.osx": "zsh",
    "terminal.integrated.defaultProfile.windows": "PowerShell",

    // 文件监视(SAST 实时扫描需要)
    "files.watcherExclude": {
    "**/node_modules/**": true,
    "**/.git/**": true,
    "**/dist/**": true,
    "**/build/**": true
    },

    // 安全相关:禁止自动执行不受信任的工作区
    "security.workspace.trust.enabled": true
    }

    第三步:安装必要的基础扩展

    在 Cursor 中按 Ctrl+Shift+X 打开扩展市场,安装以下基础扩展:

    • Python(如果开发 Python 项目)
    • ESLint(如果开发 JavaScript/TypeScript 项目)
    • Docker(如果需要容器化部署)
    • GitLens(代码追溯,辅助安全审计)
    2.1.3 Node.js / Python 运行时环境

    安全插件链依赖运行时环境,请确保安装:

    Node.js(v18+):

    # 使用 nvm 安装(推荐)
    # macOS/Linux
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
    source ~/.bashrc
    nvm install 18
    nvm use 18

    # 验证安装
    node –version # 应显示 v18.x.x 或更高
    npm –version # 应显示 9.x.x 或更高

    # Windows
    # 访问 https://nodejs.org 下载 LTS 版本安装
    # 或使用 winget:
    winget install OpenJS.NodeJS.LTS

    Python(3.9+):

    # macOS
    brew install python@3.11

    # Ubuntu/Debian
    sudo apt update
    sudo apt install python3.11 python3-pip python3-venv

    # 验证安装
    python3 –version # 应显示 3.11.x
    pip3 –version

    # Windows
    winget install Python.Python.3.11

    2.2 核心安全插件安装

    2.2.1 Semgrep 插件安装与配置

    Semgrep 是目前最流行的开源 SAST 工具之一,支持 30+ 编程语言,规则编写直观。

    第一步:安装 Semgrep CLI

    # 方式一:pip 安装(推荐)
    pip3 install semgrep

    # 方式二:macOS Homebrew
    brew install semgrep

    # 方式三:Docker(无需本地安装)
    docker run –rm -v "${PWD}:/src" semgrep/semgrep semgrep –config=auto /src

    # 验证安装
    semgrep –version
    # 应输出类似:semgrep version 1.67.0

    第二步:安装 Cursor/VS Code Semgrep 扩展

  • 在 Cursor 中按 Ctrl+Shift+X
  • 搜索 “Semgrep”
  • 安装由 Semgrep 官方发布的扩展(发布者:Semgrep)
  • 安装完成后重启 Cursor
  • 第三步:配置 Semgrep 扩展

    打开设置 JSON,添加 Semgrep 配置:

    {
    // Semgrep 扩展配置
    "semgrep.path": "semgrep", // 如果不在 PATH 中,填写完整路径
    "semgrep.scan.onType": true, // 输入时实时扫描
    "semgrep.scan.onSave": true, // 保存时扫描
    "semgrep.scan.configuration": [
    "auto", // 自动选择规则
    "./semgrep-rules/" // 本地自定义规则目录
    ],
    "semgrep.severity.overrides": {
    "INFO": "hint",
    "WARNING": "warning",
    "ERROR": "error"
    },
    // 排除不需要扫描的路径
    "semgrep.exclude": [
    "node_modules",
    "dist",
    "build",
    ".git",
    "vendor",
    "*.min.js",
    "*.test.js"
    ]
    }

    第四步:验证 Semgrep 工作

    创建一个测试文件 test-security.js:

    // test-security.js – 用于验证 Semgrep 是否正常工作
    // 这个文件故意包含一个安全问题

    const express = require('express');
    const app = express();

    // 危险:直接使用用户输入构建 SQL 查询(SQL 注入漏洞)
    app.get('/user', (req, res) => {
    const userId = req.query.id;
    // Semgrep 应该能检测到这里的 SQL 注入风险
    const query = "SELECT * FROM users WHERE id = " + userId;
    // 实际项目中这里会执行数据库查询
    res.send(query);
    });

    app.listen(3000);

    在 Cursor 中打开此文件,如果 Semgrep 配置正确,你应该能看到代码下方出现黄色/红色波浪线提示。

    2.2.2 SonarLint 插件集成

    SonarLint 是 SonarQube 的 IDE 插件版本,提供实时代码质量与安全检测。

    安装步骤:

  • Ctrl+Shift+X → 搜索 “SonarLint” → 安装
  • 重启 Cursor
  • 配置 SonarLint:
  • {
    // SonarLint 配置
    "sonarlint.connectedMode.project": {
    // 如果有 SonarQube 服务器,配置连接
    // 没有的话可以先使用独立模式
    "connectionId": "",
    "projectKey": ""
    },
    // 规则配置
    "sonarlint.rules": {
    // 启用所有安全热点规则
    "javascript:S2076": { "level": "on" }, // OS 命令注入
    "javascript:S2077": { "level": "on" }, // SQL 注入
    "javascript:S2078": { "level": "on" }, // LDAP 注入
    "javascript:S5131": { "level": "on" }, // XSS
    "python:S2076": { "level": "on" },
    "python:S2077": { "level": "on" }
    },
    // 分析触发
    "sonarlint.analyzerProperties": {
    "sonar.javascript.node.maxspace": "4096"
    }
    }

    2.2.3 ESLint Security 插件

    对于 JavaScript/TypeScript 项目,ESLint 安全插件是必备工具。

    安装:

    # 在项目目录中安装
    npm install –save-dev eslint eslint-plugin-security eslint-plugin-no-unsanitized

    配置 .eslintrc.json:

    {
    "env": {
    "browser": true,
    "node": true,
    "es2022": true
    },
    "extends": [
    "eslint:recommended",
    "plugin:security/recommended-legacy",
    "plugin:no-unsanitized/DOM"
    ],
    "plugins": [
    "security",
    "no-unsanitized"
    ],
    "rules": {
    // 安全相关规则 – 设为 error 级别
    "security/detect-eval-with-expression": "error",
    "security/detect-child-process": "error",
    "security/detect-disable-mustache-escape": "error",
    "security/detect-no-csrf-before-method-override": "error",
    "security/detect-non-literal-fs-filename": "warn",
    "security/detect-non-literal-regexp": "warn",
    "security/detect-non-literal-require": "error",
    "security/detect-object-injection": "warn",
    "security/detect-possible-timing-attacks": "error",
    "security/detect-pseudoRandomBytes": "error",
    "security/detect-unsafe-regex": "error",
    "security/detect-buffer-noassert": "error",

    // no-unsanitized 规则
    "no-unsanitized/property": "error",
    "no-unsanitized/method": "error"
    }
    }

    2.2.4 Bandit(Python 安全扫描)

    Bandit 是 Python 专用的 SAST 工具,能检测 Python 代码中的常见安全问题。

    安装:

    # pip 安装
    pip3 install bandit

    # 验证
    bandit –version

    Cursor 集成:

    创建 .bandit 配置文件(项目根目录):

    # .bandit – Bandit 配置文件
    [bandit]
    # 排除的目录
    exclude: /tests,/venv,/.git,/node_modules,/migrations

    # 扫描目标
    targets: ./src,./app

    # 严重等级过滤(low, medium, high)
    level: low

    # 置信度过滤
    confidence: low

    # 输出格式
    format: json

    # 输出文件
    output: ./reports/bandit-report.json

    在 Cursor 终端中运行:

    # 扫描整个项目
    bandit -r ./src -f json -o ./reports/bandit-report.json

    # 扫描单个文件
    bandit ./src/app.py

    # 只显示高严重性问题
    bandit -r ./src -ll

    # 使用自定义配置
    bandit -r ./src -c .bandit

    2.2.5 OWASP ZAP 辅助工具

    OWASP ZAP(Zed Attack Proxy)是用于 DAST 的开源工具。

    安装:

    # macOS
    brew install –cask owasp-zap

    # Linux (Ubuntu)
    sudo apt install zaproxy
    # 或下载最新版
    wget https://github.com/zaproxy/zaproxy/releases/download/v2.15.0/ZAP_2.15.0_Linux.tar.gz
    tar -xzf ZAP_2.15.0_Linux.tar.gz

    # Windows
    # 从 https://www.zaproxy.org/download/ 下载安装包

    # Docker 方式(推荐,无需本地安装)
    docker pull ghcr.io/zaproxy/zaproxy:stable

    Cursor 终端集成脚本(创建 scripts/dast-scan.sh):

    #!/bin/bash
    # dast-scan.sh – 在 Cursor 终端中运行 DAST 扫描
    # 用法: ./scripts/dast-scan.sh <目标URL>

    TARGET_URL=${1:-"http://localhost:3000"}
    REPORT_DIR="./reports/dast"
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)

    # 创建报告目录
    mkdir -p ${REPORT_DIR}

    echo "=========================================="
    echo " OWASP ZAP DAST 扫描"
    echo " 目标: ${TARGET_URL}"
    echo " 时间: $(date)"
    echo "=========================================="

    # 使用 Docker 运行 ZAP 基线扫描
    docker run –rm \\
    –network="host" \\
    -v $(pwd)/${REPORT_DIR}:/zap/reports \\
    ghcr.io/zaproxy/zaproxy:stable \\
    zap-baseline.py \\
    -t ${TARGET_URL} \\
    -r /zap/reports/zap-report-${TIMESTAMP}.html \\
    -J /zap/reports/zap-report-${TIMESTAMP}.json \\
    -c zap-rules.conf \\
    -T 10 \\
    -a

    echo ""
    echo "扫描完成!报告已保存至: ${REPORT_DIR}/"
    echo "HTML 报告: ${REPORT_DIR}/zap-report-${TIMESTAMP}.html"
    echo "JSON 报告: ${REPORT_DIR}/zap-report-${TIMESTAMP}.json"

    2.3 插件链依赖关系与版本兼容性

    以下是完整的插件链依赖关系图:

    ┌─────────────────────────────────────────────────────────┐
    │ Cursor IDE │
    ├─────────────────────────────────────────────────────────┤
    │ │
    │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
    │ │ Semgrep │ │ SonarLint │ │ ESLint │ │
    │ │ Extension │ │ Extension │ │ Security │ │
    │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
    │ │ │ │ │
    │ ▼ ▼ ▼ │
    │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
    │ │ Semgrep │ │ SonarQube │ │ Node.js │ │
    │ │ CLI (Py) │ │ Analyzer │ │ Runtime │ │
    │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
    │ │ │ │ │
    ├─────────┼─────────────────┼─────────────────┼───────────┤
    │ ▼ ▼ ▼ │
    │ ┌─────────────────────────────────────────────────┐ │
    │ │ Python 3.9+ Runtime │ │
    │ └─────────────────────────────────────────────────┘ │
    │ │
    │ ┌─────────────┐ ┌─────────────┐ │
    │ │ Bandit │ │ OWASP ZAP │ ← 独立运行 │
    │ │ (Python) │ │ (Java/Docker)│ │
    │ └─────────────┘ └─────────────┘ │
    │ │
    └─────────────────────────────────────────────────────────┘

    版本兼容性矩阵(截至 2026 年):

    组件推荐版本最低版本备注
    Cursor 最新稳定版 0.40+ 保持更新
    Semgrep CLI 1.70+ 1.50+ pip 安装
    Semgrep Extension 最新 1.12+ 与 CLI 版本匹配
    SonarLint 最新 4.0+ 独立模式可用
    Node.js 20 LTS 18 LTS ESLint 需要
    Python 3.11+ 3.9+ Semgrep/Bandit 需要
    ESLint 9.x 8.x 配合安全插件
    Bandit 1.7+ 1.7.0 Python 项目
    OWASP ZAP 2.15+ 2.12+ Docker 推荐

    三、配置自定义规则实现静态代码审计自动化

    3.1 Semgrep 规则文件结构详解

    Semgrep 规则使用 YAML 格式编写,存放在项目中的 semgrep-rules/ 目录下。一个完整的规则文件结构如下:

    # semgrep-rules/custom-security.yaml
    # 自定义安全规则文件

    rules:
    # 每条规则是一个列表项
    id: ruleuniqueidentifier # 规则唯一标识符(必填)
    patterns: # 匹配模式(核心)
    pattern: |
    dangerous_function(…)

    message: | # 检测到问题时的提示信息(必填)
    检测到潜在的安全问题:使用了危险函数。
    请使用安全的替代方案。
    languages: # 适用语言(必填)
    javascript
    typescript
    severity: ERROR # 严重等级:INFO/WARNING/ERROR(必填)
    metadata: # 元数据(可选但推荐)
    category: security # 分类
    cwe: "CWE-89" # CWE 编号
    owasp: "A03:2021 Injection" # OWASP 分类
    confidence: HIGH # 置信度
    likelihood: HIGH # 可能性
    impact: HIGH # 影响
    subcategory:
    vuln # vuln / audit / guardrail
    references: # 参考链接
    https://owasp.org/Top10/A03_2021Injection/
    technology:
    express # 相关技术栈
    fix: | # 自动修复建议(可选)
    safe_function()

    规则文件目录结构建议:

    project-root/
    ├── semgrep-rules/
    │ ├── injection/ # 注入类漏洞
    │ │ ├── sql-injection.yaml
    │ │ ├── command-injection.yaml
    │ │ └── ldap-injection.yaml
    │ ├── xss/ # XSS 类漏洞
    │ │ ├── reflected-xss.yaml
    │ │ └── stored-xss.yaml
    │ ├── auth/ # 认证授权类
    │ │ ├── weak-crypto.yaml
    │ │ ├── hardcoded-secrets.yaml
    │ │ └── insecure-auth.yaml
    │ ├── data-exposure/ # 数据泄露类
    │ │ ├── sensitive-logging.yaml
    │ │ └── insecure-storage.yaml
    │ ├── config/ # 配置类
    │ │ ├── cors-misconfiguration.yaml
    │ │ └── missing-security-headers.yaml
    │ └── custom/ # 业务自定义规则
    │ ├── api-security.yaml
    │ └── business-logic.yaml
    ├── .semgrepignore # 忽略文件
    └── semgrep.yaml # 全局配置

    3.2 编写第一条自定义 SAST 规则

    3.2.1 规则语法基础

    Semgrep 的匹配语法非常直观,它使用目标语言的实际代码语法来编写模式:

    # semgrep-rules/first-rule.yaml
    # 第一条自定义规则:检测 console.log 中的敏感信息

    rules:
    id: nosensitivedatainconsole
    patterns:
    # 匹配 console.log() 中包含特定变量的调用
    pattern: |
    console.log(…, $SENSITIVE, …)

    # 限定变量名包含敏感关键词
    metavariable-regex:
    metavariable: $SENSITIVE
    regex: .*(password|secret|token|apikey|api_key).*
    message: >
    检测到 console.log 中可能输出敏感信息 '$SENSITIVE'。
    生产环境中不应将密码、密钥等敏感数据输出到控制台。
    请使用日志脱敏处理或移除该输出。

    languages:
    javascript
    typescript
    severity: WARNING
    metadata:
    category: security
    cwe: "CWE-532"
    confidence: MEDIUM

    3.2.2 模式匹配与元变量

    Semgrep 支持多种匹配模式:

    rules:
    # 1. 精确匹配 – 匹配特定函数调用
    id: detectevalusage
    pattern: eval($X)
    message: "禁止使用 eval(),存在代码注入风险"
    languages: [javascript]
    severity: ERROR

    # 2. 元变量匹配 – $X 匹配任意表达式
    id: detectsqlconcat
    patterns:
    pattern: |
    $QUERY = "…" + $INPUT

    metavariable-regex:
    metavariable: $QUERY
    regex: .*(sql|query|stmt).*
    message: "检测到 SQL 查询字符串拼接,可能存在 SQL 注入"
    languages: [javascript, python, java]
    severity: ERROR

    # 3. 模式内省略号 – … 匹配任意数量参数
    id: detectexecwithuserinput
    pattern: |
    child_process.exec(…, $INPUT, …)

    message: "exec() 接收外部输入可能导致命令注入"
    languages: [javascript]
    severity: ERROR

    # 4. 否定模式 – 排除特定情况
    id: detecthardcodedsecretnotenv
    patterns:
    pattern: |
    const $VAR = "$VALUE"

    metavariable-regex:
    metavariable: $VAR
    regex: .*(password|secret|key|token).*
    pattern-not: |
    const $VAR = process.env.$ENV_VAR

    pattern-not: |
    const $VAR = ""

    message: "检测到硬编码的敏感信息,请使用环境变量"
    languages: [javascript, typescript]
    severity: ERROR

    # 5. 数据流追踪(taint mode)
    id: taintsqlinjection
    mode: taint
    pattern-sources:
    pattern: req.query.$PARAM
    pattern: req.body.$PARAM
    pattern: req.params.$PARAM
    pattern-sinks:
    pattern: db.query($SQL, )
    pattern: connection.execute($SQL, )
    pattern-sanitizers:
    pattern: sanitize($X)
    pattern: escape($X)
    message: "用户输入未经净化直接进入 SQL 查询"
    languages: [javascript]
    severity: ERROR

    3.2.3 规则严重等级定义

    # 严重等级使用指南
    severity: INFO # 信息提示:最佳实践建议,不阻塞开发
    severity: WARNING # 警告:潜在风险,建议修复
    severity: ERROR # 错误:高危漏洞,必须修复

    # 在 Cursor 中的显示效果:
    # INFO → 灰色提示(不干扰编码)
    # WARNING → 黄色波浪线(引起注意)
    # ERROR → 红色波浪线(必须处理)

    3.3 多语言规则配置

    3.3.1 JavaScript/TypeScript 规则

    # semgrep-rules/injection/sql-injection-js.yaml
    rules:
    # 检测 Express 中的 SQL 注入
    id: expresssqlinjectionstringconcat
    patterns:
    pattern-either:
    # 模式1:字符串拼接
    pattern: |
    $DB.query("…" + $INPUT + "…", …)

    # 模式2:模板字符串(无参数化)
    pattern: |
    $DB.query(`…${$INPUT}…`, …)

    # 模式3:字符串拼接后传入
    pattern: |
    const $SQL = "…" + $INPUT + "…";

    $DB.query($SQL, …)

    pattern-not: |
    $DB.query("…", [$PARAM], …)

    message: >
    检测到 SQL 查询使用字符串拼接构造。
    请使用参数化查询(Prepared Statement)防止 SQL 注入。

    修复示例:
    ✗ db.query("SELECT * FROM users WHERE id = " + userId)
    ✓ db.query("SELECT * FROM users WHERE id = ?", [userId])
    languages: [javascript, typescript]
    severity: ERROR
    metadata:
    cwe: "CWE-89"
    owasp: "A03:2021"
    category: security
    confidence: HIGH
    fix: |
    $DB.query("…", [$INPUT], …)

    # 检测命令注入
    id: jscommandinjection
    patterns:
    pattern-either:
    pattern: |
    require('child_process').exec($CMD)

    pattern: |
    require('child_process').execSync($CMD)

    pattern: |
    import { exec } from 'child_process';

    exec($CMD)

    pattern-not: |
    exec("ls") # 排除硬编码的安全命令

    message: >
    检测到使用 child_process.exec() 执行命令。
    如果命令包含用户输入,将导致命令注入漏洞。
    请使用 execFile() 或 spawn() 并传入参数数组。

    languages: [javascript, typescript]
    severity: ERROR
    metadata:
    cwe: "CWE-78"
    confidence: MEDIUM

    # 检测 XSS – innerHTML 赋值
    id: jsxssinnerhtml
    patterns:
    pattern: |
    $EL.innerHTML = $VALUE

    pattern-not: |
    $EL.innerHTML = ""

    pattern-not: |
    $EL.innerHTML = "…" # 纯字符串字面量

    message: >
    检测到直接设置 innerHTML,如果 $VALUE 包含用户输入,
    将导致存储型/反射型 XSS 漏洞。
    请使用 textContent 或 DOMPurify.sanitize() 处理。

    languages: [javascript, typescript]
    severity: ERROR
    metadata:
    cwe: "CWE-79"

    3.3.2 Python 规则

    # semgrep-rules/injection/sql-injection-python.yaml
    rules:
    # Python SQL 注入检测
    id: pythonsqlinjectionformat
    patterns:
    pattern-either:
    # f-string 格式化
    pattern: |
    $CURSOR.execute(f"…{$VAR}…")

    # % 格式化
    pattern: |
    $CURSOR.execute("…%s…" % $VAR)

    # .format()
    pattern: |
    $CURSOR.execute("…{}…".format($VAR))

    # 字符串拼接
    pattern: |
    $CURSOR.execute("…" + $VAR + "…")

    pattern-not: |
    $CURSOR.execute("…", ($VAR,))

    pattern-not: |
    $CURSOR.execute("…", [$VAR])

    message: >
    检测到 SQL 查询使用字符串格式化构造。
    请使用参数化查询:cursor.execute("SELECT … WHERE id = %s", (user_id,))

    languages: [python]
    severity: ERROR
    metadata:
    cwe: "CWE-89"
    confidence: HIGH

    # Python 命令注入
    id: pythoncommandinjection
    patterns:
    pattern-either:
    pattern: os.system($CMD)
    pattern: os.popen($CMD)
    pattern: subprocess.call($CMD, shell=True, )
    pattern: subprocess.run($CMD, shell=True, )
    pattern: subprocess.Popen($CMD, shell=True, )
    message: >
    检测到可能的命令注入风险。
    os.system() 和 shell=True 会使命令经过 shell 解释,
    如果包含用户输入则存在命令注入风险。
    请使用 subprocess.run([…], shell=False) 并传入参数列表。

    languages: [python]
    severity: ERROR
    metadata:
    cwe: "CWE-78"

    # Python 不安全的反序列化
    id: pythoninsecuredeserialization
    patterns:
    pattern-either:
    pattern: pickle.loads($DATA)
    pattern: pickle.load($FILE)
    pattern: yaml.load($DATA)
    pattern-not: |
    yaml.load($DATA, Loader=yaml.SafeLoader)

    pattern-not: |
    yaml.safe_load($DATA)

    message: >
    检测到不安全的反序列化操作。
    pickle 和 yaml.load() 可被利用执行任意代码。
    请使用 yaml.safe_load() 或添加数据验证。

    languages: [python]
    severity: ERROR
    metadata:
    cwe: "CWE-502"

    # Python 硬编码密钥
    id: pythonhardcodedsecret
    patterns:
    pattern: |
    $VAR = "$VALUE"

    metavariable-regex:
    metavariable: $VAR
    regex: (?i).*(password|passwd|secret|api_key|apikey|token|private_key).*
    metavariable-regex:
    metavariable: $VALUE
    regex: .{8,} # 至少8个字符,排除空字符串
    pattern-not: |
    $VAR = os.environ.get(…)

    pattern-not: |
    $VAR = os.getenv(…)

    message: >
    检测到硬编码的敏感信息。
    请使用环境变量或密钥管理服务存储敏感信息。

    languages: [python]
    severity: ERROR
    metadata:
    cwe: "CWE-798"

    3.3.3 Java 规则

    # semgrep-rules/injection/sql-injection-java.yaml
    rules:
    # Java SQL 注入
    id: javasqlinjection
    patterns:
    pattern-either:
    pattern: |
    $STMT.executeQuery("…" + $INPUT + "…")

    pattern: |
    $STMT.executeUpdate("…" + $INPUT + "…")

    pattern: |
    $CONN.createStatement().executeQuery("…" + $INPUT + "…")

    pattern-not: |
    $CONN.prepareStatement("…")

    message: >
    检测到 SQL 语句拼接。请使用 PreparedStatement 参数化查询。
    ✗ stmt.executeQuery("SELECT * FROM users WHERE id = " + userId)
    ✓ PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
    ps.setString(1, userId);

    languages: [java]
    severity: ERROR
    metadata:
    cwe: "CWE-89"

    # Java 不安全的随机数
    id: javainsecurerandom
    patterns:
    pattern: new Random()
    pattern-not-inside: |
    // 测试代码中允许
    @Test

    message: >
    检测到使用 java.util.Random,该类不是密码学安全的。
    安全场景请使用 java.security.SecureRandom。

    languages: [java]
    severity: WARNING
    metadata:
    cwe: "CWE-330"

    3.4 Cursor 中自动化触发配置

    在 Cursor 中配置保存时自动触发安全扫描:

    方式一:通过 Semgrep 扩展设置(已在 2.2.1 配置)

    方式二:通过 VS Code Tasks 配置

    创建 .vscode/tasks.json:

    {
    "version": "2.0.0",
    "tasks": [
    {
    "label": "Semgrep: 扫描当前文件",
    "type": "shell",
    "command": "semgrep",
    "args": [
    "–config", "auto",
    "–config", "./semgrep-rules/",
    "–error",
    "${file}"
    ],
    "group": "test",
    "presentation": {
    "reveal": "silent",
    "panel": "dedicated",
    "clear": true
    },
    "problemMatcher": {
    "owner": "semgrep",
    "fileLocation": "relative",
    "pattern": {
    "regexp": "^(.+):(\\\\d+):(\\\\d+):\\\\s+(.+)$",
    "file": 1,
    "line": 2,
    "column": 3,
    "message": 4
    }
    }
    },
    {
    "label": "Semgrep: 全项目扫描",
    "type": "shell",
    "command": "semgrep",
    "args": [
    "–config", "auto",
    "–config", "./semgrep-rules/",
    "–json",
    "–output", "./reports/semgrep-report.json",
    "."
    ],
    "group": "test",
    "presentation": {
    "reveal": "always",
    "panel": "dedicated"
    }
    },
    {
    "label": "Bandit: Python 安全扫描",
    "type": "shell",
    "command": "bandit",
    "args": [
    "-r", "./src",
    "-f", "custom",
    "–msg-template", "{abspath}:{line}: [{severity}] {msg} ({test_id})"
    ],
    "group": "test",
    "presentation": {
    "reveal": "always",
    "panel": "dedicated"
    }
    },
    {
    "label": "ESLint: 安全规则检查",
    "type": "shell",
    "command": "npx",
    "args": [
    "eslint",
    "–ext", ".js,.ts,.jsx,.tsx",
    "–rule", "security/detect-eval-with-expression: error",
    "."
    ],
    "group": "test"
    },
    {
    "label": "安全扫描: 全部",
    "dependsOn": [
    "Semgrep: 全项目扫描",
    "ESLint: 安全规则检查"
    ],
    "group": {
    "kind": "test",
    "isDefault": true
    }
    }
    ]
    }

    方式三:配置保存时自动运行(通过扩展设置)

    // settings.json 追加
    {
    // 保存时运行任务
    "tasks.autoRun": true,

    // 文件保存时触发
    "[javascript]": {
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
    }
    },
    "[typescript]": {
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
    }
    }
    }

    3.5 规则测试与验证

    编写规则后,必须测试其正确性:

    # 测试单条规则
    semgrep –config semgrep-rules/injection/sql-injection-js.yaml –test

    # 使用测试用例验证
    # 创建测试文件:semgrep-rules/injection/test-sql-injection.js

    # 测试文件内容示例(包含注释标记):

    // semgrep-rules/injection/test-sql-injection.js
    // 测试用例文件 – 用于验证规则准确性

    const express = require('express');
    const mysql = require('mysql');

    const db = mysql.createConnection({});

    // ruleid: express-sql-injection-string-concat
    // ↑ 这行注释告诉 Semgrep 下一行应该被匹配
    app.get('/user', (req, res) => {
    const userId = req.query.id;
    db.query("SELECT * FROM users WHERE id = " + userId);
    });

    // ruleid: express-sql-injection-string-concat
    app.get('/search', (req, res) => {
    const keyword = req.query.q;
    db.query(`SELECT * FROM products WHERE name LIKE '%${keyword}%'`);
    });

    // ok: express-sql-injection-string-concat
    // ↑ 这行注释告诉 Semgrep 下一行不应该被匹配
    app.get('/safe-user', (req, res) => {
    const userId = req.query.id;
    db.query("SELECT * FROM users WHERE id = ?", [userId]);
    });

    // ok: express-sql-injection-string-concat
    app.get('/safe-search', (req, res) => {
    const keyword = req.query.q;
    db.query("SELECT * FROM products WHERE name LIKE ?", [`%${keyword}%`]);
    });

    # 运行测试
    semgrep –config semgrep-rules/injection/sql-injection-js.yaml \\
    –test semgrep-rules/injection/

    # 期望输出:
    # ✓ express-sql-injection-string-concat
    # 2/2 findings matched (2 true positives, 0 false positives)
    # All tests passed!


    四、构建动态交互测试流程辅助运行时检测

    4.1 DAST 基础原理与适用场景

    DAST(动态应用安全测试)通过向运行中的应用发送恶意请求并分析响应来发现漏洞。

    适用场景:

    • Web API 安全测试
    • 表单输入验证测试
    • 认证/授权流程测试
    • 安全配置检查(CORS、安全头等)
    • 会话管理测试

    不适用场景:

    • 代码逻辑审查(应使用 SAST)
    • 第三方依赖漏洞(应使用 SCA)
    • 加密算法正确性验证

    4.2 OWASP ZAP 本地代理配置

    配置 Cursor 终端代理:

    # 启动 ZAP 作为本地代理
    # 方式一:直接启动
    zap.sh -daemon -port 8080 -config api.disablekey=true

    # 方式二:Docker 启动(推荐)
    docker run -d –name zap-proxy \\
    -p 8080:8080 \\
    -p 8090:8090 \\
    ghcr.io/zaproxy/zaproxy:stable \\
    zap.sh -daemon -port 8080 -config api.disablekey=true

    创建 ZAP 配置文件 zap-config.conf:

    # zap-config.conf – ZAP 扫描配置
    # 扫描策略
    scanner.strength=HIGH
    scanner.threshold=MEDIUM

    # 排除路径(不扫描静态资源)
    globalexcludeurl=.*\\\\.css
    globalexcludeurl=.*\\\\.js
    globalexcludeurl=.*\\\\.png
    globalexcludeurl=.*\\\\.jpg
    globalexcludeurl=.*\\\\.gif
    globalexcludeurl=.*\\\\.ico

    # 认证配置(如果应用需要登录)
    # authentication.method=form
    # authentication.loginurl=http://localhost:3000/login
    # authentication.username=admin
    # authentication.password=admin123

    # 扫描超时设置
    scanner.timeout=300
    spider.maxduration=10

    4.3 Cursor 终端集成动态扫描

    创建 scripts/dast-workflow.sh:

    #!/bin/bash
    # dast-workflow.sh – 完整的 DAST 工作流脚本
    # 在 Cursor 终端中运行: ./scripts/dast-workflow.sh

    set -e # 遇到错误立即退出

    # ==================== 配置区 ====================
    TARGET_URL="${1:-http://localhost:3000}"
    ZAP_PORT=8080
    REPORT_DIR="./reports/dast"
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    SCAN_TIMEOUT=300 # 扫描超时(秒)
    # ================================================

    echo "╔══════════════════════════════════════════╗"
    echo "║ DAST 动态安全扫描工作流 ║"
    echo "╠══════════════════════════════════════════╣"
    echo "║ 目标: ${TARGET_URL}"
    echo "║ 时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "║ 报告: ${REPORT_DIR}/"
    echo "╚══════════════════════════════════════════╝"
    echo ""

    # 步骤1:创建报告目录
    mkdir -p ${REPORT_DIR}

    # 步骤2:检查目标是否可达
    echo "[1/6] 检查目标可达性…"
    if ! curl -s -o /dev/null -w "%{http_code}" ${TARGET_URL} | grep -q "200\\|301\\|302"; then
    echo "❌ 错误:目标 ${TARGET_URL} 不可达!"
    echo " 请确保应用已启动。"
    exit 1
    fi
    echo "✅ 目标可达"

    # 步骤3:启动 ZAP(如果未运行)
    echo "[2/6] 检查 ZAP 代理状态…"
    if ! curl -s http://localhost:${ZAP_PORT}/JSON/core/view/version/ > /dev/null 2>&1; then
    echo " 启动 ZAP 代理…"
    docker run -d –name zap-scan \\
    –network="host" \\
    ghcr.io/zaproxy/zaproxy:stable \\
    zap.sh -daemon -port ${ZAP_PORT} -config api.disablekey=true

    # 等待 ZAP 启动
    echo " 等待 ZAP 启动…"
    sleep 15
    fi
    echo "✅ ZAP 代理就绪"

    # 步骤4:执行爬虫(Spider)
    echo "[3/6] 执行网站爬虫…"
    SPIDER_ID=$(curl -s "http://localhost:${ZAP_PORT}/JSON/spider/action/scan/?url=${TARGET_URL}" | \\
    python3 -c "import sys,json; print(json.load(sys.stdin)['scan'])
    ")

    # 等待爬虫完成
    while true; do
    PROGRESS=$(curl -s "http://localhost:${ZAP_PORT}/JSON/spider/view/status/?scanId=${SPIDER_ID}" | \\
    python3 -c "import sys,json; print(json.load(sys.stdin)['status'])
    ")
    echo " 爬虫进度: ${PROGRESS}%"
    if [ "$PROGRESS" = "100" ]; then
    break
    fi
    sleep 5
    done
    echo "✅ 爬虫完成"

    # 步骤5:执行主动扫描
    echo "[4/6] 执行主动安全扫描(这可能需要几分钟)…"
    SCAN_ID=$(curl -s "http://localhost:${ZAP_PORT}/JSON/ascan/action/scan/?url=${TARGET_URL}" | \\
    python3 -c "import sys,json; print(json.load(sys.stdin)['scan'])
    ")

    # 等待扫描完成
    ELAPSED=0
    while true; do
    PROGRESS=$(curl -s "http://localhost:${ZAP_PORT}/JSON/ascan/view/status/?scanId=${SCAN_ID}" | \\
    python3 -c "import sys,json; print(json.load(sys.stdin)['status'])
    ")
    echo " 扫描进度: ${PROGRESS}%"
    if [ "$PROGRESS" = "100" ]; then
    break
    fi
    sleep 10
    ELAPSED=$((ELAPSED + 10))
    if [ $ELAPSED -ge $SCAN_TIMEOUT ]; then
    echo "⚠️ 扫描超时,使用当前结果"
    break
    fi
    done
    echo "✅ 主动扫描完成"

    # 步骤6:生成报告
    echo "[5/6] 生成安全报告…"

    # HTML 报告
    curl -s "http://localhost:${ZAP_PORT}/OTHER/core/other/htmlreport/" > \\
    "${REPORT_DIR}/zap-report-${TIMESTAMP}.html"

    # JSON 报告
    curl -s "http://localhost:${ZAP_PORT}/JSON/core/view/alerts/?baseurl=${TARGET_URL}" > \\
    "${REPORT_DIR}/zap-alerts-${TIMESTAMP}.json"

    # 解析并输出摘要
    echo "[6/6] 扫描结果摘要:"
    echo "─────────────────────────────────────────"
    python3 << 'EOF'
    import json, sys

    try:
    with open(f"${REPORT_DIR}/zap-alerts-${TIMESTAMP}.json") as f:
    data = json.load(f)

    alerts = data.get('alerts', [])

    # 按风险等级统计
    risk_count = {'High': 0, 'Medium': 0, 'Low': 0, 'Informational': 0}
    for alert in alerts:
    risk = alert.get('risk', 'Informational')
    risk_count[risk] = risk_count.get(risk, 0) + 1

    print(f" 🔴 高危: {risk_count['High']}")
    print(f" 🟠 中危: {risk_count['Medium']}")
    print(f" 🟡 低危: {risk_count['Low']}")
    print(f" 🔵 信息: {risk_count['Informational']}")
    print(f" ─────────────────")
    print(f" 总计: {len(alerts)} 个发现")

    # 输出高危详情
    if risk_count['High'] > 0:
    print(f"\\n ⚠️ 高危漏洞详情:")
    for alert in alerts:
    if alert.get('risk') == 'High':
    print(f" – {alert.get('name')}")
    print(f" URL: {alert.get('url')}")
    print(f" 描述: {alert.get('description', '')[:100]}…")
    print()
    except Exception as e:
    print(f" 解析报告时出错: {e}")
    EOF

    echo ""
    echo "══════════════════════════════════════════"
    echo "扫描完成!"
    echo "HTML 报告: ${REPORT_DIR}/zap-report-${TIMESTAMP}.html"
    echo "JSON 数据: ${REPORT_DIR}/zap-alerts-${TIMESTAMP}.json"
    echo "══════════════════════════════════════════"

    4.4 API 端点自动化探测

    对于 REST API 项目,可以使用专门的 API 扫描脚本:

    #!/usr/bin/env python3
    """
    api_security_scanner.py – API 端点安全扫描器
    在 Cursor 终端中运行: python3 scripts/api_security_scanner.py

    功能:
    – 自动发现 API 端点
    – 测试常见注入点
    – 检查安全头部
    – 验证认证机制
    """

    import requests
    import json
    import time
    from datetime import datetime
    from urllib.parse import urljoin, urlparse
    from typing import List, Dict, Optional

    class APISecurityScanner:
    """API 安全扫描器"""

    def __init__(self, base_url: str, auth_token: Optional[str] = None):
    self.base_url = base_url.rstrip('/')
    self.auth_token = auth_token
    self.findings: List[Dict] = []
    self.session = requests.Session()

    if auth_token:
    self.session.headers['Authorization'] = f'Bearer {auth_token}'

    def check_security_headers(self, url: str) > None:
    """检查安全响应头"""
    try:
    response = self.session.get(url, timeout=10)
    headers = response.headers

    # 应该存在的安全头
    required_headers = {
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': None, # DENY 或 SAMEORIGIN
    'X-XSS-Protection': None, # 1; mode=block
    'Strict-Transport-Security': None, # max-age=…
    'Content-Security-Policy': None,
    }

    # 不应该存在的头
    forbidden_headers = ['Server', 'X-Powered-By']

    for header, expected in required_headers.items():
    if header not in headers:
    self._add_finding(
    severity='MEDIUM',
    title=f'缺少安全头: {header}',
    url=url,
    description=f'响应中缺少 {header} 头',
    recommendation=f'添加 {header} 响应头'
    )

    for header in forbidden_headers:
    if header in headers:
    self._add_finding(
    severity='LOW',
    title=f'信息泄露头: {header}',
    url=url,
    description=f'响应包含 {header}: {headers[header]}',
    recommendation=f'移除 {header} 响应头'
    )

    except requests.RequestException as e:
    print(f" ⚠️ 请求失败: {url}{e}")

    def test_sql_injection(self, url: str, params: Dict) > None:
    """测试 SQL 注入"""
    sqli_payloads = [
    "' OR '1'='1",
    "1' OR '1'='1'–",
    "1; DROP TABLE users–",
    "' UNION SELECT NULL–",
    ]

    for param, value in params.items():
    for payload in sqli_payloads:
    test_params = params.copy()
    test_params[param] = payload

    try:
    response = self.session.get(url, params=test_params, timeout=10)

    # 检查响应中是否有 SQL 错误信息
    sql_errors = ['sql', 'syntax', 'mysql', 'postgresql', 'oracle',
    'sqlite', 'error', 'exception']
    response_lower = response.text.lower()

    if any(err in response_lower for err in sql_errors):
    self._add_finding(
    severity='HIGH',
    title='潜在 SQL 注入',
    url=url,
    description=f'参数 {param} 可能存在 SQL 注入',
    evidence=f'Payload: {payload}',
    recommendation='使用参数化查询'
    )
    except requests.RequestException:
    pass

    def test_xss(self, url: str, params: Dict) > None:
    """测试 XSS"""
    xss_payloads = [
    '<script>alert(1)</script>',
    '"><img src=x onerror=alert(1)>',
    "javascript:alert(1)",
    ]

    for param, value in params.items():
    for payload in xss_payloads:
    test_params = params.copy()
    test_params[param] = payload

    try:
    response = self.session.get(url, params=test_params, timeout=10)

    # 检查 payload 是否被原样反射
    if payload in response.text:
    self._add_finding(
    severity='HIGH',
    title='反射型 XSS',
    url=url,
    description=f'参数 {param} 存在 XSS 漏洞',
    evidence=f'Payload 被原样反射: {payload}',
    recommendation='对所有输出进行 HTML 编码'
    )
    except requests.RequestException:
    pass

    def _add_finding(self, severity: str, title: str, url: str,
    description: str, evidence: str = '',
    recommendation: str = '') > None:
    """添加发现"""
    finding = {
    'severity': severity,
    'title': title,
    'url': url,
    'description': description,
    'evidence': evidence,
    'recommendation': recommendation,
    'timestamp': datetime.now().isoformat()
    }
    self.findings.append(finding)

    # 实时输出
    icon = {'HIGH': '🔴', 'MEDIUM': '🟠', 'LOW': '🟡', 'INFO': '🔵'}
    print(f" {icon.get(severity, '⚪')} [{severity}] {title}")
    print(f" URL: {url}")

    def generate_report(self, output_path: str) > None:
    """生成 JSON 报告"""
    report = {
    'scan_info': {
    'target': self.base_url,
    'timestamp': datetime.now().isoformat(),
    'total_findings': len(self.findings)
    },
    'findings': self.findings
    }

    with open(output_path, 'w', encoding='utf-8') as f:
    json.dump(report, f, ensure_ascii=False, indent=2)

    print(f"\\n📄 报告已保存: {output_path}")

    # ==================== 使用示例 ====================
    if __name__ == '__main__':
    # 配置扫描目标
    TARGET = 'http://localhost:3000'

    print("╔══════════════════════════════════════╗")
    print("║ API 安全扫描器 v1.0 ║")
    print("╚══════════════════════════════════════╝")
    print(f"\\n目标: {TARGET}")
    print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n")

    scanner = APISecurityScanner(TARGET)

    # 1. 检查安全头
    print("[1/3] 检查安全响应头…")
    scanner.check_security_headers(TARGET)

    # 2. 测试 SQL 注入
    print("\\n[2/3] 测试 SQL 注入…")
    scanner.test_sql_injection(
    f"{TARGET}/api/users",
    {'id': '1', 'name': 'test'}
    )

    # 3. 测试 XSS
    print("\\n[3/3] 测试 XSS…")
    scanner.test_xss(
    f"{TARGET}/api/search",
    {'q': 'test', 'filter': 'all'}
    )

    # 生成报告
    scanner.generate_report('./reports/dast/api-scan-report.json')

    print("\\n✅ 扫描完成!")

    4.5 运行时漏洞报告解析

    创建一个报告解析脚本,将 DAST 结果转化为可操作的修复建议:

    #!/usr/bin/env python3
    """
    parse_dast_report.py – 解析 DAST 扫描报告并生成修复建议
    """

    import json
    import sys
    from pathlib import Path

    def parse_zap_report(report_path: str) > dict:
    """解析 ZAP JSON 报告"""
    with open(report_path, 'r') as f:
    data = json.load(f)

    alerts = data.get('alerts', [])

    # 按 CWE 分类
    cwe_mapping = {}
    for alert in alerts:
    cwe_id = alert.get('cweid', 'N/A')
    if cwe_id not in cwe_mapping:
    cwe_mapping[cwe_id] = []
    cwe_mapping[cwe_id].append({
    'name': alert.get('name'),
    'risk': alert.get('risk'),
    'url': alert.get('url'),
    'description': alert.get('description', '')[:200],
    'solution': alert.get('solution', '')[:200]
    })

    return cwe_mapping

    def generate_fix_suggestions(cwe_mapping: dict) > str:
    """生成修复建议"""
    suggestions = []
    suggestions.append("# DAST 扫描修复建议\\n")
    suggestions.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}\\n")

    for cwe_id, alerts in cwe_mapping.items():
    suggestions.append(f"\\n## CWE-{cwe_id}")
    for alert in alerts:
    suggestions.append(f"- **{alert['name']}** [{alert['risk']}]")
    suggestions.append(f" – URL: {alert['url']}")
    suggestions.append(f" – 修复: {alert['solution']}")

    return '\\n'.join(suggestions)

    if __name__ == '__main__':
    if len(sys.argv) < 2:
    print("用法: python3 parse_dast_report.py <report.json>")
    sys.exit(1)

    report_path = sys.argv[1]
    cwe_mapping = parse_zap_report(report_path)
    suggestions = generate_fix_suggestions(cwe_mapping)

    # 输出到文件
    output_path = './reports/dast/fix-suggestions.md'
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, 'w') as f:
    f.write(suggestions)

    print(f"修复建议已生成: {output_path}")


    五、编写提示词引导 AI 识别常见漏洞模式

    5.1 Cursor AI 安全审查提示词设计原则

    在 Cursor 中,AI 可以成为强大的安全审查助手。设计有效的安全提示词需要遵循以下原则:

    原则一:明确角色与目标

    你是一位资深应用安全工程师,专注于代码安全审查…

    原则二:提供具体上下文

    请审查以下 Node.js Express 代码,关注 OWASP Top 10 漏洞…

    原则三:指定输出格式

    请以表格形式输出:漏洞类型 | 严重等级 | 代码位置 | 修复建议

    原则四:限定范围避免幻觉

    只报告你确定的安全问题,不要猜测或过度推断…

    5.2 SQL 注入识别提示词模板

    在 Cursor 中按 Ctrl+L 打开 AI 对话,使用以下提示词:

    # 角色设定
    你是一位专注于数据库安全的资深安全工程师,精通 SQL 注入攻击与防御。

    # 任务
    请审查以下代码,识别所有潜在的 SQL 注入漏洞。

    # 审查要点
    1. 是否存在字符串拼接构造 SQL 语句
    2. 用户输入是否直接进入查询而未参数化
    3. ORM 使用是否存在 raw query 注入风险
    4. 存储过程调用是否安全
    5. 动态表名/列名是否有白名单验证

    # 输出格式
    对每个发现的问题,请提供:
    – 📍 位置:文件名 + 行号
    – 🔴 严重等级:Critical/High/Medium/Low
    – 📝 问题描述:具体说明漏洞原理
    – 💡 修复方案:给出具体修复代码
    – 📚 参考:相关 CWE 编号

    # 注意事项
    – 只报告确定的问题,不确定的标注为"需要人工确认"
    – 如果使用了参数化查询,不要误报
    – 考虑框架自带的防护机制(如 Sequelize 的参数化)

    # 待审查代码
    [在此粘贴代码或选中代码后发送]

    5.3 XSS 漏洞检测提示词模板

    # 角色设定
    你是一位 Web 前端安全专家,精通各类 XSS 攻击向量与防御策略。

    # 任务
    审查以下前端/后端代码,识别所有 XSS(跨站脚本)漏洞。

    # 审查维度
    1. **反射型 XSS**:用户输入是否未经编码直接输出到 HTML
    2. **存储型 XSS**:数据库存储的内容在展示时是否转义
    3. **DOM 型 XSS**:前端 JS 是否直接使用 innerHTML/document.write
    4. **事件处理**:onerror/onclick 等事件属性是否可注入
    5. **URL 注入**:href/src 属性是否可被 javascript: 协议利用

    # 特别关注
    – React 中的 dangerouslySetInnerHTML
    – Vue 中的 v-html 指令
    – Angular 中的 [innerHTML] 绑定
    – 模板引擎的 raw/unescaped 输出(如 {{{ }}} in Handlebars)
    – URL 参数直接插入 DOM

    # 输出要求
    对每个发现:
    1. 漏洞类型(反射型/存储型/DOM型)
    2. 攻击向量示例
    3. 影响范围评估
    4. 具体修复代码(包含转义函数推荐)
    5. 防御纵深建议(CSP 配置等)

    5.4 认证与授权缺陷检测

    # 角色设定
    你是一位身份认证与访问控制安全专家。

    # 任务
    审查以下代码中的认证(Authentication)和授权(Authorization)实现。

    # 检查清单
    ## 认证相关
    – [ ] 密码是否使用 bcrypt/argon2 哈希(非 MD5/SHA1)
    – [ ] 是否有暴力破解防护(速率限制/账户锁定)
    – [ ] Session/Token 是否设置合理过期时间
    – [ ] 登录失败是否返回统一错误信息(不暴露用户是否存在)
    – [ ] 密码重置流程是否安全

    ## 授权相关
    – [ ] 是否存在越权访问(水平/垂直)
    – [ ] API 端点是否都有权限校验
    – [ ] 是否存在 IDOR(不安全的直接对象引用)
    – [ ] JWT 是否验证签名和过期时间
    – [ ] 敏感操作是否有二次确认

    ## 会话管理
    – [ ] Cookie 是否设置 HttpOnly/Secure/SameSite
    – [ ] 是否有 CSRF 防护
    – [ ] 登出是否真正销毁会话

    # 输出
    按 OWASP 分类输出,每个问题附带修复代码示例。

    5.5 敏感信息泄露检测

    # 角色设定
    你是一位数据安全与隐私保护专家。

    # 任务
    审查代码中是否存在敏感信息泄露风险。

    # 检查项
    1. **硬编码凭证**:密码、API Key、Token 是否硬编码在源码中
    2. **日志泄露**:日志中是否记录敏感数据(密码、身份证号、银行卡号)
    3. **错误信息**:异常处理是否暴露系统内部信息(堆栈、路径、版本)
    4. **前端泄露**:源代码/注释中是否包含内部 URL、测试账号
    5. **配置泄露**:.env 文件是否被忽略、调试模式是否关闭
    6. **响应泄露**:API 响应是否返回多余敏感字段
    7. **版本信息**:是否暴露框架/库版本号

    # 正则模式参考
    – API Key: /[A-Za-z0-9_\\-]{32,}/
    – AWS Key: /AKIA[0-9A-Z]{16}/
    – 私钥: /—–BEGIN (RSA|EC) PRIVATE KEY—–/
    – 密码字段: /password\\s*[:=]\\s*['"][^'"]+['"]/

    # 输出
    对每个发现标注:泄露类型、影响等级、修复方案、预防措施

    5.6 提示词链组合策略

    将多个安全提示词组合成完整的审查流程,形成系统化的安全分析链条:

    完整安全审查提示词链(分步执行):

    # 完整安全审查提示词链(在 Cursor 中按步骤执行)

    ## 第一步:快速扫描(Ctrl+L 打开 AI 对话)
    "请快速扫描这段代码,列出所有潜在的安全问题,按严重性排序。
    不需要详细解释,只需要列出问题类型和位置。格式如下:
    1. [Critical] 文件:行号 – 问题简述
    2. [High] 文件:行号 – 问题简述
    …"

    ## 第二步:深入分析(针对第一步发现的问题)
    "针对你发现的第 N 个问题 [具体问题],请详细分析:
    1. 攻击者如何利用这个漏洞?给出具体的攻击步骤和 payload
    2. 这个漏洞的影响范围有多大?
    3. 请给出完整的修复代码
    4. 如何验证修复是否有效?"

    ## 第三步:防御加固
    "在修复上述问题的基础上,请建议额外的防御措施:
    1. 需要添加哪些安全头?
    2. 是否需要添加速率限制?
    3. 日志记录应如何调整?
    4. 是否需要添加额外的输入验证?"

    ## 第四步:回归验证
    "请审查修复后的代码,确认:
    1. 原始漏洞是否已完全修复?
    2. 修复是否引入了新的安全问题?
    3. 修复是否影响了正常业务逻辑?
    4. 是否有遗漏的攻击向量?"

    提示词链的 .cursorrules 配置:

    将安全审查角色固化到项目配置中,创建项目根目录下的 .cursorrules 文件:

    <!– .cursorrules – 项目级 AI 安全审查配置 –>

    # 项目安全审查规则

    ## 角色定义
    你是一位资深应用安全工程师,在辅助编码时始终关注安全性。

    ## 编码时的安全准则
    1. 所有用户输入必须验证和净化
    2. 数据库操作必须使用参数化查询
    3. 禁止使用 eval()、exec() 等危险函数
    4. 敏感数据必须加密存储
    5. API 端点必须有认证和授权检查
    6. 错误处理不得暴露内部信息
    7. 日志中不得记录敏感数据

    ## 代码生成时的安全检查
    当生成代码时,自动考虑:
    – 输入验证是否完整
    – 是否存在注入风险
    – 错误处理是否安全
    – 是否有适当的访问控制
    – 依赖是否安全

    ## 审查输出格式
    发现安全问题时,使用以下格式:
    ⚠️ [严重等级] 问题描述
    📍 位置:文件:行号
    💡 修复建议:具体方案
    📚 参考:CWE/OWASP 编号

    ## 技术栈特定规则
    – Express.js: 必须使用 helmet(),所有路由需要输入验证
    – React: 禁止 dangerouslySetInnerHTML,使用 DOMPurify
    – Python: 使用参数化查询,禁止 os.system()
    – 数据库: 所有查询必须参数化,禁止字符串拼接

    组合提示词实战示例:

    # 场景:审查一个用户注册接口

    ## 提示词 1(初始扫描):
    "审查以下 Express.js 用户注册代码,检查 OWASP Top 10 中的所有相关漏洞:

    ```javascript
    app.post('/register', async (req, res) => {
    const { username, email, password } = req.body;
    const query = `INSERT INTO users (username, email, password)
    VALUES ('${username}', '${email}', '${password}')`;
    await db.execute(query);
    res.send('注册成功');
    });
    ```"

    ## 期望 AI 输出:
    1. [Critical] SQL 注入 – 字符串拼接构造 SQL
    2. [Critical] 明文密码存储 – 未使用哈希
    3. [High] 缺少输入验证 – 未验证 email 格式
    4. [Medium] 缺少速率限制 – 可被暴力注册
    5. [Low] 响应信息过于简单 – 缺少适当状态码

    ## 提示词 2(深入修复):
    "请给出完整的修复代码,包含:
    1. 参数化查询
    2. bcrypt 密码哈希
    3. 输入验证(使用 express-validator)
    4. 速率限制
    5. 适当的错误处理"


    六、实战演练:从代码编写到风险修复闭环

    6.1 项目初始化与场景设定

    本节通过一个完整的实战项目,演示从代码编写到漏洞发现、修复、验证的完整闭环。

    场景设定:构建一个简单的博客系统 API,包含用户管理、文章发布、评论功能。

    项目初始化:

    # 创建项目目录
    mkdir secure-blog-api && cd secure-blog-api

    # 初始化 Node.js 项目
    npm init -y

    # 安装依赖
    npm install express mysql2 bcrypt jsonwebtoken helmet cors express-validator
    npm install –save-dev nodemon eslint eslint-plugin-security semgrep

    # 创建项目结构
    mkdir -p src/{routes,controllers,middleware,models,utils}
    mkdir -p tests
    mkdir -p semgrep-rules/{injection,xss,auth}
    mkdir -p scripts
    mkdir -p reports

    项目结构:

    secure-blog-api/
    ├── src/
    │ ├── app.js # 应用入口
    │ ├── routes/
    │ │ ├── auth.js # 认证路由
    │ │ ├── posts.js # 文章路由
    │ │ └── comments.js # 评论路由
    │ ├── controllers/
    │ │ ├── authController.js
    │ │ ├── postController.js
    │ │ └── commentController.js
    │ ├── middleware/
    │ │ ├── auth.js # 认证中间件
    │ │ ├── validate.js # 输入验证中间件
    │ │ └── errorHandler.js # 错误处理中间件
    │ ├── models/
    │ │ └── db.js # 数据库连接
    │ └── utils/
    │ ├── sanitize.js # 输入净化工具
    │ └── logger.js # 安全日志工具
    ├── semgrep-rules/ # 自定义安全规则
    ├── scripts/ # 自动化脚本
    ├── tests/ # 测试文件
    ├── reports/ # 扫描报告
    ├── .eslintrc.json
    ├── .cursorrules
    ├── .semgrepignore
    └── package.json

    6.2 编写含漏洞的示例代码

    为了演示完整的安全工作流,我们先编写一段故意包含漏洞的代码(仅用于教学演示):

    // src/controllers/authController.js
    // ⚠️ 警告:此文件包含多个安全漏洞,仅用于教学演示!
    // 实际项目中绝对不要这样编写代码!

    const db = require('../models/db');
    const jwt = require('jsonwebtoken');

    // 漏洞1:硬编码密钥
    const SECRET_KEY = "my-super-secret-key-12345";

    // 漏洞2:SQL 注入 – 字符串拼接
    exports.login = async (req, res) => {
    const { username, password } = req.body;

    // 危险:直接拼接用户输入到 SQL 语句
    const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;

    try {
    const [rows] = await db.execute(query);

    if (rows.length > 0) {
    // 漏洞3:JWT 未设置过期时间
    const token = jwt.sign({ userId: rows[0].id, role: rows[0].role }, SECRET_KEY);

    // 漏洞4:返回过多用户信息
    res.json({
    success: true,
    token: token,
    user: rows[0] // 包含密码哈希等敏感信息
    });
    } else {
    // 漏洞5:错误信息暴露用户是否存在
    res.status(401).json({ error: '用户名不存在' });
    }
    } catch (error) {
    // 漏洞6:暴露详细错误信息
    res.status(500).json({ error: error.message, stack: error.stack });
    }
    };

    // 漏洞7:缺少输入验证
    exports.register = async (req, res) => {
    const { username, email, password } = req.body;

    // 漏洞8:明文存储密码
    const query = `INSERT INTO users (username, email, password) VALUES ('${username}', '${email}', '${password}')`;

    try {
    await db.execute(query);
    res.json({ success: true, message: '注册成功' });
    } catch (error) {
    res.status(500).json({ error: error.message });
    }
    };

    // src/controllers/postController.js
    // ⚠️ 同样包含安全漏洞的教学代码

    const db = require('../models/db');

    // 漏洞9:存储型 XSS – 未净化用户输入
    exports.createPost = async (req, res) => {
    const { title, content, authorId } = req.body;

    // 直接将用户输入存入数据库,未做任何净化
    const query = `INSERT INTO posts (title, content, author_id) VALUES (?, ?, ?)`;

    try {
    const [result] = await db.execute(query, [title, content, authorId]);
    res.json({ success: true, postId: result.insertId });
    } catch (error) {
    res.status(500).json({ error: error.message });
    }
    };

    // 漏洞10:IDOR – 未验证资源所有权
    exports.getPost = async (req, res) => {
    const postId = req.params.id;

    // 任何人都可以访问任何文章,未验证权限
    const query = `SELECT * FROM posts WHERE id = ${postId}`;

    try {
    const [rows] = await db.execute(query);
    if (rows.length > 0) {
    // 漏洞11:直接输出未转义的 HTML 内容
    res.json({ success: true, post: rows[0] });
    } else {
    res.status(404).json({ error: '文章不存在' });
    }
    } catch (error) {
    res.status(500).json({ error: error.message });
    }
    };

    // 漏洞12:批量赋值 – 未限制可更新字段
    exports.updatePost = async (req, res) => {
    const postId = req.params.id;
    const updates = req.body; // 接受所有字段

    // 构造动态 UPDATE 语句
    const fields = Object.keys(updates).map(key => `${key} = '${updates[key]}'`).join(', ');
    const query = `UPDATE posts SET ${fields} WHERE id = ${postId}`;

    try {
    await db.execute(query);
    res.json({ success: true });
    } catch (error) {
    res.status(500).json({ error: error.message });
    }
    };

    // 漏洞13:命令注入
    exports.exportPost = async (req, res) => {
    const { format, filename } = req.query;
    const { exec } = require('child_process');

    // 危险:用户输入直接进入命令
    exec(`pandoc posts/${filename} -o exports/${filename}.${format}`, (error, stdout) => {
    if (error) {
    res.status(500).json({ error: error.message });
    } else {
    res.download(`exports/${filename}.${format}`);
    }
    });
    };

    6.3 SAST 扫描触发与结果分析

    在 Cursor 中触发扫描:

    方法一:保存文件时自动触发(已配置 Semgrep 扩展)

    方法二:手动在终端运行:

    # 在 Cursor 终端中执行
    # 扫描整个 src 目录
    semgrep –config auto –config ./semgrep-rules/ ./src –json > reports/semgrep-scan.json

    # 查看人类可读的结果
    semgrep –config auto –config ./semgrep-rules/ ./src

    预期扫描结果:

    ┌─────────────────────────────────────────────────────────────────┐
    │ Semgrep 扫描结果 – secure-blog-api │
    ├─────────────────────────────────────────────────────────────────┤
    │ │
    │ 🔴 ERROR: sql-injection-string-concat │
    │ src/controllers/authController.js:14 │
    │ > const query = `SELECT * FROM users WHERE username = … │
    │ │
    │ 🔴 ERROR: sql-injection-string-concat │
    │ src/controllers/authController.js:30 │
    │ > const query = `INSERT INTO users … │
    │ │
    │ 🔴 ERROR: hardcoded-secret │
    │ src/controllers/authController.js:7 │
    │ > const SECRET_KEY = "my-super-secret-key-12345"; │
    │ │
    │ 🔴 ERROR: plaintext-password-storage │
    │ src/controllers/authController.js:30 │
    │ > INSERT INTO users … password … │
    │ │
    │ 🔴 ERROR: command-injection │
    │ src/controllers/postController.js:62 │
    │ > exec(`pandoc posts/${filename}…`) │
    │ │
    │ 🟠 WARNING: missing-input-validation │
    │ src/controllers/authController.js:27 │
    │ > exports.register = async (req, res) => { │
    │ │
    │ 🟠 WARNING: excessive-data-exposure │
    │ src/controllers/authController.js:19 │
    │ > user: rows[0] │
    │ │
    │ 🟡 INFO: missing-rate-limiting │
    │ src/controllers/authController.js:12 │
    │ > exports.login = async (req, res) => { │
    │ │
    │ 总计: 5 ERROR, 2 WARNING, 1 INFO │
    └─────────────────────────────────────────────────────────────────┘

    6.4 AI 辅助漏洞解读

    在 Cursor 中选中问题代码,按 Ctrl+L 打开 AI 对话:

    提示词:

    请解释这个 SQL 注入漏洞的攻击原理,并给出具体的攻击 payload 示例。
    然后提供完整的修复代码。

    AI 预期回复(示例):

    ## 漏洞分析:SQL 注入

    ### 攻击原理
    当前代码使用模板字符串直接拼接用户输入:
    ```javascript
    const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;

    攻击者可以输入以下用户名:

    admin' —

    这使得 SQL 变为:

    SELECT * FROM users WHERE username = 'admin' –' AND password = ''

    — 注释掉了密码检查,攻击者无需密码即可登录。

    • 更危险的 Payload

      ' UNION SELECT id, username, password, 'admin' FROM users —

      可以获取所有用户数据。

    • 修复代码 [完整修复代码见下一节]

    6.5 修复代码编写与验证

    修复后的 authController.js:

    // src/controllers/authController.js
    // ✅ 修复后的安全版本

    const db = require('../models/db');
    const jwt = require('jsonwebtoken');
    const bcrypt = require('bcrypt');
    const { body, validationResult } = require('express-validator');
    const { sanitizeInput } = require('../utils/sanitize');
    const { logger } = require('../utils/logger');

    // ✅ 修复1:密钥从环境变量读取
    const SECRET_KEY = process.env.JWT_SECRET_KEY;
    const SALT_ROUNDS = 12; // bcrypt 盐值轮数

    // 输入验证规则
    const loginValidation = [
    body('username')
    .trim()
    .isLength({ min: 3, max: 50 })
    .withMessage('用户名长度必须在3-50个字符之间')
    .matches(/^[a-zA-Z0-9_]+$/)
    .withMessage('用户名只能包含字母、数字和下划线'),
    body('password')
    .isLength({ min: 8, max: 128 })
    .withMessage('密码长度必须在8-128个字符之间')
    ];

    const registerValidation = [
    body('username')
    .trim()
    .isLength({ min: 3, max: 50 })
    .matches(/^[a-zA-Z0-9_]+$/),
    body('email')
    .trim()
    .isEmail()
    .withMessage('请输入有效的邮箱地址')
    .normalizeEmail(),
    body('password')
    .isLength({ min: 8, max: 128 })
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&]).+$/)
    .withMessage('密码必须包含大小写字母、数字和特殊字符')
    ];

    /**
    * 用户登录
    * ✅ 修复2:使用参数化查询防止 SQL 注入
    * ✅ 修复3:使用 bcrypt 验证密码哈希
    * ✅ 修复4:JWT 设置过期时间
    * ✅ 修复5:统一错误信息,不暴露用户是否存在
    * ✅ 修复6:安全的错误处理
    */

    exports.login = [
    loginValidation,
    async (req, res) => {
    // 检查验证结果
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(400).json({
    success: false,
    errors: errors.array().map(e => ({
    field: e.path,
    message: e.msg
    }))
    });
    }

    const { username, password } = req.body;

    try {
    // ✅ 参数化查询 – 防止 SQL 注入
    const query = 'SELECT id, username, email, password_hash, role, created_at FROM users WHERE username = ?';
    const [rows] = await db.execute(query, [username]);

    if (rows.length === 0) {
    // ✅ 统一错误信息 – 不暴露用户是否存在
    logger.warn(`登录失败 – 用户不存在: ${sanitizeInput(username)}`);
    return res.status(401).json({
    success: false,
    error: '用户名或密码错误' // 统一错误信息
    });
    }

    const user = rows[0];

    // ✅ 使用 bcrypt 比较密码哈希
    const isPasswordValid = await bcrypt.compare(password, user.password_hash);

    if (!isPasswordValid) {
    logger.warn(`登录失败 – 密码错误: ${sanitizeInput(username)}`);
    return res.status(401).json({
    success: false,
    error: '用户名或密码错误' // 与上面相同的错误信息
    });
    }

    // ✅ JWT 设置过期时间(2小时)
    const token = jwt.sign(
    {
    userId: user.id,
    role: user.role
    // 不包含敏感信息
    },
    SECRET_KEY,
    {
    expiresIn: '2h', // 2小时过期
    algorithm: 'HS256'
    }
    );

    // ✅ 只返回必要的用户信息
    res.json({
    success: true,
    token: token,
    user: {
    id: user.id,
    username: user.username,
    email: user.email,
    role: user.role
    // 不返回 password_hash 等敏感字段
    }
    });

    logger.info(`用户登录成功: ${user.id}`);

    } catch (error) {
    // ✅ 安全的错误处理 – 不暴露内部信息
    logger.error(`登录处理错误: ${error.message}`);
    res.status(500).json({
    success: false,
    error: '服务器内部错误,请稍后重试'
    });
    }
    }
    ];

    /**
    * 用户注册
    * ✅ 修复7:完整的输入验证
    * ✅ 修复8:bcrypt 哈希密码
    */

    exports.register = [
    registerValidation,
    async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(400).json({
    success: false,
    errors: errors.array().map(e => ({
    field: e.path,
    message: e.msg
    }))
    });
    }

    const { username, email, password } = req.body;

    try {
    // 检查用户名是否已存在
    const [existing] = await db.execute(
    'SELECT id FROM users WHERE username = ? OR email = ?',
    [username, email]
    );

    if (existing.length > 0) {
    return res.status(409).json({
    success: false,
    error: '用户名或邮箱已被注册'
    });
    }

    // ✅ 使用 bcrypt 哈希密码
    const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);

    // ✅ 参数化查询
    await db.execute(
    'INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)',
    [username, email, passwordHash]
    );

    logger.info(`新用户注册: ${username}`);

    res.status(201).json({
    success: true,
    message: '注册成功'
    });

    } catch (error) {
    logger.error(`注册处理错误: ${error.message}`);
    res.status(500).json({
    success: false,
    error: '注册失败,请稍后重试'
    });
    }
    }
    ];

    修复后的 postController.js:

    // src/controllers/postController.js
    // ✅ 修复后的安全版本

    const db = require('../models/db');
    const DOMPurify = require('isomorphic-dompurify');
    const { body, param, validationResult } = require('express-validator');
    const { sanitizeInput } = require('../utils/sanitize');
    const { logger } = require('../utils/logger');
    const path = require('path');

    // 允许更新的字段白名单
    const ALLOWED_UPDATE_FIELDS = ['title', 'content', 'status'];

    /**
    * 创建文章
    * ✅ 修复9:输入净化防止 XSS
    */

    exports.createPost = [
    body('title')
    .trim()
    .isLength({ min: 1, max: 200 })
    .withMessage('标题长度必须在1-200个字符之间'),
    body('content')
    .trim()
    .isLength({ min: 1, max: 50000 })
    .withMessage('内容长度必须在1-50000个字符之间'),
    async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(400).json({
    success: false,
    errors: errors.array()
    });
    }

    const { title, content } = req.body;
    // 从 JWT token 中获取用户ID(而非请求体)
    const authorId = req.user.userId;

    try {
    // ✅ 净化 HTML 内容防止存储型 XSS
    const sanitizedContent = DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'code', 'pre'],
    ALLOWED_ATTR: ['href', 'target', 'rel'],
    // 禁止 javascript: 协议
    ALLOW_DATA_ATTR: false
    });

    // ✅ 参数化查询
    const [result] = await db.execute(
    'INSERT INTO posts (title, content, author_id, created_at) VALUES (?, ?, ?, NOW())',
    [sanitizeInput(title), sanitizedContent, authorId]
    );

    logger.info(`文章创建成功: ID=${result.insertId}, 作者=${authorId}`);

    res.status(201).json({
    success: true,
    postId: result.insertId
    });

    } catch (error) {
    logger.error(`创建文章失败: ${error.message}`);
    res.status(500).json({
    success: false,
    error: '创建文章失败'
    });
    }
    }
    ];

    /**
    * 获取文章
    * ✅ 修复10:参数化查询 + 输入验证
    */

    exports.getPost = [
    param('id')
    .isInt({ min: 1 })
    .withMessage('无效的文章ID'),
    async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(400).json({
    success: false,
    error: '无效的文章ID'
    });
    }

    const postId = parseInt(req.params.id, 10);

    try {
    // ✅ 参数化查询
    const [rows] = await db.execute(
    'SELECT id, title, content, author_id, created_at, updated_at FROM posts WHERE id = ? AND status = ?',
    [postId, 'published']
    );

    if (rows.length === 0) {
    return res.status(404).json({
    success: false,
    error: '文章不存在'
    });
    }

    res.json({
    success: true,
    post: rows[0]
    });

    } catch (error) {
    logger.error(`获取文章失败: ${error.message}`);
    res.status(500).json({
    success: false,
    error: '获取文章失败'
    });
    }
    }
    ];

    /**
    * 更新文章
    * ✅ 修复11:字段白名单 + 所有权验证
    * ✅ 修复12:参数化动态查询
    */

    exports.updatePost = [
    param('id').isInt({ min: 1 }),
    body('title').optional().trim().isLength({ min: 1, max: 200 }),
    body('content').optional().trim().isLength({ min: 1, max: 50000 }),
    body('status').optional().isIn(['draft', 'published', 'archived']),
    async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(400).json({
    success: false,
    errors: errors.array()
    });
    }

    const postId = parseInt(req.params.id, 10);
    const userId = req.user.userId;

    try {
    // ✅ 验证资源所有权
    const [ownership] = await db.execute(
    'SELECT id FROM posts WHERE id = ? AND author_id = ?',
    [postId, userId]
    );

    if (ownership.length === 0) {
    return res.status(403).json({
    success: false,
    error: '无权修改此文章'
    });
    }

    // ✅ 字段白名单过滤
    const updates = {};
    for (const [key, value] of Object.entries(req.body)) {
    if (ALLOWED_UPDATE_FIELDS.includes(key)) {
    updates[key] = key === 'content' ? DOMPurify.sanitize(value) : sanitizeInput(value);
    }
    }

    if (Object.keys(updates).length === 0) {
    return res.status(400).json({
    success: false,
    error: '没有有效的更新字段'
    });
    }

    // ✅ 安全的动态查询构建
    const setClauses = Object.keys(updates).map(key => `${key} = ?`);
    const values = Object.values(updates);
    values.push(postId);

    await db.execute(
    `UPDATE posts SET ${setClauses.join(', ')}, updated_at = NOW() WHERE id = ?`,
    values
    );

    logger.info(`文章更新: ID=${postId}, 用户=${userId}`);

    res.json({ success: true });

    } catch (error) {
    logger.error(`更新文章失败: ${error.message}`);
    res.status(500).json({
    success: false,
    error: '更新文章失败'
    });
    }
    }
    ];

    /**
    * 导出文章
    * ✅ 修复13:移除命令注入风险,使用安全的文件操作
    */

    exports.exportPost = [
    param('id').isInt({ min: 1 }),
    async (req, res) => {
    const postId = parseInt(req.params.id, 10);
    const format = req.query.format;

    // ✅ 白名单验证导出格式
    const ALLOWED_FORMATS = ['pdf', 'html', 'markdown'];
    if (!ALLOWED_FORMATS.includes(format)) {
    return res.status(400).json({
    success: false,
    error: '不支持的导出格式'
    });
    }

    try {
    const [rows] = await db.execute(
    'SELECT title, content FROM posts WHERE id = ?',
    [postId]
    );

    if (rows.length === 0) {
    return res.status(404).json({
    success: false,
    error: '文章不存在'
    });
    }

    // ✅ 使用安全的文件生成方式,而非 shell 命令
    const safeFilename = `post_${postId}_${Date.now()}.${format}`;
    const outputPath = path.join(__dirname, '../../exports', safeFilename);

    // 使用 Node.js 库生成文件(而非 exec 命令)
    // 具体实现取决于格式…

    res.json({
    success: true,
    downloadUrl: `/exports/${safeFilename}`
    });

    } catch (error) {
    logger.error(`导出文章失败: ${error.message}`);
    res.status(500).json({
    success: false,
    error: '导出失败'
    });
    }
    }
    ];

    6.6 回归扫描确认修复有效

    修复完成后,重新运行安全扫描验证:

    # 重新扫描
    semgrep –config auto –config ./semgrep-rules/ ./src

    # 期望输出:
    # 无 ERROR 级别发现
    # 可能仍有少量 INFO 级别建议

    # 运行 ESLint 安全检查
    npx eslint –ext .js ./src –rule 'security/detect-eval-with-expression: error'

    # 运行 Bandit(如果有 Python 代码)
    # bandit -r ./src

    使用 Cursor AI 进行修复验证:

    # 在 Cursor 中按 Ctrl+L,输入:
    "请对比修复前后的代码,确认以下问题已解决:
    1. SQL 注入 → 参数化查询 ✓
    2. 明文密码 → bcrypt 哈希 ✓
    3. 硬编码密钥 → 环境变量 ✓
    4. 命令注入 → 安全文件操作 ✓
    5. XSS → DOMPurify 净化 ✓

    是否还有遗漏的安全问题?"


    七、误报率优化策略与审计结果精准度提升

    7.1 误报产生的常见原因

    误报(False Positive)是 SAST 工具面临的最大挑战之一。常见原因包括:

    误报原因示例占比
    缺乏上下文理解 测试代码中的"危险"操作 ~30%
    规则过于宽泛 匹配所有字符串拼接 ~25%
    框架特性未识别 ORM 已自动参数化 ~20%
    安全控制已存在 上层已做输入验证 ~15%
    死代码/不可达路径 已废弃但未删除的代码 ~10%

    7.2 规则精细化调优

    策略一:添加上下文条件

    # 优化前:过于宽泛的规则
    rules:
    id: detectstringconcatsql
    pattern: |
    $VAR = "…" + $INPUT + "…"

    message: "可能的 SQL 注入"
    languages: [javascript]
    severity: ERROR

    # 优化后:添加上下文限定
    rules:
    id: detectstringconcatsql
    patterns:
    pattern: |
    $VAR = "…" + $INPUT + "…"

    # 限定变量名包含 SQL 关键词
    metavariable-regex:
    metavariable: $VAR
    regex: (?i).*(sql|query|stmt|select|insert|update|delete).*
    # 排除已知的安全模式
    pattern-not: |
    $VAR = "…" + parseInt($INPUT) + "…"

    pattern-not: |
    $VAR = "…" + Number($INPUT) + "…"

    # 排除测试文件
    pattern-not-inside: |
    describe(…, () => { … })

    pattern-not-inside: |
    test(…, () => { … })

    message: "可能的 SQL 注入:检测到 SQL 查询字符串拼接"
    languages: [javascript]
    severity: ERROR
    metadata:
    confidence: HIGH # 提高置信度标注

    策略二:使用 taint 模式替代简单模式匹配

    # 使用污点追踪,只在数据确实从不可信源流向危险操作时报警
    rules:
    id: taintsqlinjectionprecise
    mode: taint
    pattern-sources:
    # 明确定义不可信数据来源
    pattern: req.query.$PARAM
    pattern: req.body.$PARAM
    pattern: req.params.$PARAM
    pattern: req.headers.$PARAM
    pattern-sinks:
    # 明确定义危险操作
    pattern: db.query($SQL)
    pattern: db.execute($SQL)
    pattern: connection.query($SQL)
    pattern-sanitizers:
    # 定义净化操作(经过这些处理的数据是安全的)
    pattern: parseInt($X)
    pattern: Number($X)
    pattern: $X.replace()
    pattern: sanitize($X)
    pattern: escape($X)
    # 参数化查询本身就是安全的
    pattern: db.query($SQL, [$PARAMS])
    pattern: db.execute($SQL, [$PARAMS])
    message: >
    用户输入 $SOURCE 未经净化直接流入 SQL 查询。
    请使用参数化查询。

    languages: [javascript, typescript]
    severity: ERROR
    metadata:
    confidence: VERY_HIGH
    cwe: "CWE-89"

    7.3 上下文感知过滤

    利用 Semgrep 的 pattern-not-inside 排除安全上下文:

    rules:
    id: detectexecusage
    patterns:
    pattern: exec($CMD)
    # 排除:硬编码的命令字符串(无用户输入)
    pattern-not: exec("ls la")
    pattern-not: exec("npm test")
    pattern-not: exec("git status")
    # 排除:在测试文件中
    pattern-not-inside: |
    // @test

    pattern-not-inside: |
    describe(…)

    # 排除:已有输入验证
    pattern-not-inside: |
    if (whitelist.includes($CMD)) {

    }

    message: "exec() 调用需要确认命令来源安全"
    languages: [javascript]
    severity: WARNING # 降级为警告,需要人工确认

    7.4 白名单与抑制机制

    项目级白名单配置 (.semgrepignore):

    # .semgrepignore – Semgrep 忽略文件
    # 语法与 .gitignore 相同

    # 忽略第三方依赖
    node_modules/
    vendor/
    bower_components/

    # 忽略构建产物
    dist/
    build/
    out/
    *.min.js
    *.bundle.js

    # 忽略测试文件(可选,根据团队策略)
    # tests/
    # *.test.js
    # *.spec.js

    # 忽略生成的代码
    generated/
    migrations/
    *.generated.*

    # 忽略特定规则对特定文件
    # 格式: 规则ID 文件路径
    # no-eval tests/helpers/eval-helper.js

    代码内抑制注释:

    // 当确认某个警告是误报时,可以使用抑制注释

    // nosemgrep: detect-eval-usage
    // 原因:此处 eval 仅用于解析受信任的内部配置,输入来源已验证
    const config = eval(trustedInternalConfig);

    // 或者使用行内抑制
    const result = eval(expression); // nosemgrep: detect-eval-usage

    团队级白名单管理(创建 security-exceptions.yaml):

    # security-exceptions.yaml – 安全例外管理
    # 所有例外必须记录原因和审批人

    exceptions:
    rule_id: detectevalusage
    file: src/utils/templateengine.js
    line: 42
    reason: "内部模板引擎需要,输入已通过白名单验证"
    approved_by: "security-team@company.com"
    approved_date: "2026-07-15"
    review_date: "2026-10-15" # 3个月后需重新评审
    ticket: "SEC-2026-042"

    rule_id: hardcodedsecret
    file: tests/fixtures/testdata.js
    line: 15
    reason: "测试用假数据,非真实凭证"
    approved_by: "dev-lead@company.com"
    approved_date: "2026-07-20"
    review_date: null # 测试数据无需重新评审

    7.5 持续反馈循环建立

    #!/bin/bash
    # scripts/feedback-loop.sh – 误报反馈收集脚本
    # 定期运行,收集团队对扫描结果的反馈

    REPORT_FILE="./reports/semgrep-scan.json"
    FEEDBACK_FILE="./reports/feedback-log.json"

    echo "=== 安全扫描反馈收集 ==="
    echo ""

    # 解析当前扫描结果
    FINDINGS=$(cat ${REPORT_FILE} | python3 -c "
    import json, sys
    data = json.load(sys.stdin)
    results = data.get('results', [])
    print(f'当前发现: {len(results)} 个问题')

    for r in results:
    print(f' [{r[\\"extra\\"][\\"severity\\"]}] {r[\\"check_id\\"]} @ {r[\\"path\\"]}:{r[\\"start\\"][\\"line\\"]}')
    ")

    echo "${FINDINGS}"
    echo "
    "

    # 记录到反馈日志
    python3 << 'EOF'
    import json
    from datetime import datetime

    feedback_entry = {
    "timestamp": datetime.now().isoformat(),
    "total_findings": 0,
    "confirmed_true_positives": 0,
    "confirmed_false_positives": 0,
    "needs_review": 0,
    "notes": ""
    }

    # 这里可以集成到团队的反馈系统
    print("请将此数据同步到团队安全仪表板")
    EOF


    八、团队协作规范与安全检查流程标准化

    8.1 安全编码规范文档化

    创建团队安全编码规范文档 SECURITY_GUIDELINES.md:

    # 团队安全编码规范 v2.0

    ## 1. 总则
    – 所有代码必须通过 SAST 扫描后方可合并
    – 安全漏洞修复优先级高于新功能开发
    – 每位开发者每季度至少完成一次安全培训

    ## 2. 输入处理规范
    ### 2.1 所有用户输入必须验证
    – 类型检查
    – 长度限制
    – 格式验证(正则)
    – 范围检查

    ### 2.2 输出编码
    – HTML 上下文:HTML 实体编码
    – JavaScript 上下文:JS 转义
    – URL 上下文:URL 编码
    – CSS 上下文:CSS 转义

    ## 3. 数据库操作规范
    – 必须使用参数化查询
    – 禁止字符串拼接 SQL
    – ORM raw query 需要特别审批

    ## 4. 认证与授权
    – 密码必须使用 bcrypt (cost >= 12) 或 argon2
    – JWT 必须设置过期时间
    – 所有 API 必须有权限校验

    ## 5. 错误处理
    – 生产环境禁止输出堆栈信息
    – 统一错误响应格式
    – 详细错误只记录到日志

    ## 6. 日志规范
    – 禁止记录:密码、token、身份证号、银行卡号
    – 必须记录:安全事件、认证失败、权限变更

    8.2 Git Hooks 集成安全扫描

    创建 .husky/pre-commit 和 .husky/pre-push:

    #!/bin/sh
    # .husky/pre-commit – 提交前安全检查
    # 安装: npm install –save-dev husky && npx husky init

    echo "🔒 运行提交前安全检查…"

    # 1. 获取暂存的文件
    STAGED_FILES=$(git diff –cached –name-only –diff-filter=ACM | grep -E '\\.(js|ts|jsx|tsx|py|java)$')

    if [ -z "$STAGED_FILES" ]; then
    echo "✅ 无需要检查的代码文件"
    exit 0
    fi

    # 2. 运行 Semgrep 扫描暂存文件
    echo "📋 Semgrep 扫描…"
    echo "$STAGED_FILES" | xargs semgrep –config auto –config ./semgrep-rules/ –error

    if [ $? -ne 0 ]; then
    echo ""
    echo "❌ 安全检查未通过!"
    echo " 请修复上述安全问题后重新提交。"
    echo " 如确认为误报,请联系安全团队添加例外。"
    exit 1
    fi

    # 3. 检查是否包含敏感信息
    echo "🔍 检查敏感信息…"
    echo "$STAGED_FILES" | xargs grep -l -E "(password|secret|api_key|token)\\s*[:=]\\s*['\\"][^'\\"]{8,}['\\"]" 2>/dev/null

    if [ $? -eq 0 ]; then
    echo ""
    echo "⚠️ 警告:检测到可能的硬编码敏感信息!"
    echo " 请确认是否为真实凭证。"
    read -p " 是否继续提交?(y/N) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    exit 1
    fi
    fi

    # 4. ESLint 安全检查
    echo "📝 ESLint 安全检查…"
    echo "$STAGED_FILES" | grep -E '\\.(js|ts|jsx|tsx)$' | xargs npx eslint –no-eslintrc –plugin security –rule 'security/detect-eval-with-expression: error' 2>/dev/null

    echo ""
    echo "✅ 安全检查通过!"

    #!/bin/sh
    # .husky/pre-push – 推送前完整安全扫描

    echo "🔒 运行推送前完整安全扫描…"

    # 完整项目扫描
    semgrep –config auto –config ./semgrep-rules/ . –json > ./reports/pre-push-scan.json

    # 检查是否有 ERROR 级别发现
    ERROR_COUNT=$(python3 -c "
    import json
    with open('./reports/pre-push-scan.json') as f:
    data = json.load(f)
    errors = [r for r in data.get('results', []) if r['extra']['severity'] == 'ERROR']
    print(len(errors))

    ")

    if [ "$ERROR_COUNT" -gt 0 ]; then
    echo "
    ❌ 发现 ${ERROR_COUNT} 个高危安全问题,禁止推送!"
    echo "
    详细报告: ./reports/pre-push-scan.json"
    exit 1
    fi

    echo "✅ 推送前安全检查通过"

    8.3 Code Review 安全检查清单

    # Code Review 安全检查清单

    ## 审查人:________ 日期:________ PR:________

    ### 输入验证
    – [ ] 所有外部输入(请求参数、文件上传、第三方回调)是否已验证?
    – [ ] 是否对输入进行了类型、长度、格式、范围检查?
    – [ ] 文件上传是否验证了类型和大小?

    ### SQL/数据库
    – [ ] 是否使用参数化查询?
    – [ ] 是否存在动态 SQL 拼接?
    – [ ] 数据库错误信息是否暴露给用户?

    ### 认证/授权
    – [ ] 新端点是否有适当的认证检查?
    – [ ] 是否验证了资源所有权(防 IDOR)?
    – [ ] 权限检查是否在服务端(而非仅前端)?

    ### XSS 防护
    – [ ] 用户输入在输出时是否进行了编码/转义?
    – [ ] 是否使用了 dangerouslySetInnerHTML/v-html?
    – [ ] CSP 策略是否覆盖新页面?

    ### 敏感数据
    – [ ] 是否有硬编码的密钥/密码?
    – [ ] 日志中是否记录了敏感信息?
    – [ ] 敏感数据是否加密存储/传输?

    ### 错误处理
    – [ ] 异常是否被正确捕获?
    – [ ] 错误响应是否暴露了内部信息?
    – [ ] 是否有适当的错误日志记录?

    ### 依赖安全
    – [ ] 是否引入了新的依赖?
    – [ ] 新依赖是否有已知漏洞?(npm audit / pip audit)

    ### 审查结论
    – [ ] ✅ 通过,无安全问题
    – [ ] ⚠️ 有条件通过,需修复后合并
    – [ ] ❌ 不通过,存在严重安全问题

    审查人签名:________

    8.4 安全事件响应流程

    # security-incident-response.yaml – 安全事件响应流程

    severity_levels:
    critical:
    description: "数据泄露、RCE、认证完全绕过"
    response_time: "1小时内响应"
    actions:
    立即通知安全团队负责人
    评估影响范围
    必要时下线受影响服务
    24小时内完成修复
    事后编写 RCA 报告

    high:
    description: "SQL注入、XSS、越权访问"
    response_time: "4小时内响应"
    actions:
    通知安全团队
    创建高优先级修复任务
    3个工作日内完成修复
    添加回归测试

    medium:
    description: "信息泄露、CSRF、配置不当"
    response_time: "1个工作日内响应"
    actions:
    记录到安全任务看板
    当前迭代内修复
    Code Review 确认修复

    low:
    description: "最佳实践建议、低风险配置"
    response_time: "下一迭代处理"
    actions:
    记录到技术债务列表
    规划修复时间

    escalation_path:
    level: 1
    contact: "开发团队负责人"
    when: "初次发现"
    level: 2
    contact: "安全团队"
    when: "确认为真实漏洞"
    level: 3
    contact: "CTO/安全总监"
    when: "Critical 级别或影响用户数据"
    level: 4
    contact: "法务/公关"
    when: "涉及数据泄露需外部通报"

    8.5 团队安全培训与知识共享

    # 团队安全培训计划

    ## 新人入职(第1周)
    – [ ] 安全编码规范培训(2小时)
    – [ ] Cursor 安全工具链使用培训(1小时)
    – [ ] 完成安全编码练习(含故意漏洞的代码修复)
    – [ ] 阅读 OWASP Top 10 文档

    ## 季度培训
    – [ ] 最新安全威胁趋势分享
    – [ ] 内部安全事件复盘
    – [ ] 新工具/规则更新培训
    – [ ] CTF 练习或安全竞赛

    ## 知识共享机制
    – 每周安全 Tip(Slack/飞书频道)
    – 月度安全 Review 会议
    – 内部安全 Wiki 维护
    – 漏洞修复案例库积累


    九、典型报错分析与插件链运行故障排查

    9.1 插件安装失败排查

    问题1:Semgrep CLI 安装失败

    # 错误信息
    ERROR: Could not find a version that satisfies the requirement semgrep
    ERROR: No matching distribution found for semgrep

    # 原因分析
    # 1. Python 版本过低(需要 3.8+)
    # 2. pip 版本过旧
    # 3. 网络问题

    # 解决方案
    python3 –version # 确认 >= 3.8

    # 升级 pip
    pip3 install –upgrade pip

    # 使用国内镜像(中国大陆用户)
    pip3 install semgrep -i https://pypi.tuna.tsinghua.edu.cn/simple

    # 如果仍然失败,使用 Docker
    docker pull semgrep/semgrep:latest
    alias semgrep='docker run –rm -v "${PWD}:/src" semgrep/semgrep semgrep'

    问题2:Cursor 扩展市场无法访问

    # 错误信息
    Unable to connect to the marketplace

    # 解决方案
    # 1. 检查网络连接
    curl -I https://marketplace.visualstudio.com

    # 2. 配置代理(如果需要)
    # 在 Cursor 设置中:
    # "http.proxy": "http://127.0.0.1:7890"
    # "http.proxyStrictSSL": false

    # 3. 手动安装 VSIX
    # 下载 .vsix 文件后:
    # Ctrl+Shift+P → "Install from VSIX…"

    问题3:Node.js 版本不兼容

    # 错误信息
    Error: ESLint requires Node.js version >= 18.0.0

    # 解决方案
    node –version # 检查当前版本

    # 使用 nvm 切换版本
    nvm install 18
    nvm use 18
    nvm alias default 18

    # 重新安装依赖
    rm -rf node_modules package-lock.json
    npm install

    9.2 规则解析错误

    问题:YAML 语法错误

    # 错误信息
    semgrep.exceptions.SemgrepError:
    Rule file semgrep-rules/custom.yaml is not valid YAML:
    mapping values are not allowed here

    # 常见原因及修复

    # ❌ 错误:多行字符串未正确缩进
    rules:
    – id: test-rule
    pattern: |
    const x = "hello" # 错误:pattern 内容需要缩进
    message: "test"

    # ✅ 正确:
    rules:
    – id: test-rule
    pattern: |
    const x = "hello"
    message: "test"

    # ❌ 错误:特殊字符未转义
    rules:
    – id: test
    message: "Use "safe" function" # 引号冲突

    # ✅ 正确:
    rules:
    – id: test
    message: 'Use "safe" function' # 使用单引号包裹
    # 或者
    message: >
    Use "safe" function

    验证规则文件的命令:

    # 验证 YAML 语法
    python3 -c "import yaml; yaml.safe_load(open('semgrep-rules/custom.yaml'))"

    # 验证 Semgrep 规则
    semgrep –config semgrep-rules/custom.yaml –validate

    # 详细错误输出
    semgrep –config semgrep-rules/custom.yaml –debug 2>&1 | head -50

    9.3 扫描超时与性能问题

    问题:大型项目扫描时间过长

    # 优化方案

    # 1. 使用 .semgrepignore 排除不需要扫描的目录
    # 参考 7.4 节的 .semgrepignore 配置

    # 2. 限制扫描的文件类型
    semgrep –config auto –include="*.js" –include="*.ts" ./src

    # 3. 使用并行扫描
    semgrep –config auto –jobs 4 ./src

    # 4. 只扫描变更文件(CI 环境)
    git diff –name-only HEAD~1 | xargs semgrep –config auto

    # 5. 使用缓存
    semgrep –config auto –use-cache ./src

    # 6. 分模块扫描(超大项目)
    semgrep –config ./semgrep-rules/injection/ ./src/api/
    semgrep –config ./semgrep-rules/xss/ ./src/frontend/

    Cursor 中的性能优化配置:

    {
    // 减少实时扫描频率
    "semgrep.scan.onType": false, // 关闭输入时扫描
    "semgrep.scan.onSave": true, // 仅保存时扫描

    // 限制扫描范围
    "semgrep.exclude": [
    "node_modules",
    "dist",
    "build",
    ".git",
    "coverage",
    "*.min.js",
    "*.map",
    "vendor",
    "third-party"
    ],

    // 增加内存限制
    "semgrep.maxMemory": "4096",

    // 超时设置
    "semgrep.timeout": 300
    }

    9.4 AI 提示词无响应处理

    问题:Cursor AI 对话无响应或响应不完整

    # 排查步骤

    ## 1. 检查网络连接
    – 确认能访问 AI 服务
    – 检查防火墙/代理设置

    ## 2. 检查订阅状态
    – 确认 Cursor Pro/Team 订阅有效
    – 检查是否达到使用限额

    ## 3. 优化提示词
    – 减少单次输入的代码量(建议 < 500 行)
    – 分步骤提问而非一次性大段输入
    – 明确指定输出格式

    ## 4. 替代方案
    – 使用 Ctrl+K(内联编辑)代替 Ctrl+L(对话)
    – 选中特定代码段再提问
    – 重启 Cursor 后重试

    9.5 网络代理与证书问题

    # 问题:企业内网环境下的 SSL 证书错误

    # 错误信息
    ERROR: SSL: CERTIFICATE_VERIFY_FAILED

    # 解决方案

    # 方案1:配置企业 CA 证书
    export SSL_CERT_FILE=/path/to/company-ca.pem
    export REQUESTS_CA_BUNDLE=/path/to/company-ca.pem

    # 方案2:pip 配置
    pip3 config set global.cert /path/to/company-ca.pem

    # 方案3:Node.js 环境
    export NODE_EXTRA_CA_CERTS=/path/to/company-ca.pem

    # 方案4:临时禁用验证(仅限开发环境,不推荐生产使用)
    export NODE_TLS_REJECT_UNAUTHORIZED=0 # ⚠️ 仅用于调试

    9.6 版本冲突解决

    # 问题:多个安全工具版本冲突

    # 诊断
    pip3 list | grep -i semgrep
    npm list eslint
    python3 –version
    node –version

    # 解决方案:使用虚拟环境隔离

    # Python 虚拟环境
    python3 -m venv .security-tools
    source .security-tools/bin/activate # Linux/macOS
    # .security-tools\\Scripts\\activate # Windows
    pip install semgrep bandit

    # Node.js 使用项目本地版本
    # 在 package.json 中锁定版本
    {
    "devDependencies": {
    "eslint": "^9.0.0",
    "eslint-plugin-security": "^3.0.0"
    }
    }

    # 使用 npx 确保使用项目本地版本
    npx eslint –version

    故障排查决策树:

    插件/工具不工作
    ├── 是否能正常安装?
    │ ├── 否 → 检查网络/权限/版本要求(→ 9.1)
    │ └── 是 → 继续
    ├── 是否能正常启动?
    │ ├── 否 → 检查依赖/环境变量(→ 9.5, 9.6)
    │ └── 是 → 继续
    ├── 扫描是否有输出?
    │ ├── 无输出 → 检查配置/路径/权限
    │ ├── 报错 → 检查规则语法(→ 9.2)
    │ └── 超时 → 优化性能(→ 9.3)
    └── 结果是否准确?
    ├── 误报多 → 优化规则(→ 第七章)
    └── 漏报 → 补充规则/启用更多规则集


    十、进阶技巧:扩展私有规则库与持续集成对接

    10.1 企业私有规则库搭建

    私有规则库目录结构:

    company-security-rules/
    ├── README.md
    ├── CHANGELOG.md
    ├── version.yaml # 版本信息
    ├── rules/
    │ ├── language-specific/
    │ │ ├── javascript/
    │ │ │ ├── injection.yaml
    │ │ │ ├── xss.yaml
    │ │ │ └── auth.yaml
    │ │ ├── python/
    │ │ │ ├── injection.yaml
    │ │ │ └── deserialization.yaml
    │ │ └── java/
    │ │ ├── injection.yaml
    │ │ └── crypto.yaml
    │ ├── framework-specific/
    │ │ ├── express/
    │ │ ├── django/
    │ │ ├── spring/
    │ │ └── react/
    │ ├── business-specific/
    │ │ ├── payment-security.yaml # 支付安全
    │ │ ├── pii-handling.yaml # 个人信息处理
    │ │ └── api-standards.yaml # 内部 API 规范
    │ └── compliance/
    │ ├── gdpr.yaml # GDPR 合规
    │ ├── pci-dss.yaml # PCI DSS 合规
    │ └── soc2.yaml # SOC2 合规
    ├── tests/ # 规则测试用例
    │ ├── javascript/
    │ ├── python/
    │ └── java/
    └── scripts/
    ├── validate-rules.sh # 规则验证脚本
    ├── publish.sh # 发布脚本
    └── update-registry.sh # 更新注册表

    私有规则示例(业务特定):

    # rules/business-specific/payment-security.yaml
    # 支付安全专用规则

    rules:
    # 规则1:禁止在日志中记录卡号
    id: nocardnumberinlogs
    patterns:
    pattern-either:
    pattern: |
    logger.$METHOD(…, $CARD, …)

    pattern: |
    console.log(…, $CARD, …)

    metavariable-regex:
    metavariable: $CARD
    regex: .*(card|cardNumber|card_number|pan|creditCard).*
    message: >
    🚫 PCI DSS 违规:禁止在日志中记录银行卡号。
    请使用脱敏处理:仅保留前6后4位。
    示例: maskCardNumber(cardNumber) → "6222****1234"

    languages: [javascript, typescript, python, java]
    severity: ERROR
    metadata:
    category: compliance
    standard: PCIDSS
    requirement: "Requirement 3.4"
    cwe: "CWE-532"

    # 规则2:金额计算必须使用整数(分为单位)
    id: nofloatformoney
    patterns:
    pattern-either:
    pattern: |
    $AMOUNT = $X * $Y

    pattern: |
    $AMOUNT = $X + $Y

    pattern: |
    $AMOUNT = $X – $Y

    metavariable-regex:
    metavariable: $AMOUNT
    regex: .*(price|amount|total|fee|cost).*
    pattern-not: |
    $AMOUNT = Math.round(…)

    pattern-not: |
    $AMOUNT = parseInt(…)

    message: >
    ⚠️ 金融计算不应使用浮点数。
    请使用整数(以分/厘为单位)或专用库(如 decimal.js、BigDecimal)。

    languages: [javascript, typescript]
    severity: WARNING
    metadata:
    category: businesslogic
    confidence: MEDIUM

    # 规则3:支付接口必须有幂等性检查
    id: paymentendpointneedsidempotency
    patterns:
    pattern: |
    $ROUTER.post("$PATH", …, $HANDLER)

    metavariable-regex:
    metavariable: $PATH
    regex: .*(pay|payment|charge|transaction|order).*
    pattern-not-inside: |
    // @idempotent

    message: >
    支付相关接口必须实现幂等性。
    请添加幂等性检查(如请求去重、状态机验证)。
    添加 "// @idempotent" 注释标记已处理。

    languages: [javascript, typescript]
    severity: WARNING
    metadata:
    category: businesslogic

    规则库版本管理:

    # version.yaml
    version: "2.3.1"
    last_updated: "2026-08-01"
    maintainer: "security-team@company.com"
    changelog:
    version: "2.3.1"
    date: "2026-08-01"
    changes:
    "新增 React 19 安全规则"
    "优化 SQL 注入检测精确度"
    version: "2.3.0"
    date: "2026-07-15"
    changes:
    "新增 GDPR 合规规则集"
    "支持 TypeScript 5.5 语法"

    10.2 CI/CD 管道集成

    GitHub Actions 配置:

    # .github/workflows/security-scan.yml
    name: Security Scan Pipeline

    on:
    push:
    branches: [main, develop]
    pull_request:
    branches: [main]

    jobs:
    sast-scan:
    name: SAST 静态安全扫描
    runs-on: ubuntulatest
    steps:
    name: 检出代码
    uses: actions/checkout@v4
    with:
    fetch-depth: 0 # 完整历史,用于增量扫描

    name: 设置 Python
    uses: actions/setuppython@v5
    with:
    python-version: '3.11'

    name: 安装 Semgrep
    run: pip install semgrep

    name: 运行 Semgrep 扫描
    run: |
    semgrep \\
    –config auto \\
    –config ./semgrep-rules/ \\
    –config ./company-security-rules/rules/ \\
    –json \\
    –output reports/semgrep-results.json \\
    –sarif \\
    –output reports/semgrep-results.sarif \\
    .

    continue-on-error: true # 不因扫描失败阻塞管道

    name: 检查扫描结果
    run: |
    ERROR_COUNT=$(python3 -c "
    import json
    with open('reports/semgrep-results.json') as f:
    data = json.load(f)
    errors = [r for r in data.get('results', [])
    if r['extra']['severity'] == 'ERROR']
    print(len(errors))
    ")

    echo "发现 ${ERROR_COUNT} 个高危问题"

    if [ "$ERROR_COUNT" gt 0 ]; then
    echo "::error::发现 ${ERROR_COUNT} 个高危安全漏洞,请修复后重新提交"
    exit 1
    fi

    name: 上传 SARIF 报告
    uses: github/codeqlaction/uploadsarif@v3
    if: always()
    with:
    sarif_file: reports/semgrepresults.sarif

    name: 上传扫描报告
    uses: actions/uploadartifact@v4
    if: always()
    with:
    name: securityscanreport
    path: reports/

    dependency-scan:
    name: 依赖安全扫描
    runs-on: ubuntulatest
    steps:
    uses: actions/checkout@v4

    name: npm audit
    run: |
    npm audit –audit-level=high –json > reports/npm-audit.json || true
    HIGH_VULNS=$(cat reports/npm-audit.json | python3 -c "
    import json, sys
    data = json.load(sys.stdin)
    print(data.get('metadata', {}).get('vulnerabilities', {}).get('high', 0) +
    data.get('metadata', {}).get('vulnerabilities', {}).get('critical', 0))
    ")
    echo "高危依赖漏洞: ${HIGH_VULNS}"
    if [ "$HIGH_VULNS" -gt 0 ]; then
    echo "::warning::发现 ${HIGH_VULNS} 个高危依赖漏洞"
    fi

    dast-scan:
    name: DAST 动态安全扫描
    runs-on: ubuntulatest
    needs: [sastscan] # SAST 通过后才运行 DAST
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
    uses: actions/checkout@v4

    name: 启动应用
    run: |
    npm ci
    npm run build
    npm start &
    sleep 10 # 等待应用启动

    name: 运行 ZAP 基线扫描
    uses: zaproxy/actionbaseline@v0.12.0
    with:
    target: 'http://localhost:3000'
    rules_file_name: 'zap-rules.conf'
    cmd_options: '-a -T 10'

    name: 上传 DAST 报告
    uses: actions/uploadartifact@v4
    if: always()
    with:
    name: dastreport
    path: report_html.html

    GitLab CI 配置:

    # .gitlab-ci.yml 安全扫描部分

    stages:
    security

    sast:
    stage: security
    image: semgrep/semgrep:latest
    script:
    semgrep config auto config ./semgreprules/ json output glsastreport.json .
    artifacts:
    reports:
    sast: glsastreport.json
    expire_in: 30 days
    rules:
    if: $CI_MERGE_REQUEST_ID
    if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

    dependency_scanning:
    stage: security
    image: node:20
    script:
    npm audit json > gldependencyreport.json || true
    artifacts:
    paths:
    gldependencyreport.json
    expire_in: 30 days

    10.3 安全报告自动生成

    完成安全报告自动生成器的实现:

    #!/usr/bin/env python3
    """
    generate_security_report.py – 自动生成安全扫描综合报告(完整版)
    整合 SAST、DAST、依赖扫描结果,生成统一 HTML/Markdown 报告

    用法: python3 scripts/generate_security_report.py [–format html|markdown|json]
    输出: reports/security-report-{date}.html
    """

    import json
    import os
    import sys
    import argparse
    from datetime import datetime
    from pathlib import Path
    from typing import List, Dict, Optional

    class SecurityReportGenerator:
    """安全报告生成器 – 整合多种扫描源"""

    def __init__(self, project_name: str):
    """
    初始化报告生成器

    Args:
    project_name: 项目名称,用于报告标题
    """
    self.project_name = project_name
    self.timestamp = datetime.now()
    self.data = {
    'sast_findings': [], # SAST 静态扫描发现
    'dast_findings': [], # DAST 动态扫描发现
    'dependency_vulns': [], # 依赖漏洞
    'ai_review_notes': [], # AI 审查备注
    'summary': {} # 统计摘要
    }

    def load_semgrep_results(self, path: str) > None:
    """
    加载 Semgrep SAST 扫描结果

    Args:
    path: Semgrep JSON 报告文件路径
    """
    if not os.path.exists(path):
    print(f" ⚠️ SAST 报告不存在: {path},跳过")
    return

    with open(path, 'r', encoding='utf-8') as f:
    data = json.load(f)

    results = data.get('results', [])
    print(f" 📋 加载 SAST 发现: {len(results)} 条")

    for result in results:
    extra = result.get('extra', {})
    metadata = extra.get('metadata', {})

    self.data['sast_findings'].append({
    'rule_id': result.get('check_id', 'unknown'),
    'severity': extra.get('severity', 'INFO'),
    'message': extra.get('message', '').strip(),
    'file': result.get('path', ''),
    'line': result.get('start', {}).get('line', 0),
    'column': result.get('start', {}).get('col', 0),
    'code_snippet': extra.get('lines', ''),
    'cwe': metadata.get('cwe', 'N/A'),
    'owasp': metadata.get('owasp', 'N/A'),
    'confidence': metadata.get('confidence', 'MEDIUM'),
    'fix_suggestion': extra.get('fix', '')
    })

    def load_zap_results(self, path: str) > None:
    """
    加载 OWASP ZAP DAST 扫描结果

    Args:
    path: ZAP JSON 报告文件路径
    """
    if not os.path.exists(path):
    print(f" ⚠️ DAST 报告不存在: {path},跳过")
    return

    with open(path, 'r', encoding='utf-8') as f:
    data = json.load(f)

    alerts = data.get('alerts', [])
    print(f" 📋 加载 DAST 发现: {len(alerts)} 条")

    for alert in alerts:
    self.data['dast_findings'].append({
    'name': alert.get('name', ''),
    'risk': alert.get('risk', 'Informational'),
    'confidence': alert.get('confidence', ''),
    'url': alert.get('url', ''),
    'method': alert.get('method', 'GET'),
    'param': alert.get('param', ''),
    'description': alert.get('description', '')[:500],
    'solution': alert.get('solution', '')[:500],
    'cwe_id': alert.get('cweid', 'N/A'),
    'wasc_id': alert.get('wascid', 'N/A'),
    'evidence': alert.get('evidence', '')[:200]
    })

    def load_npm_audit(self, path: str) > None:
    """
    加载 npm audit 依赖扫描结果

    Args:
    path: npm audit JSON 输出文件路径
    """
    if not os.path.exists(path):
    print(f" ⚠️ 依赖审计报告不存在: {path},跳过")
    return

    with open(path, 'r', encoding='utf-8') as f:
    data = json.load(f)

    # npm audit 格式(npm v7+)
    vulns = data.get('vulnerabilities', {})
    print(f" 📋 加载依赖漏洞: {len(vulns)} 个")

    for pkg_name, vuln_info in vulns.items():
    self.data['dependency_vulns'].append({
    'package': pkg_name,
    'severity': vuln_info.get('severity', 'unknown'),
    'via': vuln_info.get('via', []),
    'range': vuln_info.get('range', ''),
    'fix_available': vuln_info.get('fixAvailable', False),
    'direct': vuln_info.get('isDirect', False)
    })

    def generate_summary(self) > None:
    """生成统计摘要数据"""
    # SAST 按严重等级统计
    sast_by_severity = {'ERROR': 0, 'WARNING': 0, 'INFO': 0}
    for f in self.data['sast_findings']:
    sev = f['severity']
    if sev in sast_by_severity:
    sast_by_severity[sev] += 1

    # DAST 按风险等级统计
    dast_by_risk = {'High': 0, 'Medium': 0, 'Low': 0, 'Informational': 0}
    for f in self.data['dast_findings']:
    risk = f['risk']
    if risk in dast_by_risk:
    dast_by_risk[risk] += 1

    # 依赖漏洞按严重等级统计
    dep_by_severity = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0}
    for v in self.data['dependency_vulns']:
    sev = v['severity']
    if sev in dep_by_severity:
    dep_by_severity[sev] += 1

    self.data['summary'] = {
    'project': self.project_name,
    'scan_date': self.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
    'total_issues': (
    len(self.data['sast_findings']) +
    len(self.data['dast_findings']) +
    len(self.data['dependency_vulns'])
    ),
    'sast_total': len(self.data['sast_findings']),
    'sast_by_severity': sast_by_severity,
    'dast_total': len(self.data['dast_findings']),
    'dast_by_risk': dast_by_risk,
    'dependency_total': len(self.data['dependency_vulns']),
    'dependency_by_severity': dep_by_severity,
    'risk_score': self._calculate_risk_score()
    }

    def _calculate_risk_score(self) > int:
    """
    计算综合风险评分(0-100)

    评分规则:
    – Critical/ERROR: 每个 +15 分
    – High: 每个 +10 分
    – Medium/WARNING: 每个 +5 分
    – Low/INFO: 每个 +1 分
    最终分数上限 100
    """
    score = 0

    # SAST 计分
    for f in self.data['sast_findings']:
    if f['severity'] == 'ERROR':
    score += 15
    elif f['severity'] == 'WARNING':
    score += 5
    else:
    score += 1

    # DAST 计分
    for f in self.data['dast_findings']:
    if f['risk'] == 'High':
    score += 10
    elif f['risk'] == 'Medium':
    score += 5
    elif f['risk'] == 'Low':
    score += 2
    else:
    score += 1

    # 依赖漏洞计分
    for v in self.data['dependency_vulns']:
    if v['severity'] == 'critical':
    score += 15
    elif v['severity'] == 'high':
    score += 10
    elif v['severity'] == 'medium':
    score += 5
    else:
    score += 1

    return min(score, 100)

    def generate_html_report(self, output_path: str) > None:
    """
    生成 HTML 格式的安全报告

    Args:
    output_path: 输出文件路径
    """
    summary = self.data['summary']

    # 构建高危发现列表
    critical_findings = []
    for f in self.data['sast_findings']:
    if f['severity'] == 'ERROR':
    critical_findings.append({
    'type': 'SAST',
    'title': f['rule_id'],
    'location': f"{f['file']}:{f['line']}",
    'message': f['message'],
    'cwe': f['cwe']
    })
    for f in self.data['dast_findings']:
    if f['risk'] == 'High':
    critical_findings.append({
    'type': 'DAST',
    'title': f['name'],
    'location': f['url'],
    'message': f['description'][:200],
    'cwe': f"CWE-{f['cwe_id']}"
    })

    # 生成 HTML
    html_content = f"""<!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>安全扫描报告 –
    {self.project_name}</title>
    <style>
    * {{ margin: 0; padding: 0; box-sizing: border-box; }}
    body {{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: #f0f2f5;
    color: #333;
    line-height: 1.6;
    }}
    .container {{
    max-width: 1200px;
    margin: 0 auto;
    padding: 40px 20px;
    }}
    .header {{
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
    color: white;
    padding: 40px;
    border-radius: 12px;
    margin-bottom: 30px;
    }}
    .header h1 {{ font-size: 28px; margin-bottom: 10px; }}
    .header .meta {{ opacity: 0.8; font-size: 14px; }}
    .risk-score {{
    display: inline-block;
    padding: 8px 20px;
    border-radius: 20px;
    font-weight: bold;
    font-size: 18px;
    margin-top: 15px;
    }}
    .risk-critical {{ background: #e74c3c; }}
    .risk-high {{ background: #e67e22; }}
    .risk-medium {{ background: #f39c12; }}
    .risk-low {{ background: #27ae60; }}
    .summary-grid {{
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 20px;
    margin-bottom: 30px;
    }}
    .card {{
    background: white;
    border-radius: 10px;
    padding: 25px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    }}
    .card h3 {{
    font-size: 14px;
    color: #666;
    text-transform: uppercase;
    margin-bottom: 10px;
    }}
    .card .number {{
    font-size: 36px;
    font-weight: bold;
    }}
    .card .number.error {{ color: #e74c3c; }}
    .card .number.warning {{ color: #f39c12; }}
    .card .number.info {{ color: #3498db; }}
    .section {{
    background: white;
    border-radius: 10px;
    padding: 30px;
    margin-bottom: 20px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    }}
    .section h2 {{
    font-size: 20px;
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 2px solid #eee;
    }}
    table {{
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    }}
    th, td {{
    padding: 12px 15px;
    text-align: left;
    border-bottom: 1px solid #eee;
    }}
    th {{
    background: #f8f9fa;
    font-weight: 600;
    color: #555;
    }}
    tr:hover {{ background: #f8f9fa; }}
    .badge {{
    display: inline-block;
    padding: 3px 10px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 600;
    }}
    .badge-error {{ background: #fde8e8; color: #e74c3c; }}
    .badge-warning {{ background: #fef3e2; color: #e67e22; }}
    .badge-info {{ background: #e8f4fd; color: #3498db; }}
    .badge-high {{ background: #fde8e8; color: #e74c3c; }}
    .badge-medium {{ background: #fef3e2; color: #e67e22; }}
    .badge-low {{ background: #e8f8e8; color: #27ae60; }}
    code {{
    background: #f4f4f4;
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 13px;
    }}
    .footer {{
    text-align: center;
    padding: 20px;
    color: #999;
    font-size: 13px;
    }}
    </style>
    </head>
    <body>
    <div class="container">
    <!– 报告头部 –>
    <div class="header">
    <h1>🔒 安全扫描报告</h1>
    <div class="meta">
    <p>项目:
    {self.project_name}</p>
    <p>扫描时间:
    {summary['scan_date']}</p>
    <p>扫描工具:Semgrep + OWASP ZAP + npm audit + Cursor AI</p>
    </div>
    <div class="risk-score
    {'risk-critical' if summary['risk_score'] >= 70 else 'risk-high' if summary['risk_score'] >= 50 else 'risk-medium' if summary['risk_score'] >= 25 else 'risk-low'}">
    风险评分:
    {summary['risk_score']}/100
    </div>
    </div>

    <!– 统计摘要 –>
    <div class="summary-grid">
    <div class="card">
    <h3>SAST 静态扫描</h3>
    <div class="number {'error' if summary['sast_by_severity']['ERROR'] > 0 else ''}">{summary['sast_total']}</div>
    <p>🔴 ERROR:
    {summary['sast_by_severity']['ERROR']} | 🟡 WARNING: {summary['sast_by_severity']['WARNING']} | 🔵 INFO: {summary['sast_by_severity']['INFO']}</p>
    </div>
    <div class="card">
    <h3>DAST 动态扫描</h3>
    <div class="number">
    {summary['dast_total']}</div>
    <p>🔴 High:
    {summary['dast_by_risk']['High']} | 🟠 Medium: {summary['dast_by_risk']['Medium']} | 🟡 Low: {summary['dast_by_risk']['Low']}</p>
    </div>
    <div class="card">
    <h3>依赖漏洞</h3>
    <div class="number">
    {summary['dependency_total']}</div>
    <p>🔴 Critical:
    {summary['dependency_by_severity']['critical']} | 🟠 High: {summary['dependency_by_severity']['high']} | 🟡 Medium: {summary['dependency_by_severity']['medium']}</p>
    </div>
    <div class="card">
    <h3>总发现数</h3>
    <div class="number">
    {summary['total_issues']}</div>
    <p>综合安全评估</p>
    </div>
    </div>

    <!– 高危发现详情 –>
    <div class="section">
    <h2>🚨 高危发现(需立即处理)</h2>
    {'<p style="color: #27ae60; font-size: 16px;">✅ 未发现高危安全问题</p>' if not critical_findings else ''}
    {'<table><tr><th>类型</th><th>问题</th><th>位置</th><th>CWE</th></tr>' + ''.join(f'<tr><td><span class="badge badge-error">{f["type"]}</span></td><td>{f["title"]}</td><td><code>{f["location"]}</code></td><td>{f["cwe"]}</td></tr>' for f in critical_findings) + '</table>' if critical_findings else ''}
    </div>

    <!– SAST 详细结果 –>
    <div class="section">
    <h2>📋 SAST 静态分析详细结果</h2>
    <table>
    <tr>
    <th>严重等级</th>
    <th>规则</th>
    <th>文件位置</th>
    <th>描述</th>
    <th>CWE</th>
    </tr>
    {''.join(f'<tr><td><span class="badge badge-{f["severity"].lower()}">{f["severity"]}</span></td><td>{f["rule_id"]}</td><td><code>{f["file"]}:{f["line"]}</code></td><td>{f["message"][:100]}</td><td>{f["cwe"]}</td></tr>' for f in self.data['sast_findings'][:50])}
    </table>
    </div>

    <!– DAST 详细结果 –>
    <div class="section">
    <h2>🌐 DAST 动态分析详细结果</h2>
    <table>
    <tr>
    <th>风险</th>
    <th>漏洞名称</th>
    <th>URL</th>
    <th>修复建议</th>
    </tr>
    {''.join(f'<tr><td><span class="badge badge-{f["risk"].lower()}">{f["risk"]}</span></td><td>{f["name"]}</td><td><code>{f["url"][:60]}</code></td><td>{f["solution"][:100]}</td></tr>' for f in self.data['dast_findings'][:30])}
    </table>
    </div>

    <!– 页脚 –>
    <div class="footer">
    <p>本报告由 Cursor 安全插件链自动生成 | 生成时间:{self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}</p>
    <p>工具链:Semgrep + OWASP ZAP + npm audit + Cursor AI</p>
    </div>
    </div>
    </body>
    </html>"""

    # 写入文件
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, 'w', encoding='utf-8') as f:
    f.write(html_content)

    print(f" ✅ HTML 报告已生成: {output_path}")

    def generate_markdown_report(self, output_path: str) > None:
    """
    生成 Markdown 格式的安全报告(适合在 Git 仓库中查看)

    Args:
    output_path: 输出文件路径
    """
    summary = self.data['summary']

    md_lines = [
    f"# 🔒 安全扫描报告 – {self.project_name}",
    f"",
    f"**扫描时间**:{summary['scan_date']}",
    f"**风险评分**:{summary['risk_score']}/100",
    f"**总发现数**:{summary['total_issues']}",
    f"",
    f"—",
    f"",
    f"## 📊 统计摘要",
    f"",
    f"| 类别 | 总数 | 高危 | 中危 | 低危 |",
    f"|——|——|——|——|——|",
    f"| SAST | {summary['sast_total']} | {summary['sast_by_severity']['ERROR']} | {summary['sast_by_severity']['WARNING']} | {summary['sast_by_severity']['INFO']} |",
    f"| DAST | {summary['dast_total']} | {summary['dast_by_risk']['High']} | {summary['dast_by_risk']['Medium']} | {summary['dast_by_risk']['Low']} |",
    f"| 依赖 | {summary['dependency_total']} | {summary['dependency_by_severity']['critical'] + summary['dependency_by_severity']['high']} | {summary['dependency_by_severity']['medium']} | {summary['dependency_by_severity']['low']} |",
    f"",
    f"—",
    f"",
    f"## 🚨 SAST 发现详情",
    f"",
    ]

    # SAST 详情
    for i, f in enumerate(self.data['sast_findings'], 1):
    md_lines.extend([
    f"### {i}. [{f['severity']}] {f['rule_id']}",
    f"",
    f"- **位置**:`{f['file']}:{f['line']}`",
    f"- **CWE**:{f['cwe']}",
    f"- **描述**:{f['message'][:200]}",
    ])
    if f['code_snippet']:
    md_lines.extend([
    f"- **代码片段**:",
    f"```",
    f"{f['code_snippet'][:300]}",
    f"```",
    ])
    md_lines.append("")

    # DAST 详情
    md_lines.extend([
    f"—",
    f"",
    f"## 🌐 DAST 发现详情",
    f"",
    ])

    for i, f in enumerate(self.data['dast_findings'], 1):
    md_lines.extend([
    f"### {i}. [{f['risk']}] {f['name']}",
    f"",
    f"- **URL**:`{f['url']}`",
    f"- **CWE**:CWE-{f['cwe_id']}",
    f"- **描述**:{f['description'][:200]}",
    f"- **修复建议**:{f['solution'][:200]}",
    f"",
    ])

    # 写入文件
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, 'w', encoding='utf-8') as f:
    f.write('\\n'.join(md_lines))

    print(f" ✅ Markdown 报告已生成: {output_path}")

    def run(self, output_format: str = 'html') > None:
    """
    执行完整的报告生成流程

    Args:
    output_format: 输出格式 (html/markdown/json)
    """
    print("╔══════════════════════════════════════════════╗")
    print("║ 安全扫描报告生成器 v2.0 ║")
    print("╚══════════════════════════════════════════════╝")
    print(f" 项目: {self.project_name}")
    print(f" 时间: {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
    print(f" 格式: {output_format}")
    print("")

    # 步骤1:加载各类扫描结果
    print("[1/3] 加载扫描数据…")
    self.load_semgrep_results('./reports/semgrep-results.json')
    self.load_zap_results('./reports/dast/zap-alerts-latest.json')
    self.load_npm_audit('./reports/npm-audit.json')

    # 步骤2:生成统计摘要
    print("\\n[2/3] 生成统计摘要…")
    self.generate_summary()
    print(f" 风险评分: {self.data['summary']['risk_score']}/100")

    # 步骤3:输出报告
    print("\\n[3/3] 生成报告文件…")
    timestamp_str = self.timestamp.strftime('%Y%m%d_%H%M%S')

    if output_format == 'html':
    self.generate_html_report(f'./reports/security-report-{timestamp_str}.html')
    elif output_format == 'markdown':
    self.generate_markdown_report(f'./reports/security-report-{timestamp_str}.md')
    elif output_format == 'json':
    json_path = f'./reports/security-report-{timestamp_str}.json'
    with open(json_path, 'w', encoding='utf-8') as f:
    json.dump(self.data, f, ensure_ascii=False, indent=2)
    print(f" ✅ JSON 报告已生成: {json_path}")

    print("\\n✅ 报告生成完成!")

    # ==================== 命令行入口 ====================
    if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='安全扫描报告生成器')
    parser.add_argument('–project', default='MyProject', help='项目名称')
    parser.add_argument('–format', choices=['html', 'markdown', 'json'],
    default='html', help='输出格式')

    args = parser.parse_args()

    generator = SecurityReportGenerator(args.project)
    generator.run(output_format=args.format)

    在 Cursor 中配置为 Task:

    // .vscode/tasks.json 追加
    {
    "label": "生成安全报告 (HTML)",
    "type": "shell",
    "command": "python3",
    "args": [
    "scripts/generate_security_report.py",
    "–project", "${workspaceFolderBasename}",
    "–format", "html"
    ],
    "group": "test",
    "presentation": {
    "reveal": "always",
    "panel": "dedicated"
    }
    }

    10.4 与漏洞管理平台对接

    将扫描结果自动同步到企业漏洞管理平台(如 Jira、禅道、自研平台):

    #!/usr/bin/env python3
    """
    sync_to_tracker.py – 将安全发现同步到漏洞管理平台

    支持平台:
    – Jira (Atlassian)
    – 禅道 (ZenTao)
    – 自定义 Webhook

    用法: python3 scripts/sync_to_tracker.py –platform jira –config tracker-config.yaml
    """

    import json
    import yaml
    import requests
    from datetime import datetime
    from typing import List, Dict, Optional
    from dataclasses import dataclass, asdict

    @dataclass
    class VulnerabilityTicket:
    """漏洞工单数据结构"""
    title: str # 标题
    description: str # 详细描述
    severity: str # 严重等级: Critical/High/Medium/Low
    vuln_type: str # 漏洞类型: SAST/DAST/Dependency
    cwe_id: str # CWE 编号
    affected_file: str # 影响文件
    affected_line: int # 影响行号
    fix_suggestion: str # 修复建议
    assignee: str # 指派给
    labels: List[str] # 标签
    due_date: str # 截止日期
    source_tool: str # 来源工具

    class JiraSyncer:
    """Jira 漏洞同步器"""

    def __init__(self, config: Dict):
    """
    初始化 Jira 同步器

    Args:
    config: Jira 连接配置
    – base_url: Jira 服务器地址
    – project_key: 项目标识
    – username: 用户名
    – api_token: API Token
    – issue_type: 工单类型(默认 Bug)
    """
    self.base_url = config['base_url'].rstrip('/')
    self.project_key = config['project_key']
    self.username = config['username']
    self.api_token = config['api_token']
    self.issue_type = config.get('issue_type', 'Bug')

    self.session = requests.Session()
    self.session.auth = (self.username, self.api_token)
    self.session.headers.update({
    'Content-Type': 'application/json',
    'Accept': 'application/json'
    })

    def create_ticket(self, vuln: VulnerabilityTicket) > Optional[str]:
    """
    在 Jira 中创建漏洞工单

    Args:
    vuln: 漏洞信息

    Returns:
    创建的工单 Key(如 SEC-123),失败返回 None
    """
    # 严重等级映射到 Jira Priority
    priority_map = {
    'Critical': 'Highest',
    'High': 'High',
    'Medium': 'Medium',
    'Low': 'Low'
    }

    # 构建 Jira Issue 数据
    issue_data = {
    "fields": {
    "project": {"key": self.project_key},
    "summary": f"[{vuln.vuln_type}][{vuln.severity}] {vuln.title}",
    "description": {
    "type": "doc",
    "version": 1,
    "content": [
    {
    "type": "paragraph",
    "content": [{"type": "text", "text": vuln.description}]
    },
    {
    "type": "heading",
    "attrs": {"level": 3},
    "content": [{"type": "text", "text": "修复建议"}]
    },
    {
    "type": "paragraph",
    "content": [{"type": "text", "text": vuln.fix_suggestion}]
    },
    {
    "type": "heading",
    "attrs": {"level": 3},
    "content": [{"type": "text", "text": "影响位置"}]
    },
    {
    "type": "codeBlock",
    "content": [{"type": "text", "text": f"{vuln.affected_file}:{vuln.affected_line}"}]
    }
    ]
    },
    "issuetype": {"name": self.issue_type},
    "priority": {"name": priority_map.get(vuln.severity, 'Medium')},
    "labels": vuln.labels + [
    f"cwe-{vuln.cwe_id}",
    vuln.vuln_type.lower(),
    "security"
    ],
    "customfield_10001": vuln.cwe_id, # 自定义字段:CWE
    "customfield_10002": vuln.source_tool # 自定义字段:来源工具
    }
    }

    try:
    response = self.session.post(
    f"{self.base_url}/rest/api/3/issue",
    json=issue_data
    )

    if response.status_code == 201:
    issue_key = response.json()['key']
    print(f" ✅ 工单创建成功: {issue_key}")
    return issue_key
    else:
    print(f" ❌ 工单创建失败: {response.status_code}{response.text}")
    return None

    except requests.RequestException as e:
    print(f" ❌ 网络错误: {e}")
    return None

    def batch_sync(self, findings: List[Dict], source: str) > Dict[str, int]:
    """
    批量同步扫描发现到 Jira

    Args:
    findings: 发现列表
    source: 来源(SAST/DAST/Dependency)

    Returns:
    同步统计 {'success': n, 'failed': n, 'skipped': n}
    """
    stats = {'success': 0, 'failed': 0, 'skipped': 0}

    for finding in findings:
    # 跳过 INFO 级别
    severity = finding.get('severity', finding.get('risk', 'INFO'))
    if severity in ('INFO', 'Informational'):
    stats['skipped'] += 1
    continue

    # 检查是否已存在相同工单(避免重复)
    if self._check_duplicate(finding):
    stats['skipped'] += 1
    continue

    # 构建工单
    ticket = VulnerabilityTicket(
    title=finding.get('rule_id', finding.get('name', 'Unknown')),
    description=finding.get('message', finding.get('description', '')),
    severity=self._map_severity(severity),
    vuln_type=source,
    cwe_id=finding.get('cwe', finding.get('cweid', 'N/A')),
    affected_file=finding.get('file', finding.get('url', '')),
    affected_line=finding.get('line', 0),
    fix_suggestion=finding.get('fix_suggestion', finding.get('solution', '')),
    assignee='security-team',
    labels=['auto-generated', source.lower()],
    due_date=self._calculate_due_date(severity),
    source_tool=finding.get('tool', 'Semgrep')
    )

    result = self.create_ticket(ticket)
    if result:
    stats['success'] += 1
    else:
    stats['failed'] += 1

    return stats

    def _check_duplicate(self, finding: Dict) > bool:
    """检查是否已存在相同的漏洞工单"""
    rule_id = finding.get('rule_id', finding.get('name', ''))
    file_path = finding.get('file', finding.get('url', ''))

    # 使用 JQL 搜索
    jql = f'project = {self.project_key} AND labels = "security" AND summary ~ "{rule_id}" AND summary ~ "{file_path}"'

    try:
    response = self.session.get(
    f"{self.base_url}/rest/api/3/search",
    params={'jql': jql, 'maxResults': 1}
    )
    if response.status_code == 200:
    return response.json().get('total', 0) > 0
    except requests.RequestException:
    pass

    return False

    def _map_severity(self, severity: str) > str:
    """映射严重等级"""
    mapping = {
    'ERROR': 'Critical',
    'WARNING': 'Medium',
    'High': 'High',
    'Medium': 'Medium',
    'Low': 'Low',
    'critical': 'Critical',
    'high': 'High',
    'medium': 'Medium',
    'low': 'Low'
    }
    return mapping.get(severity, 'Medium')

    def _calculate_due_date(self, severity: str) > str:
    """根据严重等级计算截止日期"""
    from datetime import timedelta
    days_map = {
    'Critical': 1, # 1天内修复
    'High': 3, # 3天内修复
    'Medium': 7, # 7天内修复
    'Low': 30 # 30天内修复
    }
    days = days_map.get(severity, 7)
    due = datetime.now() + timedelta(days=days)
    return due.strftime('%Y-%m-%d')

    class WebhookSyncer:
    """通用 Webhook 同步器(适用于自研平台)"""

    def __init__(self, webhook_url: str, headers: Optional[Dict] = None):
    self.webhook_url = webhook_url
    self.headers = headers or {'Content-Type': 'application/json'}

    def send(self, findings: List[Dict], metadata: Dict) > bool:
    """发送发现数据到 Webhook"""
    payload = {
    'timestamp': datetime.now().isoformat(),
    'metadata': metadata,
    'findings': findings,
    'total_count': len(findings)
    }

    try:
    response = requests.post(
    self.webhook_url,
    json=payload,
    headers=self.headers,
    timeout=30
    )
    return response.status_code in (200, 201, 202)
    except requests.RequestException as e:
    print(f" ❌ Webhook 发送失败: {e}")
    return False

    # ==================== 主执行逻辑 ====================
    if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='漏洞管理平台同步工具')
    parser.add_argument('–platform', choices=['jira', 'zentao', 'webhook'],
    default='webhook', help='目标平台')
    parser.add_argument('–config', default='./tracker-config.yaml',
    help='配置文件路径')

    args = parser.parse_args()

    print("╔══════════════════════════════════════════╗")
    print("║ 漏洞管理平台同步工具 ║")
    print("╚══════════════════════════════════════════╝")

    # 加载配置
    if os.path.exists(args.config):
    with open(args.config, 'r') as f:
    config = yaml.safe_load(f)
    else:
    print(f"⚠️ 配置文件不存在: {args.config}")
    print(" 使用默认 Webhook 模式")
    config = {'webhook_url': 'http://localhost:9000/api/security/findings'}

    # 加载扫描结果
    findings = []
    if os.path.exists('./reports/semgrep-results.json'):
    with open('./reports/semgrep-results.json') as f:
    data = json.load(f)
    findings.extend(data.get('results', []))

    print(f"\\n 待同步发现: {len(findings)} 条")

    # 执行同步
    if args.platform == 'jira':
    syncer = JiraSyncer(config.get('jira', {}))
    stats = syncer.batch_sync(findings, 'SAST')
    print(f"\\n 同步完成: 成功 {stats['success']}, 失败 {stats['failed']}, 跳过 {stats['skipped']}")

    elif args.platform == 'webhook':
    syncer = WebhookSyncer(config.get('webhook_url', ''))
    success = syncer.send(findings, {'project': 'secure-blog-api', 'scan_type': 'SAST'})
    print(f"\\n {'✅ 同步成功' if success else '❌ 同步失败'}")

    配置文件示例 tracker-config.yaml:

    # tracker-config.yaml – 漏洞管理平台对接配置

    # Jira 配置
    jira:
    base_url: "https://your-company.atlassian.net"
    project_key: "SEC"
    username: "security-bot@company.com"
    api_token: "${JIRA_API_TOKEN}" # 使用环境变量
    issue_type: "Bug"
    # 严重等级到 Jira Priority 的映射
    priority_mapping:
    Critical: "Highest"
    High: "High"
    Medium: "Medium"
    Low: "Low"

    # Webhook 配置(自研平台)
    webhook_url: "https://internal-api.company.com/api/v2/security/findings"
    webhook_headers:
    Authorization: "Bearer ${INTERNAL_API_TOKEN}"
    X-Source: "cursor-security-chain"

    # 同步策略
    sync_policy:
    # 最低同步等级(低于此等级不同步)
    min_severity: "Medium"
    # 是否自动指派
    auto_assign: true
    # 默认指派人
    default_assignee: "security-team"
    # 去重策略
    dedup_strategy: "rule_id + file_path"
    # 同步频率
    sync_interval: "on_scan_complete" # on_scan_complete / hourly / daily

    10.5 自定义仪表盘与度量

    创建团队安全度量仪表盘配置:

    #!/usr/bin/env python3
    """
    security_metrics.py – 安全度量数据收集与仪表盘数据生成
    追踪团队安全趋势,为管理决策提供数据支撑
    """

    import json
    import sqlite3
    from datetime import datetime, timedelta
    from pathlib import Path
    from typing import Dict, List

    class SecurityMetricsDB:
    """安全度量数据库管理"""

    def __init__(self, db_path: str = './reports/metrics/security_metrics.db'):
    """初始化 SQLite 数据库"""
    Path(db_path).parent.mkdir(parents=True, exist_ok=True)
    self.conn = sqlite3.connect(db_path)
    self._init_tables()

    def _init_tables(self):
    """初始化数据表"""
    cursor = self.conn.cursor()

    # 扫描记录表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS scan_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    scan_date TEXT NOT NULL,
    project TEXT NOT NULL,
    scan_type TEXT NOT NULL, — SAST/DAST/Dependency
    total_findings INTEGER DEFAULT 0,
    critical_count INTEGER DEFAULT 0,
    high_count INTEGER DEFAULT 0,
    medium_count INTEGER DEFAULT 0,
    low_count INTEGER DEFAULT 0,
    info_count INTEGER DEFAULT 0,
    risk_score INTEGER DEFAULT 0,
    scan_duration_seconds REAL DEFAULT 0,
    tool_version TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
    )
    '''
    )

    # 漏洞修复记录表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS fix_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    finding_id TEXT NOT NULL,
    rule_id TEXT NOT NULL,
    severity TEXT NOT NULL,
    file_path TEXT,
    detected_at TEXT NOT NULL,
    fixed_at TEXT,
    fix_duration_hours REAL,
    fixed_by TEXT,
    fix_commit TEXT,
    status TEXT DEFAULT 'open' — open/fixed/false_positive/wont_fix
    )
    '''
    )

    # 团队安全指标表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS team_metrics (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    period TEXT NOT NULL, — 统计周期 (weekly/monthly)
    period_start TEXT NOT NULL,
    period_end TEXT NOT NULL,
    team_name TEXT NOT NULL,
    total_scans INTEGER DEFAULT 0,
    total_findings INTEGER DEFAULT 0,
    findings_fixed INTEGER DEFAULT 0,
    avg_fix_time_hours REAL DEFAULT 0,
    false_positive_rate REAL DEFAULT 0,
    code_coverage_percent REAL DEFAULT 0,
    security_training_completed INTEGER DEFAULT 0,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
    )
    '''
    )

    self.conn.commit()

    def record_scan(self, scan_data: Dict) > None:
    """记录一次扫描结果"""
    cursor = self.conn.cursor()
    cursor.execute('''
    INSERT INTO scan_records
    (scan_date, project, scan_type, total_findings,
    critical_count, high_count, medium_count, low_count,
    info_count, risk_score, scan_duration_seconds, tool_version)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    '''
    , (
    scan_data['scan_date'],
    scan_data['project'],
    scan_data['scan_type'],
    scan_data['total_findings'],
    scan_data.get('critical_count', 0),
    scan_data.get('high_count', 0),
    scan_data.get('medium_count', 0),
    scan_data.get('low_count', 0),
    scan_data.get('info_count', 0),
    scan_data.get('risk_score', 0),
    scan_data.get('duration', 0),
    scan_data.get('tool_version', '')
    ))
    self.conn.commit()

    def get_trend_data(self, project: str, days: int = 30) > List[Dict]:
    """获取趋势数据(用于仪表盘图表)"""
    cursor = self.conn.cursor()
    since_date = (datetime.now() timedelta(days=days)).isoformat()

    cursor.execute('''
    SELECT scan_date, scan_type, total_findings,
    critical_count, high_count, risk_score
    FROM scan_records
    WHERE project = ? AND scan_date >= ?
    ORDER BY scan_date ASC
    '''
    , (project, since_date))

    columns = ['date', 'type', 'total', 'critical', 'high', 'risk_score']
    return [dict(zip(columns, row)) for row in cursor.fetchall()]

    def generate_dashboard_data(self, project: str) > Dict:
    """生成仪表盘数据(JSON 格式,可供前端渲染)"""
    trend = self.get_trend_data(project, days=30)

    # 计算关键指标
    total_scans = len(trend)
    avg_findings = sum(t['total'] for t in trend) / max(total_scans, 1)
    latest_risk = trend[1]['risk_score'] if trend else 0

    # 趋势判断(与7天前对比)
    week_ago = [t for t in trend if t['date'] >= (datetime.now() timedelta(days=7)).isoformat()]
    prev_week = [t for t in trend if t['date'] < (datetime.now() timedelta(days=7)).isoformat()]

    trend_direction = 'stable'
    if week_ago and prev_week:
    recent_avg = sum(t['total'] for t in week_ago) / len(week_ago)
    prev_avg = sum(t['total'] for t in prev_week) / max(len(prev_week), 1)
    if recent_avg > prev_avg * 1.2:
    trend_direction = 'worsening'
    elif recent_avg < prev_avg * 0.8:
    trend_direction = 'improving'

    return {
    'project': project,
    'generated_at': datetime.now().isoformat(),
    'summary': {
    'total_scans_30d': total_scans,
    'avg_findings_per_scan': round(avg_findings, 1),
    'latest_risk_score': latest_risk,
    'trend_direction': trend_direction
    },
    'trend_data': trend,
    'recommendations': self._generate_recommendations(trend)
    }

    def _generate_recommendations(self, trend: List[Dict]) > List[str]:
    """基于趋势数据生成建议"""
    recommendations = []

    if not trend:
    return ["暂无数据,请先运行安全扫描"]

    latest = trend[1]

    if latest['critical'] > 0:
    recommendations.append(f"⚠️ 当前有 {latest['critical']} 个 Critical 级别问题需要立即处理")

    if latest['risk_score'] > 70:
    recommendations.append("🔴 风险评分过高,建议暂停新功能开发,集中修复安全问题")
    elif latest['risk_score'] > 50:
    recommendations.append("🟠 风险评分偏高,建议本迭代优先处理安全问题")

    # 检查趋势
    if len(trend) >= 7:
    recent_7 = trend[7:]
    if all(t['total'] > 10 for t in recent_7):
    recommendations.append("📈 连续7天发现数超过10,建议加强安全编码培训")

    if not recommendations:
    recommendations.append("✅ 安全状况良好,请继续保持当前实践")

    return recommendations

    def close(self):
    """关闭数据库连接"""
    self.conn.close()

    # ==================== 使用示例 ====================
    if __name__ == '__main__':
    print("╔══════════════════════════════════════════╗")
    print("║ 安全度量数据收集 ║")
    print("╚══════════════════════════════════════════╝")

    db = SecurityMetricsDB()

    # 记录本次扫描
    scan_record = {
    'scan_date': datetime.now().isoformat(),
    'project': 'secure-blog-api',
    'scan_type': 'SAST',
    'total_findings': 8,
    'critical_count': 2,
    'high_count': 3,
    'medium_count': 2,
    'low_count': 1,
    'info_count': 0,
    'risk_score': 65,
    'duration': 12.5,
    'tool_version': 'semgrep 1.70.0'
    }

    db.record_scan(scan_record)
    print(f" ✅ 扫描记录已保存")

    # 生成仪表盘数据
    dashboard = db.generate_dashboard_data('secure-blog-api')

    # 输出仪表盘 JSON
    output_path = './reports/metrics/dashboard-data.json'
    with open(output_path, 'w', encoding='utf-8') as f:
    json.dump(dashboard, f, ensure_ascii=False, indent=2)

    print(f" ✅ 仪表盘数据已生成: {output_path}")
    print(f"\\n 📊 风险评分: {dashboard['summary']['latest_risk_score']}/100")
    print(f" 📈 趋势: {dashboard['summary']['trend_direction']}")
    for rec in dashboard['recommendations']:
    print(f" {rec}")

    db.close()


    常见陷阱与问题排除

    本节汇总在使用 Cursor 安全插件链过程中最常遇到的陷阱和错误,提供快速解决方案。

    陷阱一:Semgrep 规则匹配不到预期代码

    现象:规则语法正确,但扫描时不报告预期的漏洞。

    原因:

    • 模式匹配过于严格(缩进、空格、换行不一致)
    • 语言标识不正确
    • 被 pattern-not 意外排除

    解决方案:

    # 1. 使用 –debug 查看详细匹配过程
    semgrep –config my-rule.yaml –debug target-file.js 2>&1 | grep -A5 "matching"

    # 2. 使用 Semgrep Playground 在线调试
    # 访问 https://semgrep.dev/playground
    # 粘贴代码和规则,实时查看匹配结果

    # 3. 简化规则逐步添加条件
    # 先测试最基础的 pattern,确认能匹配
    # 再逐步添加 pattern-not、metavariable-regex 等条件

    # 4. 检查语言标识
    # JavaScript 文件必须用: languages: [javascript]
    # TypeScript 文件必须用: languages: [typescript]
    # 两者通用: languages: [javascript, typescript]

    陷阱二:Cursor 扩展与 CLI 版本不一致

    现象:CLI 扫描正常,但 Cursor 中无提示或报错。

    解决方案:

    # 检查 CLI 版本
    semgrep –version

    # 检查扩展版本
    # Cursor → Extensions → 搜索 Semgrep → 查看版本号

    # 确保兼容:扩展版本应 >= CLI 版本
    # 如果不一致:
    pip3 install –upgrade semgrep
    # 然后重启 Cursor(Ctrl+Shift+P → "Reload Window")

    陷阱三:大型 monorepo 扫描极慢

    现象:扫描耗时超过 10 分钟,Cursor 卡顿。

    解决方案:

    # 1. 精确配置 .semgrepignore
    # 排除所有非源码目录

    # 2. 使用 –include 只扫描目标语言
    semgrep –config auto –include="*.ts" –include="*.tsx" ./packages/

    # 3. 分模块扫描(在 tasks.json 中配置多个任务)
    # 4. 关闭实时扫描,仅保存时扫描
    # 5. 增加 –jobs 并行度
    semgrep –config auto –jobs 8 ./src/

    陷阱四:Docker 中 ZAP 无法访问 localhost

    现象:ZAP 容器报告目标不可达。

    原因:Docker 容器中的 localhost 指向容器自身,而非宿主机。

    解决方案:

    # 方案1:使用 host 网络模式
    docker run –network="host" ghcr.io/zaproxy/zaproxy:stable ...

    # 方案2:使用宿主机 IP
    # macOS/Windows: host.docker.internal
    # Linux: 172.17.0.1 或 –add-host=host.docker.internal:host-gateway

    docker run –add-host=host.docker.internal:host-gateway \\
    ghcr.io/zaproxy/zaproxy:stable \\
    zap-baseline.py -t http://host.docker.internal:3000

    # 方案3:将应用也放入 Docker 网络
    docker network create security-test-net
    docker run –network security-test-net –name myapp myapp:latest
    docker run –network security-test-net ghcr.io/zaproxy/zaproxy:stable \\
    zap-baseline.py -t http://myapp:3000

    陷阱五:AI 提示词产生幻觉(误报不存在的漏洞)

    现象:Cursor AI 报告了代码中实际不存在的安全问题。

    解决方案:

    # 在提示词中添加约束:

    "请仅基于我提供的代码进行分析,不要假设代码中存在未展示的逻辑。
    如果无法确定某个问题是否真实存在,请标注为'需要人工确认'。
    不要编造代码中不存在的函数调用或变量。"

    # 同时要求 AI 给出证据:
    "对每个发现,请引用具体的代码行作为证据。"

    陷阱六:Git Hooks 导致无法正常提交

    现象:pre-commit hook 中的安全扫描阻止了紧急修复提交。

    解决方案:

    # 紧急情况下跳过 hook(不推荐常规使用)
    git commit –no-verify -m "hotfix: urgent security patch"

    # 更好的方案:在 hook 中添加超时和降级机制
    # 在 pre-commit 脚本中:
    timeout 60 semgrep –config auto ./src || echo "⚠️ 扫描超时,允许提交"

    # 或者配置为仅警告不阻塞:
    semgrep –config auto ./src –severity=ERROR –error || true

    陷阱七:环境变量未正确加载

    现象:process.env.JWT_SECRET_KEY 为 undefined。

    解决方案:

    # 1. 确认 .env 文件存在且格式正确
    # .env
    JWT_SECRET_KEY=your-256-bit-secret-key-here
    DB_HOST=localhost
    DB_PASSWORD=your-password

    # 2. 确认 .env 在 .gitignore 中
    echo ".env" >> .gitignore

    # 3. 在应用入口加载
    # app.js 顶部
    require('dotenv').config();

    # 4. Cursor 终端中确认
    echo $JWT_SECRET_KEY # 应输出值

    # 5. 如果使用 Docker,通过 –env-file 传入
    docker run –env-file .env myapp:latest


    总结

    本教程从"安全左移"的核心理念出发,系统性地介绍了如何在 Cursor IDE 中构建一套完整的 SAST/DAST 辅助分析工作流。以下是各章节的核心要点回顾:

    核心收获

    章节核心技能关键工具
    理解安全左移理念
    环境搭建与插件安装 Semgrep, SonarLint, Bandit, ZAP
    自定义 SAST 规则 Semgrep YAML 规则语法
    DAST 动态测试 OWASP ZAP, 自定义扫描脚本
    AI 安全提示词工程 Cursor AI, .cursorrules
    漏洞修复闭环 参数化查询, bcrypt, DOMPurify
    误报优化 taint 模式, 白名单, 上下文过滤
    团队协作标准化 Git Hooks, Review 清单, 培训计划
    故障排查 版本管理, 网络配置, 性能优化
    CI/CD 与进阶 GitHub Actions, Jira 对接, 度量仪表盘

    实施路线图建议

    第 1 周(基础搭建):

    • 完成环境搭建(第二章)
    • 安装核心插件并验证基本功能
    • 编写第一批基础安全规则

    第 2-3 周(规则完善):

    • 根据项目技术栈定制规则(第三章)
    • 配置 AI 安全审查提示词(第五章)
    • 完成第一次完整的安全扫描

    第 4 周(流程集成):

    • 配置 Git Hooks(第八章)
    • 建立 Code Review 安全检查清单
    • 完成第一次团队安全培训

    第 2 个月(优化迭代):

    • 根据误报反馈优化规则(第七章)
    • 集成 CI/CD 管道(第十章)
    • 建立安全度量体系

    第 3 个月及以后(持续改进):

    • 扩展私有规则库
    • 对接漏洞管理平台
    • 定期安全培训与演练

    关键原则

  • 渐进式引入:不要试图一次性配置所有规则,从高危漏洞开始逐步扩展
  • 平衡安全与效率:过多的检查会降低开发体验,找到团队可接受的平衡点
  • 持续优化:定期回顾误报和漏报,迭代改进规则
  • 工具为辅,意识为主:工具只能发现已知模式的漏洞,安全编码意识才是根本
  • 自动化优先:能自动化的检查绝不依赖人工,减少遗漏

  • 详细参考资料

    官方文档

    资源链接说明
    Semgrep 官方文档 https://semgrep.dev/docs/ 规则编写、CLI 使用
    Semgrep 规则注册表 https://semgrep.dev/explore/ 社区规则库
    OWASP ZAP 文档 https://www.zaproxy.org/docs/ DAST 工具使用
    OWASP Top 10 https://owasp.org/Top10/ 十大 Web 安全风险
    Cursor 官方文档 https://docs.cursor.com/ IDE 使用指南
    Bandit 文档 https://bandit.readthedocs.io/ Python 安全扫描
    ESLint Security https://github.com/nodesecurity/eslint-plugin-security JS 安全插件
    SonarLint 文档 https://docs.sonarsource.com/sonarlint/ 代码质量

    CWE/OWASP 参考

    CWE 编号漏洞类型对应章节
    CWE-78 OS 命令注入 3.3, 6.2
    CWE-79 跨站脚本 (XSS) 3.3, 5.3
    CWE-89 SQL 注入 3.2, 5.2, 6.2
    CWE-22 路径遍历 4.4
    CWE-330 不安全随机数 3.3.3
    CWE-502 不安全反序列化 3.3.2
    CWE-532 日志信息泄露 3.2.1, 5.5
    CWE-639 越权访问 (IDOR) 5.4, 6.2
    CWE-798 硬编码凭证 3.3.2, 5.5

    推荐书籍

    • 《Web 应用安全权威指南》- 德丸浩
    • 《白帽子讲 Web 安全》- 吴翰清
    • 《Secure Coding in C and C++》- Robert C. Seacord
    • 《The Tangled Web》- Michal Zalewski
    • 《OWASP Testing Guide》- OWASP Foundation

    社区与学习资源

    • OWASP 本地分会:参加线下安全交流活动
    • Semgrep Slack:https://semgrep.dev/slack(规则编写交流)
    • CTF 练习平台:DVWA、WebGoat、HackTheBox
    • GitHub 安全规则库:
      • https://github.com/semgrep/semgrep-rules
      • https://github.com/trailofbits/semgrep-rules
      • https://github.com/returntocorp/semgrep-rules

    附录

    附录 A:完整规则文件模板

    # semgrep-rules/TEMPLATE.yaml
    # Semgrep 规则模板 – 复制此文件创建新规则
    # 文档: https://semgrep.dev/docs/writing-rules/rule-syntax/

    rules:
    # ============================================
    # 规则模板 1:简单模式匹配
    # ============================================
    id: templatesimplepattern
    # 匹配模式:使用目标语言的实际语法
    pattern: |
    dangerous_function($ARG)

    # 检测到时的提示信息(支持多行)
    message: >
    检测到使用 dangerous_function()。
    此函数存在安全风险,请使用 safe_alternative() 替代。

    # 适用语言(可多选)
    languages:
    javascript
    typescript
    # 严重等级:INFO / WARNING / ERROR
    severity: WARNING
    # 元数据(用于分类和报告)
    metadata:
    category: security
    cwe: "CWE-XXX"
    owasp: "AXX:2021"
    confidence: MEDIUM # LOW / MEDIUM / HIGH / VERY_HIGH
    likelihood: MEDIUM
    impact: MEDIUM
    subcategory:
    audit # vuln(确定漏洞) / audit(需确认) / guardrail(防护)
    references:
    https://owasp.org/Top10/
    technology:
    nodejs

    # ============================================
    # 规则模板 2:多模式匹配(任一匹配即报警)
    # ============================================
    id: templatemultipattern
    patterns:
    pattern-either:
    pattern: func_a($X)
    pattern: func_b($X)
    pattern: func_c($X)
    # 排除条件
    pattern-not: func_a("safe_literal")
    message: "检测到危险函数调用"
    languages: [javascript]
    severity: ERROR

    # ============================================
    # 规则模板 3:污点追踪模式(数据流分析)
    # ============================================
    id: templatetainttracking
    mode: taint
    # 不可信数据来源(污点源)
    pattern-sources:
    pattern: req.query.$PARAM
    pattern: req.body.$PARAM
    pattern: req.params.$PARAM
    pattern: req.headers.$PARAM
    pattern: window.location.$PROP
    # 危险操作(污点汇)
    pattern-sinks:
    pattern: db.query($SQL)
    pattern: eval($CODE)
    pattern: document.write($HTML)
    pattern: $EL.innerHTML = $VAL
    # 净化操作(经过处理的数据视为安全)
    pattern-sanitizers:
    pattern: sanitize($X)
    pattern: escape($X)
    pattern: parseInt($X)
    pattern: DOMPurify.sanitize($X)
    message: >
    不可信数据从 $SOURCE 流入危险操作,
    中间未经过有效的安全净化处理。

    languages: [javascript, typescript]
    severity: ERROR
    metadata:
    confidence: VERY_HIGH

    # ============================================
    # 规则模板 4:带自动修复的规则
    # ============================================
    id: templatewithfix
    pattern: |
    md5($INPUT)

    fix: |
    bcrypt.hash($INPUT, 12)

    message: >
    MD5 不是安全的哈希算法,请使用 bcrypt。
    已提供自动修复建议。

    languages: [python]
    severity: ERROR
    metadata:
    cwe: "CWE-328"

    附录 B:提示词速查表

    场景提示词模板快捷键
    快速安全扫描 “扫描这段代码的安全问题,按严重性排序列出” Ctrl+L
    SQL 注入检查 “检查此代码是否存在 SQL 注入,关注字符串拼接和参数化” Ctrl+L
    XSS 检查 “检查此代码的 XSS 风险,关注 innerHTML/v-html/模板输出” Ctrl+L
    认证检查 “审查认证逻辑:密码哈希、token 过期、暴力破解防护” Ctrl+L
    修复建议 “给出此漏洞的完整修复代码,包含输入验证和错误处理” Ctrl+K
    代码审查 “以安全工程师角色审查此 PR 代码,输出安全检查清单” Ctrl+L
    解释漏洞 “用通俗语言解释这个漏洞的攻击原理和影响” Ctrl+L
    生成测试 “为这个安全修复编写单元测试,验证漏洞已修复” Ctrl+K
    规则编写 “为以下漏洞模式编写 Semgrep 规则:[描述漏洞模式]” Ctrl+L
    配置审查 “检查此配置文件的安全问题:CORS/CSP/安全头/调试模式” Ctrl+L

    提示词编写黄金公式:

    [角色] + [任务] + [上下文/约束] + [输出格式] + [注意事项]

    示例:
    "你是一位资深安全工程师(角色),
    请审查以下 Express.js 代码中的注入漏洞(任务),
    重点关注 OWASP Top 10 中的 A03 注入类漏洞(约束),
    以表格形式输出:漏洞类型|严重等级|位置|修复方案(格式),
    只报告确定的问题,不确定的标注'需确认'(注意事项)。"

    附录 C:故障排查决策树

    安全插件链异常

    ├─ 插件无法安装
    │ ├─ 网络错误 → 检查代理/镜像源
    │ ├─ 权限不足 → sudo / 管理员权限
    │ └─ 版本不兼容 → 检查 Node/Python 版本

    ├─ 插件已安装但不工作
    │ ├─ 无输出/无提示
    │ │ ├─ 检查 CLI 是否在 PATH 中 → which semgrep
    │ │ ├─ 检查扩展设置路径是否正确
    │ │ └─ 重启 Cursor (Reload Window)
    │ ├─ 报错退出
    │ │ ├─ 查看 Output 面板 (View → Output → 选择扩展)
    │ │ ├─ 检查规则 YAML 语法
    │ │ └─ 检查文件编码 (UTF-8)
    │ └─ 扫描超时
    │ ├─ 添加 .semgrepignore 排除大目录
    │ ├─ 减少规则数量
    │ └─ 增加 timeout 配置

    ├─ 扫描结果不准确
    │ ├─ 误报太多
    │ │ ├─ 添加 pattern-not 排除条件
    │ │ ├─ 使用 taint 模式替代简单匹配
    │ │ ├─ 配置 .semgrepignore
    │ │ └─ 使用 nosemgrep 注释抑制
    │ └─ 漏报
    │ ├─ 检查规则是否覆盖目标语言
    │ ├─ 添加更多 pattern-either 分支
    │ ├─ 启用更多官方规则集
    │ └─ 使用 Semgrep Playground 调试

    ├─ DAST 扫描失败
    │ ├─ 目标不可达 → 确认应用已启动
    │ ├─ Docker 网络问题 → 使用 –network host
    │ ├─ 认证失败 → 配置 ZAP 认证脚本
    │ └─ 超时 → 增加 timeout 参数

    └─ CI/CD 集成失败
    ├─ 环境变量未设置 → 配置 Secrets
    ├─ 权限不足 → 检查 token 权限
    ├─ 依赖安装失败 → 使用缓存
    └─ 报告上传失败 → 检查 artifact 配置

    附录 D:推荐学习资源与进阶路径

    入门阶段(1-2 周):

    资源类型时长
    OWASP Top 10 官方文档 阅读 4h
    Semgrep 官方教程 互动 3h
    DVWA 漏洞靶场练习 实操 8h
    Cursor 官方文档 阅读 2h

    进阶阶段(3-4 周):

    资源类型时长
    Semgrep 规则编写高级教程 实操 6h
    OWASP ZAP 完整指南 阅读+实操 8h
    WebGoat 全关卡通关 实操 16h
    安全代码审查实践 实操 持续

    高级阶段(1-3 月):

    资源类型时长
    自定义 taint 分析规则 实操 持续
    CI/CD 安全管道设计 设计+实施 2周
    企业安全度量体系 设计 1周
    安全培训体系建设 规划 持续
    CTF 竞赛参与 实操 持续

    认证推荐:

    • CSSLP(Certified Secure Software Lifecycle Professional)
    • CEH(Certified Ethical Hacker)
    • CompTIA Security+
    • OWASP 相关培训认证

    附录 E:项目完整文件清单

    以下是本教程涉及的完整项目文件结构,可作为实际项目的参考模板:

    secure-blog-api/

    ├── .cursor/
    │ └── rules/
    │ └── security-review.mdc # Cursor AI 安全审查规则

    ├── .cursorrules # 项目级 AI 配置
    ├── .eslintrc.json # ESLint 安全规则配置
    ├── .semgrepignore # Semgrep 忽略文件
    ├── .env.example # 环境变量模板
    ├── .gitignore # Git 忽略(含 .env)
    ├── package.json # 项目依赖
    ├── SECURITY_GUIDELINES.md # 安全编码规范
    ├── tracker-config.yaml # 漏洞平台对接配置

    ├── .husky/
    │ ├── pre-commit # 提交前安全检查
    │ └── pre-push # 推送前完整扫描

    ├── .vscode/
    │ ├── settings.json # Cursor/VS Code 设置
    │ └── tasks.json # 自动化任务配置

    ├── src/
    │ ├── app.js # 应用入口
    │ ├── routes/ # 路由定义
    │ ├── controllers/ # 控制器(含安全修复)
    │ ├── middleware/
    │ │ ├── auth.js # JWT 认证中间件
    │ │ ├── validate.js # 输入验证中间件
    │ │ ├── rateLimiter.js # 速率限制中间件
    │ │ └── errorHandler.js # 安全错误处理
    │ ├── models/ # 数据模型
    │ └── utils/
    │ ├── sanitize.js # 输入净化
    │ ├── logger.js # 安全日志
    │ └── crypto.js # 加密工具

    ├── semgrep-rules/ # 自定义 SAST 规则
    │ ├── injection/
    │ │ ├── sql-injection.yaml
    │ │ ├── command-injection.yaml
    │ │ └── test-sql-injection.js # 规则测试用例
    │ ├── xss/
    │ │ ├── reflected-xss.yaml
    │ │ └── stored-xss.yaml
    │ ├── auth/
    │ │ ├── weak-crypto.yaml
    │ │ ├── hardcoded-secrets.yaml
    │ │ └── insecure-session.yaml
    │ └── business/
    │ ├── payment-security.yaml
    │ └── pii-handling.yaml

    ├── scripts/
    │ ├── dast-scan.sh # DAST 扫描脚本
    │ ├── dast-workflow.sh # 完整 DAST 工作流
    │ ├── api_security_scanner.py # API 安全扫描器
    │ ├── parse_dast_report.py # DAST 报告解析
    │ ├── generate_security_report.py # 综合报告生成
    │ ├── sync_to_tracker.py # 漏洞平台同步
    │ ├── security_metrics.py # 安全度量收集
    │ └── feedback-loop.sh # 误报反馈收集

    ├── reports/ # 扫描报告输出目录
    │ ├── semgrep-results.json
    │ ├── npm-audit.json
    │ ├── dast/
    │ │ ├── zap-report-*.html
    │ │ ├── zap-alerts-*.json
    │ │ └── fix-suggestions.md
    │ └── metrics/
    │ ├── security_metrics.db
    │ └── dashboard-data.json

    ├── tests/
    │ ├── security/
    │ │ ├── test-sql-injection.js # SQL 注入修复验证
    │ │ ├── test-xss.js # XSS 修复验证
    │ │ └── test-auth.js # 认证安全验证
    │ └── integration/

    └── .github/
    └── workflows/
    └── security-scan.yml # CI/CD 安全管道

    附录 F:快速启动命令速查

    # ===== 环境初始化 =====
    pip3 install semgrep bandit # 安装 Python 安全工具
    npm install -D eslint eslint-plugin-security # 安装 JS 安全插件
    docker pull ghcr.io/zaproxy/zaproxy:stable # 拉取 ZAP 镜像

    # ===== 日常扫描 =====
    semgrep –config auto –config ./semgrep-rules/ ./src # SAST 全量扫描
    semgrep –config auto ${file} # 扫描当前文件
    npx eslint –ext .js,.ts ./src –plugin security # ESLint 安全检查
    bandit -r ./src -ll # Python 安全扫描

    # ===== DAST 扫描 =====
    ./scripts/dast-workflow.sh http://localhost:3000 # 完整 DAST 流程
    python3 scripts/api_security_scanner.py # API 安全测试

    # ===== 报告生成 =====
    python3 scripts/generate_security_report.py –format html # HTML 报告
    python3 scripts/generate_security_report.py –format markdown # MD 报告

    # ===== 规则管理 =====
    semgrep –config ./semgrep-rules/ –validate # 验证规则语法
    semgrep –config ./semgrep-rules/ –test # 运行规则测试
    semgrep –config ./semgrep-rules/ –dryrun ./src # 试运行

    # ===== CI/CD =====
    semgrep –config auto –sarif -o results.sarif . # SARIF 格式输出
    semgrep –config auto –json -o results.json . # JSON 格式输出
    semgrep –config auto –error –severity ERROR . # 仅 ERROR 阻塞

    # ===== 故障排查 =====
    semgrep –version # 检查版本
    semgrep –config ./rules/ –debug ./src 2>&1 | head -50 # 调试模式
    python3 -c "import yaml; yaml.safe_load(open('rules.yaml'))" # YAML 验证


    本文完

    本教程版本:v1.0 | 最后更新:2026 年 8 月

    适用工具版本:Cursor 0.40+, Semgrep 1.70+, OWASP ZAP 2.15+, Node.js 20 LTS, Python 3.11+

    如有问题或建议,欢迎在团队安全频道中交流讨论。安全是一个持续改进的过程,愿每一位开发者都能编写出安全、可靠的代码。



    赞(0)
    未经允许不得转载:171主机测评 » Cursor 安全插件链:构建实时 SAST/DAST 辅助分析工作流
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址