前言
近期,我们的一个基于 .NET Framework 的老项目被客户的安全扫描器扫出了 SQL 注入。收到报告的那一刻,我的第一反应不是"修",而是 "除了报告里写的这几个点,系统里到底还有多少我们不知道的?"
当然现在的大环境下,基本的安全问题可能利用WAF,EDR等外挂式的防护进行一些阻断,但这本质上都是在系统外面套了一层壳。而壳是可以被绕过的,系统自身的铜头铁骨,才是兜底的防线。
大语言模型发展到今天,已经具备了难以想象的能力。以前我们做安全审查,可以利用一些工具,比如SAST,DAST,甚至是人工审计,但它们都各有各的不足,要么是语义类判断力不从心,要么是需要运行环境,自动化配置操作过复杂,要么就是慢!效率低下。
而今天的大语言模型在安全审查这件事上,有一个传统工具或工作方式不具备的核心能力:它能"读懂"代码的上下文。
当你告诉它"这个搜索参数是从 HTTP 请求里取的",它能顺着调用链往上追到 Controller 的 Action 方法,再确认这个 Action 有没有登录态保护。这种 source → sink 的全链追踪,以前只有经验丰富的安全工程师能做到,现在 AI 可以批量、持续、不疲劳地执行。
更重要的是,AI 能带着判断干活
本篇笔者将提供一套完整的、经过多个项目实战验证的 AI 驱动安全审查工作流供大家参考,需要说明的是,这里重要的不是工作流的内容,而是这个工作的切入方式和思考角度。你完全可以根据自己的实际情况,来创建适合自己项目的审查工作流。
好了,本篇正文开始。
前置条件
- 一个支持长上下文的 AI 编程助手(如 DeepSeek Harness / Hermes / Claude Code 等,本文以 Hermes 为例)
- 你的项目源码(我这里设置的是 .NET 技术栈,大家根据自身情况配置即可)
- 项目里有一个文档目录
开始操作
Step 1:投放初始化模板
把下面这份初始化模板复制到你的项目文档目录,命名为 安全漏洞检查方法论-初始化模板.md(见本文附录 A):
"
这份模板约 260 行,包含 8 条核心原则、项目画像模板、SQL 注入双路线扫描表(传统 ADO.NET + ORM)、通用攻击清单、验证判据、文档约定、以及 AI 首次接管的完整执行手册。
Step 2:让 AI 接管
在你的 AI 编程助手里,粘贴这段提示词:
请对本项目做一次安全审查。
第一步:读取 {你的文档目录}/安全漏洞检查方法论-初始化模板.md,
执行 §9 首次接管流程------技术栈探测 + 项目画像 + 生成安全基线。
第二步:按基线中的扫描清单开始首轮扫描,产出漏洞台账。
红线:扫描期只读,不改代码;写库测试只用可丢弃数据。
把 {你的文档目录} 替换成实际路径,比如 Docs/安全审查 。
执行效果如下图

