ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Cursor安全插件链实战:用SAST+LLM搭建代码审计新范式

Cursor安全插件链实战:用SAST+LLM搭建代码审计新范式 1. 为什么我把代码审计搬进了 Cursor代码审计这件事过去在我手里一直是「发布前卡点」的角色。CI 流水线跑一遍 Semgrep 或者 SonarQube报告出来几百条安全同学筛一遍开发同学再改一遍一个越权漏洞从写出来到被修掉中间可能隔了一周。更麻烦的是很多告警在业务上下文里根本站不住脚——测试文件里的 mock 密钥、内部工具里的固定 token全被当成高危拦下来久而久之大家对扫描报告就麻木了。Cursor 这类 AI 编辑器改变了写代码的方式但「AI 生成的代码安全吗」这个问题反而更尖锐了。模型补全出来的代码语法没问题、逻辑也通可它不会主动帮你加鉴权、不会校验资源归属、更不会告诉你引入的这个依赖版本有已知 CVE。如果审计还停留在提交之后那 AI 写得越快欠的安全债就越多。我想要的是一种「编码即审计」的流程在 Cursor 里敲代码、保存、甚至让 AI 补全的那一刻SAST 规则先扫一遍抓可疑点再由 LLM 结合业务上下文做二次定性最后把修复建议直接以行内提示的形式怼到我脸上。这就是所谓的 Cursor 安全插件链——不是某一个插件而是把静态扫描引擎和语义审计模型串成一条可重复执行的链路。这篇会给出可复制的插件链配置骨架包括用 TaoToken 统一 Key 打通 LLM 审计通道的接入点以及一次从扫描到修复建议的完整验证动作。适合需要把安全扫描接进日常编码流程、又不想被 CI 报告淹没的开发者。2. 前置准备TaoToken 统一 Key 与 Cursor 环境插件链里最容易被忽略的一环是「LLM 审计通道怎么接」。Cursor 本身支持自定义模型接入但如果你要在插件脚本里独立调用审计模型就需要一个稳定的 API 入口。我这边用的是 TaoToken 的统一 Key好处是一个 Key 能覆盖对话、编码、Agent 几类场景不用在多个平台之间来回切。先拿到 Key。打开 https://taotoken.net/api-keys 创建复制出来存到环境变量里别硬编码进脚本。模型对话的调试入口在 https://taotoken.net/models 接入文档在 https://taotoken.net/doc 这两个页面建议先过一眼后面配参数会用到。环境变量这样设Linux/macOS 写进~/.zshrc或~/.bashrcexport TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell 用$env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/apiCursor 侧需要确认两件事一是 Settings 里开启了本地插件/脚本执行权限二是项目根目录有.cursor文件夹用来放规则和脚本。我习惯在项目里建一个security/目录专门放插件链的配置和扫描脚本跟业务代码隔离避免误提交。注意API Key 只放环境变量或本地.env记得加进.gitignore任何情况下不要写进会被提交的配置文件。3. 可复制的插件链配置骨架插件链的核心思路是「SAST 抓点 → LLM 定性 → 行内反馈」三段式。下面这套骨架可以直接抄改改路径就能跑。3.1 目录结构project/ ├── .cursor/ │ └── rules/ │ └── security-audit.mdc ├── security/ │ ├── chain.config.json │ ├── sast_scan.py │ └── llm_audit.py └── src/3.2 链式编排配置security/chain.config.json定义每一链的输入输出和触发时机{ chain: [ { name: sast, engine: semgrep, rules: [p/owasp-top-ten, p/python], trigger: [onSave, onAIComplete], output: sast_findings.json }, { name: llm-audit, provider: taotoken, baseUrl: https://taotoken.net/api, model: claude-sonnet, input: sast_findings.json, promptTemplate: security/prompts/audit.md, output: audit_result.json }, { name: feedback, mode: inline, severityThreshold: medium } ] }这里trigger我配了onSave和onAIComplete两个时机前者是手动保存时扫后者是 Cursor 补全落地时扫。severityThreshold设成 medium低危的噪音直接过滤掉不然行内提示会刷屏。3.3 SAST 扫描脚本sast_scan.py负责调 Semgrep 并把结果转成统一格式import json import subprocess import sys def run_sast(target_path): result subprocess.run( [semgrep, --config, p/owasp-top-ten, --json, --quiet, target_path], capture_outputTrue, textTrue ) if result.returncode not in (0, 1): print(fsemgrep error: {result.stderr}, filesys.stderr) sys.exit(1) data json.loads(result.stdout or {}) findings [] for item in data.get(results, []): findings.append({ file: item[path], line: item[start][line], rule: item[check_id], message: item[extra][message], severity: item[extra][severity].lower() }) return findings if __name__ __main__: target sys.argv[1] if len(sys.argv) 1 else src/ out run_sast(target) with open(security/sast_findings.json, w) as f: json.dump(out, f, indent2) print(fSAST done, {len(out)} findings)跑起来是这样python security/sast_scan.py src/ # SAST done, 7 findings3.4 LLM 审计脚本llm_audit.py读 SAST 结果拼上业务上下文调 TaoToken 的 API 做二次定性import json import os import requests API_KEY os.environ[TAOTOKEN_API_KEY] BASE_URL os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) def load_prompt(): with open(security/prompts/audit.md) as f: return f.read() def audit(findings): prompt load_prompt() payload { model: claude-sonnet, messages: [ {role: system, content: prompt}, {role: user, content: json.dumps(findings, ensure_asciiFalse)} ], temperature: 0.1 } resp requests.post( f{BASE_URL}/v1/chat/completions, headers{ Authorization: fBearer {API_KEY}, Content-Type: application/json }, jsonpayload, timeout60 ) resp.raise_for_status() return resp.json()[choices][0][message][content] if __name__ __main__: with open(security/sast_findings.json) as f: findings json.load(f) if not findings: print(no findings, skip llm audit) raise SystemExit(0) result audit(findings) with open(security/audit_result.json, w) as f: f.write(result) print(LLM audit done)security/prompts/audit.md是审计提示词模板核心是让模型结合上下文判断真伪并给修复建议你是代码安全审计专家。输入是一组 SAST 扫描结果每条包含文件、行号、规则和描述。 请对每条 finding 做三件事 1. 判断是否为真实风险结合文件路径和代码语义排除测试/mock 场景的误报 2. 如果是真实风险给出风险等级high/medium/low和一句话业务影响 3. 给出可直接应用的修复代码片段。 输出 JSON 数组每条包含file, line, is_real, severity, impact, fix_snippet。3.5 Cursor 规则文件.cursor/rules/security-audit.mdc让 Cursor 在补全和编辑时感知安全约束--- description: 安全审计规则 globs: [src/**/*.py, src/**/*.js] --- 在生成或修改涉及数据库查询、文件操作、外部请求的代码时 - 必须校验资源归属禁止直接透传用户传入的 ID 到查询条件 - 禁止硬编码密钥、token、密码 - 引入新依赖时提示检查已知漏洞版本。4. 验证请求从扫描到修复建议跑一遍配置搭好后用一段有越权风险的代码验证整条链。在src/order_api.py写from flask import Flask, jsonify, request app Flask(__name__) app.route(/api/order/order_id, methods[GET]) def get_order(order_id): order db.query_one( SELECT * FROM orders WHERE id %s, (order_id,) ) return jsonify(order)先跑 SASTpython security/sast_scan.py src/order_api.py # SAST done, 2 findingssecurity/sast_findings.json里会出现类似「缺少鉴权装饰器」「未校验资源归属」的条目。接着跑 LLM 审计python security/llm_audit.py # LLM audit donesecurity/audit_result.json会给出定性结果大致长这样[ { file: src/order_api.py, line: 6, is_real: true, severity: high, impact: 接口未校验订单归属任意用户可通过遍历 order_id 读取他人订单构成越权访问。, fix_snippet: login_required\ndef get_order(order_id):\n uid get_current_user().id\n order db.query_one(\n \SELECT * FROM orders WHERE id %s AND user_id %s\,\n (order_id, uid)\n )\n if not order:\n return jsonify({\error\: \not found\}), 404\n return jsonify(order) } ]到这一步链路是通的SAST 抓出可疑点LLM 结合业务语义判定为真实越权风险并给出带鉴权和归属校验的修复片段。把这段修复贴回代码再跑一次扫描findings 归零说明闭环成立。如果你想让 Cursor 在补全阶段就介入可以在 Composer 里直接引用security/audit_result.json让它按修复建议改代码。模型对话调试可以用 https://taotoken.net/models 这个入口先验证提示词效果确认输出格式稳定后再固化进脚本。5. 本篇常见错排查Semgrep 报command not found先确认装了 Semgreppip install semgrep或brew install semgrep。如果是在虚拟环境里装的注意 Cursor 调脚本时用的解释器路径要和安装时一致否则会找不到命令。LLM 审计返回 401九成是TAOTOKEN_API_KEY没生效。检查环境变量是否在当前 shell 会话里echo $TAOTOKEN_API_KEY看有没有值。如果是 Cursor 内部调脚本环境变量可能没继承需要在脚本里显式加载.env。审计结果全是误报提示词里没给足上下文。SAST 结果只有文件路径和行号模型看不到代码内容自然判断不准。改进办法是在sast_scan.py里把命中行的前后几行代码一起塞进 findings让模型有据可判。行内提示不显示检查chain.config.json里severityThreshold是不是设太高把 medium 以下的都过滤了。另外 Cursor 的插件反馈依赖规则文件里的globs匹配确认你的文件路径在 glob 范围内。扫描太慢拖累保存onSave触发全量扫描在大型项目里会卡。把 SAST 范围收窄到当前修改的文件用git diff --name-only拿变更列表传给扫描脚本只扫改动部分。依赖漏洞检测没触发插件链默认只扫代码不查依赖库。需要额外加一链在chain.config.json里挂一个 OSV 查询步骤输入是requirements.txt或package.json输出已知 CVE 列表。6. 把审计变成编辑器里的日常动作这套插件链跑通之后我最大的感受是审计的「心理成本」降下来了。以前看到 CI 报告里几十条告警会本能地想拖现在保存文件时行内直接弹出两三条经过语义定性的真实风险顺手就改了。SAST 负责广度LLM 负责精度两者串起来才让「编码即审计」这件事真正可执行。如果你要长期在团队里推这套流程建议把 LLM 审计通道固定下来用 Coding Plan 统一管理调用配额和模型切换避免每个人各配一套 Key 导致审计标准不一致。接入细节和参数说明在 https://taotoken.net/doc 里有完整对照配之前扫一遍能少踩不少坑。
返回列表