Step 3:等待 AI 输出
AI 会自动完成以下工作:
- 读你的 csproj/sln,判断技术栈
- 生成《安全基线.md》,包含项目画像、扫描清单、已知线索
- 执行首轮扫描,产出漏洞台账(JSON 格式)
- 给出修复建议
首次运行大约需要 10-30 分钟(取决于项目大小)。之后你可以用"继续审查"或"推进到下一轮"来驱动多轮审查。本工作流程包含了多轮会话之间的交接文档,详见附录D
"
这里给出我的一个老项目的审查报告
完整工作流详解
工作流全景图
┌─────────────────────────────────────────────────────────┐
│ 初始化模板 v0.3 │
│ (普适方法论,不含任何项目细节) │
└─────────────┬───────────────────────────────────────────┘
│ 投放到项目
▼
┌─────────────────────────────────────────────────────────┐
│ AI 探测 → 裁剪 → 回写 │
│ 读 csproj/sln → 判断 ORM → 裁剪扫描模式 → 生成安全基线 │
└─────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 多轮审查 │
│ 轮次 0:初始化 │
│ 轮次 1:首轮扫描 + 漏洞台账 │
│ 轮次 2:HIGH 级修复 │
│ 轮次 3:MEDIUM 级修复 │
│ 轮次 4:专项修复(文件上传/JWT/鉴权...) │
│ 轮次 5+:验证、回归、收尾 │
└─────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 三层验证 │
│ ① 编译验证(dotnet build 零错误) │
│ ② SQL 层回归(临时控制台直连数据库) │
│ ③ HTTP 层全链路(起站点、登录、发请求、验证响应) │
└─────────────────────────────────────────────────────────┘
文档体系(四件套)
每轮审查都会产出/更新以下文档:
| 文档 | 作用 | 位置 |
|---|---|---|
| 安全基线 | 项目画像 + 扫描清单 + 红线 | 安全审查/安全基线.md |
| 漏洞台账 | 唯一的状态权威,JSON 格式 | 安全审查/_review-ledger-{项目名}.md |
| 进度看板 | 从台账汇总的概览 | 安全审查/安全审查进度看板.md |
| CHANGELOG | 按时间倒序的变更记录 | 安全审查/CHANGELOG-安全审查.md |
| 交接文档 | 跨会话的上下文续接 | 安全审查/记录/_session-handoff-*.md |
关键规则 :台账是明细、看板是概览,看板的计数必须从台账汇总,禁止另写一套。这是我们踩过坑的教训------曾经出现过台账 18 条、看板写 19 条的情况。
漏洞台账格式
每条漏洞用 JSON 记录,结构如下:
{
"id": "VULN-001",
"pattern_code": "A1_StringInterpolation",
"sink_file": "xxx.cs",
"sink_line": 142,
"source_entry": "xxx(string keyword)",
"is_controllable": true,
"risk_level": "HIGH",
"status": "OPEN",
"repair_strategy": "Parameterization",
"note": "keyword 来自 HTTP GET 参数,用户完全可控"
}
状态流转:OPEN → IN_PROGRESS → FIXED → VERIFIED
- OPEN:发现但未修
- IN_PROGRESS:已落码 + 编译通过,但未做回归自测
- FIXED:SQL 层回归自测通过
- VERIFIED:HTTP 层全链路验证通过
- REJECTED_UNCONTROLLABLE:确认不可控(如 int 类型约束、死代码无调用方)
"
这里给出一个我这里审查完的目录结构
导入为 AI Skill(推荐)
这个 Skill 的设计哲学是薄壳------它不复制方法论的全文,只做两件事:
- 接管引导:告诉 AI "开工前先跑 preflight、先读哪两份文档"
- 硬约束:列出该项目会咬人的规矩(如仓库边界、状态语义、证据文件纪律)
方法论的正文始终以项目内的《安全基线.md》和《初始化模板.md》为准,Skill 只做索引。
preflight.py 自动化预检
"
文件 :[preflight.py 通用模板](#preflight.py 通用模板)(见本文附录 C,内容多但不用人看,扔给模型即可)
这个脚本在每轮审查开始和结束时运行,自动检查:
| 检查项 | 说明 |
|---|---|
| 仓库状态 | HEAD 是否与远端一致、有无未提交改动 |
| 台账自洽 | JSON 可解析、ID 连续、条目数 = 状态计数之和 |
| 路径存在性 | 台账/看板/CHANGELOG 文件是否在预期位置 |
| 证据文件 | assets 目录是否意外提交了大文件(>1MB) |
| 凭据泄露 | 是否有密码/密钥/连接串被跟踪 |
| 日期自洽 | 轮次提交日期与交接文档日期是否一致 |
退出码 0 = 全通过,退出码 1 = 有 FAIL 项,先修再开工。
"
我在早期执行时,出现过 AI 把台账修了但忘记更新看板的情况,甚至出现过上一轮说'修好了'但代码没提交的乌龙。为了解决这类'人工疏忽',才写了 preflight 脚本做机械自查。
适用场景与局限
适合用这套流程的场景
- .NET 项目(Framework 4.x / Core / .NET 5+),有历史代码需要体检
- 使用 ADO.NET / EF Core / FreeSql / Dapper 等数据访问方式
- 希望有结构化的审查记录(不是"扫一遍看报告"就完事)
- 愿意投入多轮审查,逐步深入
当前局限
- 主要覆盖 SQL 注入,其他攻击类型(XSS/CSRF/越权)只做了速查清单,未做深度专项
- 需要人工参与验证:编译和 SQL 层可以自动化,但 HTTP 层全链路测试往往需要登录态、特定业务数据
- 依赖 AI 的代码理解能力:对于非常复杂的动态 SQL 生成(如运行时拼接 LINQ 表达式树),AI 可能误判
- 不是渗透测试:这是代码层面的安全审查,不替代专业的渗透测试
总结
这套工作流最大的价值,不是帮你找出几个 SQL 注入,而是把'安全'从一个低频、依赖专家经验的'运动式扫荡',变成了一个高频、可沉淀、越用越聪明的'日常基建'。当 AI 能读懂你的代码上下文,它能替你扛下 80% 的脏活累活,而你只需要做那 20% 的关键决策。在这个时代,善用 AI 去重构工作流,本身就是一种核心竞争力。
附录 A:初始化模板完整内容
"
完整的《安全漏洞检查方法论-初始化模板》约 260 行,包含:
- §1 核心原则(8 条,来自实战教训)
- §2 项目画像模板(9 项
[待填]占位符)- §3 检查阶段总览 + 漏洞台账 JSON Schema
- §4 攻击面清点(taint source 清单)
- §5 SQL 注入专项(路线 A:传统 ADO.NET 8 种模式 / 路线 B:ORM 6 种模式)+ 修复分层 + 验证判据 + 自测约定
- §6 其他攻击速查清单(13 类)
- §7 经验教训库(滚动追加)
- §8 文档与流程约定
- §9 首次接管流程 + AI Skill 自导出指南 + 自升级闭环
- §10 版本演进记录
获取方式 :将本文仓库中的
安全漏洞检查方法论-初始化模板.md复制到你的项目文档目录即可。 该文件设计为"跨项目稳定"------[普适]标记的内容不要改,[待填]由 AI 投放到具体项目后自动填充。
由于模板较长,这里给出你粘贴给 AI 的最简提示词(模板本身由 AI 按 §9 自动执行):
读取当前项目的 {docs目录}/安全漏洞检查方法论-初始化模板.md,
执行 §9 首次接管流程:
1. 技术栈探测(读 csproj/sln/packages)
2. 填 §2 项目画像
3. 选路线(A/B/混合)
4. 首轮扫描 + 漏洞台账
5. 过 §6 通用清单
6. 与我确认范围后开始修复
附录 B:SKILL.md 通用模板
下面是 Skill 入口文件的通用模板。你需要根据你的项目定制**§2 项目硬约束**部分。
---
name: {项目名}-security-review
description: {项目名} 安全审查的接管入口与机械自查。当用户要继续/推进安全审查、复测已修漏洞、更新台账/看板/交接时使用。
---
# {项目名} 安全审查 · 接管引导
**定位**:这是"防脱节"的薄壳,不是方法论副本。凡本文与项目内《安全基线.md》/母版模板冲突,以项目文档为准。
## 0. 接管第一动作(不可跳过)
运行预检脚本确认状态一致,然后只读两份文档:
1. 最新的交接文档 `_session-handoff-*.md`
2. 漏洞台账 `_review-ledger-*.md`
其余文档按需点读。
## 1. 事实源映射
| 载体 | 路径 | 职责 |
|------|------|------|
| 入口 | `安全审查/安全基线.md` | 项目画像、扫描清单、红线 |
| 明细权威 | `安全审查/_review-ledger-*.md` | 漏洞条目 JSON,状态以此为准 |
| 概览 | `安全审查/安全审查进度看板.md` | 从台账汇总,不得独立计数 |
| 轨迹 | `安全审查/CHANGELOG-安全审查.md` | 倒序,不删原文 |
| 续接 | `安全审查/记录/_session-handoff-*.md` | 每轮一份 |
## 2. 项目硬约束(根据你的项目定制)
- **仓库边界**:只改 `{项目目录}`,勿混编
- **状态语义**:台账只用 OPEN / IN_PROGRESS / FIXED / REJECTED_UNCONTROLLABLE
- **FIXED 标准**:至少需 SQL 层回归自测通过;仅落码+编译记 IN_PROGRESS
- **收尾一致性**:每轮结束须满足「台账条目数 == 状态计数之和 == 看板计数」
- **证据文件**:全表 dump 一律不入版本库
- **熔断**:禁止大面积重构与新功能同轮推进
## 3. 每轮收尾清单
1. 台账更新 → 跑 preflight 确认自洽
2. 看板从台账重汇总
3. CHANGELOG 顶部加本轮条目
4. 新写交接文档(附 git status 实测输出)
5. 分逻辑提交
## 4. 已知不要做
(在此列出你项目中已确认不可控的项、死代码、暂缓项等)
附录 C:preflight.py 通用模板
自动化预检脚本,检查 7 类一致性问题。复制后修改顶部的路径配置即可使用。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
安全审查 · 接管前机械自查(preflight)
用法:python preflight.py # 只输出结论
python preflight.py -v # 附带明细行
退出码:0=全 PASS/WARN,1=有 FAIL
"""
import json, os, re, subprocess, sys
# ===== 根据你的项目修改以下路径 =====
REPO = os.environ.get("SECURITY_REPO", r"G:\your\project\path")
DOCS_SUBDIR = "Docs/安全审查" # 相对于 REPO 的文档目录
LEDGER_NAME = "_review-ledger-{项目名}.md" # 台账文件名
BOARD_NAME = "安全审查进度看板.md"
ASSETS_DIR = "assets"
# ===================================
DOCS = DOCS_SUBDIR
LEDGER = f"{DOCS}/{LEDGER_NAME}"
BOARD = f"{DOCS}/{BOARD_NAME}"
BIG_BLOB_BYTES = 1_000_000
VALID_STATUS = {"OPEN", "IN_PROGRESS", "FIXED", "REJECTED_UNCONTROLLABLE", "REJECTED"}
fails, warns, oks = [], [], []
def ok(m): oks.append(m)
def fail(m): fails.append(m)
def warn(m): warns.append(m)
def git(*args):
r = subprocess.run(
["git", "-C", REPO, "-c", "core.quotepath=false", *args],
capture_output=True, text=True, encoding="utf-8", errors="replace"
)
return (r.stdout or "").strip()
# 1. 仓库状态
head = git("rev-parse", "--short", "HEAD")
remote = git("ls-remote", "--heads", "origin", "refs/heads/main")
remote_sha = remote.split()[0][:7] if remote else ""
if not remote:
warn("未能读取 origin(离线?)")
elif remote_sha != git("rev-parse", "HEAD")[:7]:
fail(f"本地 HEAD={head} 与 origin/main={remote_sha} 不一致")
else:
ok(f"HEAD == origin/main == {head}")
porcelain = git("status", "--porcelain")
if porcelain.strip():
fail(f"工作区有 {len(porcelain.splitlines())} 项未提交改动")
else:
ok("工作区干净")
# 2. 台账 JSON 自洽
ledger_path = os.path.join(REPO, LEDGER.replace("/", os.sep))
if not os.path.isfile(ledger_path):
fail(f"台账不存在:{LEDGER}")
else:
raw = open(ledger_path, encoding="utf-8").read()
m = re.search(r"```json\n(.*?)\n```", raw, re.S)
if not m:
fail("台账里找不到 JSON 代码块")
else:
try:
entries = json.loads(m.group(1))
ok(f"台账 JSON 可解析,{len(entries)} 条")
except Exception as e:
fail(f"台账 JSON 解析失败:{e}")
entries = []
if entries:
# ID 连续性
ids = [e.get("id", "") for e in entries]
want = [f"VULN-{i:03d}" for i in range(1, len(ids) + 1)]
if ids != want:
fail(f"台账 ID 不连续:末条 {ids[-1]},期望 {want[-1]}")
else:
ok(f"ID 连续 VULN-001~{ids[-1][-3:]}")
# 状态计数
tally = {}
for e in entries:
st = e.get("status", "?")
tally[st] = tally.get(st, 0) + 1
if st not in VALID_STATUS:
fail(f"{e.get('id')} 状态非法:{st}")
if sum(tally.values()) == len(entries):
ok("状态计数 == 条目数:" + " ".join(f"{k}={v}" for k, v in sorted(tally.items())))
else:
fail("状态计数之和 != 条目数")
# 3. 文件存在性
for name, label in [(BOARD, "看板"), (LEDGER, "台账")]:
if os.path.isfile(os.path.join(REPO, name.replace("/", os.sep))):
ok(f"{label}存在:{name}")
else:
fail(f"{label}不存在:{name}")
# 4. 证据文件(大文件检查)
assets_path = os.path.join(REPO, DOCS.replace("/", os.sep), ASSETS_DIR)
if os.path.isdir(assets_path):
tracked = git("ls-files", assets_path)
for f in tracked.splitlines():
fp = os.path.join(REPO, f)
if os.path.isfile(fp) and os.path.getsize(fp) > BIG_BLOB_BYTES:
fail(f"assets 下有大文件被跟踪:{f} ({os.path.getsize(fp)//1024}KB)")
# 5. 凭据泄露(简单关键词扫描)
config_patterns = re.compile(r'(?i)(password|passwd|pwd)\s*[:=]\s*["\'][^"\']{4,}', re.S)
for f in git("ls-files").splitlines():
if not f.endswith((".cs", ".json", ".xml", ".config", ".yaml", ".yml")):
continue
fp = os.path.join(REPO, f)
if not os.path.isfile(fp):
continue
try:
content = open(fp, encoding="utf-8", errors="ignore").read(50000)
if config_patterns.search(content):
warn(f"疑似凭据:{f}")
except:
pass
# 输出
print("\n" + "="*60)
print("安全审查 · 预检报告")
print("="*60)
for m in oks: print(f" √ {m}")
for m in warns: print(f" ! {m}")
for m in fails: print(f" × {m}")
print(f"\n总计:{len(oks)} PASS / {len(warns)} WARN / {len(fails)} FAIL")
if fails:
print("× 有 FAIL 项,请先处置再开工!")
sys.exit(1)
else:
print("√ 全部通过,可以开工。")
sys.exit(0)
"
使用方法 :复制此文件到你的项目 Skill 目录或项目根目录,修改顶部 4 个路径变量,然后
python preflight.py即可。
附录 D:多轮审查的提示词模板
继续审查(通用版)
继续 {项目名} 安全审查。
先读 {docs}/安全审查/安全基线.md,按 §2 扫描清单与 §4 台账推进。
本轮目标:{具体目标,如"确认 L2 的调用链可控性并给出参数化方案"}。
已确认不可控项:{粘贴上次交接的"勿重复调查清单"}。
红线:只读分析;写库测试只用可丢弃数据;修复前贴 Patch。

