Deepsec 实战指南:从 /deepsec 到全仓库漏洞扫描的完整 Runbook
Deepsec 实战指南:从 /deepsec 到全仓库漏洞扫描的完整 Runbook
Deepsec 是一个由 AI 智能体(coding agent)驱动的开源漏洞扫描器,它先用高速正则扫描(matcher)筛出候选文件,再让 AI 模型逐个深入调查候选文件并输出带严重级别的发现(finding),最后支持重新验证(revalidate)、分类(triage)与导出(export)。本指南以仓库根目录的 SKILL.md 为骨架,讲解在任意代码库上从零接入 deepsec 的完整流程——如何确定扫描范围、如何无全量处理地完成 onboarding、如何在 .deepsec/ 工作区内执行分范围处理、如何解读结果,以及如何把 deepsec 接入 CI 作为 PR 安全门禁。读完你将掌握一套可直接照抄的扫描与集成方案。
为什么用 deepsec 做存量代码安全审计
deepsec 的设计出发点很直接:现代 AI 模型在安全代码审查上已经足够强,但市面多数审查方案只跑在 pull request 上——这意味着大量历史遗留代码从未被审查过,半年前的代码也是由更旧的模型审查的。deepsec 的定位是用 scaled VM fanout(规模化虚拟机扇出)对所有存量代码做安全审查,而不是只盯增量。
从 README.md 与 SKILL.md 可以看到它的整体工作流:
- 快速正则扫描:免费、无 AI 参与,用 matcher 标记候选文件;
- AI 深度调查:Agent 逐个调查候选文件,记录带严重级别的发现;
- 重新验证与导出:发现可被 revalidate、triage 并 export,供人工处置。
一个关键特征是单文件工作区:deepsec 往仓库里添加的所有内容都只存在于单个 .deepsec/ 工作区内(配置、安装的包、按项目划分的数据)。由于处理阶段运行的是真实 AI 智能体,成本与它调查的代码量成正比——这也是为什么 SKILL.md 反复强调"先问范围、全仓扫描很贵"。
第一步:先问清楚扫描范围
SKILL.md 要求 agent 在做任何事之前先确定扫描范围(使用结构化提问工具如 Claude Code 的 AskUserQuestion,否则用纯文本提问),共有三种范围:
| 范围 | 含义 |
|---|---|
| Uncommitted changes | 工作树 + 未跟踪文件(即未提交改动) |
| Diff to main | 与 origin/main 的差异(无 origin 远端时用 main) |
| Entire codebase | 整个代码库 —— 需要明确警告这是最贵的选项 |
为什么必须先问?因为 AI 处理会调查每一个候选文件,在大型仓库上可能产生真实费用。先锁定范围,后续流程才能无人值守地跑完。这一步对应到源码上,就是 process 命令的 direct mode(直接模式)与标准模式的切换点——见 process.ts:只要传了 --diff、--diff-staged、--diff-working、--files、--files-from 中的任意一个,就进入 processDirectMode;否则走标准 processStandardMode。
第二步:检测 onboarding 状态
在仓库根目录检查 .deepsec/ 工作区的存在情况:
- 没有
.deepsec/deepsec.config.ts→ 尚未 onboarding,需要完整执行第三步; .deepsec/存在但.deepsec/node_modules/deepsec缺失,或上一次 setup 被中断 → 重跑第三步的 init 命令,它会从检查点恢复并修复安装,而不是从头开始;- 其他情况 → 已完成 onboarding,直接跳到第四步。
这背后是 setup 协调器的检查点机制。正如 architecture.md 所述,deepsec init 和 deepsec setup 协调初始化图,每个阶段都会写入输入摘要(input digest)和输出检查点(output checkpoint),重试时跳过已完成阶段、从第一个缺失或无效输出处恢复。安装与认证有两级幂等:setup 状态避免昂贵的工作,同时廉价探针仍确认 node_modules/deepsec 存在并重新水合已配置的模型凭证;auth 层在项目链接、路由和 agent 集合完全不变时,独立短路掉新的模型与 Sandbox 探针。
第三步:只 onboarding、不全量处理
Onboarding 正常情况下以一次覆盖全仓库的 AI 处理收尾。既然用户已经选了范围,就应让 setup 停在 coverage(覆盖度)阶段之后——这仍然包含安装、登录、威胁建模、matcher 生成和最终正则扫描,只是跳过全仓库的 AI process 阶段。范围化处理放到第四步执行。
在仓库根目录,先只读查看计划,再运行 setup:
npx -y deepsec init --plan --output json
npx -y deepsec init --yes --through coverage --output jsonl
把输出的每一行都当作 JSON 解析。出现 needs_input 事件时,把附带的消息和操作展示给用户,而不是自行发明修复方案。特别是:
VERCEL_AUTH_REQUIRED:通常要求用户运行npx vercel login;登录后按返回的 link action 在.deepsec/内操作(用户需要选择项目时用npx vercel link),然后重跑同一条 init 命令;- 退出码 2:表示需要输入;
- 退出码 3:表示某个成本/时长边界停止了可恢复的运行——重跑同一条命令即可恢复。
从 protocol.ts 的源码可以确认这套协议:SetupProtocolError 携带 kind,取值 "needs_input" | "limit" | "failure",其中 needs_input 映射为退出码 2(见 setupErrorExitCode)。
无头模式与模型选择
SKILL.md 面向的是被调用的 agent(/deepsec),因此强调无头(headless)运行。对应命令与 getting-started.md 中"Running from CI or an agent"一节一致:
# 只读预览 setup 会做的一切,不改变任何东西
npx deepsec init --plan --output json
# 无人值守:接受默认值,按 profile 选模型,输出事件流
npx deepsec init --yes --model-profile value --output jsonl
--model-profile 帮你自动选模型:best(最高基准分)、value(合理价格下的最佳分)、budget(最便宜)。--thinking-level 可调推理强度(minimal / low / medium / high / xhigh,处理主运行默认 xhigh)。这些参数在 cli.ts 的 init 与 setup 命令定义中均有对应选项。
三种模型接入方式
- Vercel AI Gateway(默认):deepsec 登录 Vercel(如需要)并建立一个专用小项目存放凭证,setup 期间不产生任何计费项;
- 自带 API Key:无需 Vercel 账号,例如用 OpenAI key 时:
MY_OPENAI_KEY=... npx deepsec init \
--agent codex \
--model-auth direct \
--ai-provider openai \
--ai-api-key-env MY_OPENAI_KEY
Anthropic 用 --agent claude --ai-provider anthropic。deepsec 只存环境变量的名字,绝不存 key 本身。后续命令要重新导出该变量,或放进 .deepsec/.env.local。
- 本地订阅(
--model-auth local):机器上的claude或codexCLI 已登录时,跳过一切模型凭证配置(无 API key、无 gateway token、无环境变量),后续process/revalidate/triage跳过凭证检查。两个注意点:登录必须存在于实际运行的 harness(--agent claude要 claude 登录、--agent codex要 codex 登录);sandbox命令仍需要真实 API token(AI_GATEWAY_API_KEY),因为机器本地登录无法被代理进隔离沙箱。
第四步:按范围运行处理
处理命令必须在 .deepsec/ 内部运行——配置加载器只在当前目录或其祖先目录里找 deepsec.config.ts;第三步之后 npx deepsec 也会解析到安装在 .deepsec/ 里的那份副本。三种范围对应三条命令:
| 范围 | 命令 |
|---|---|
| Uncommitted changes | cd .deepsec && npx deepsec process --diff-working |
| Diff to main | cd .deepsec && npx deepsec process --diff origin/main |
| 全仓(紧接第三步后) | cd .deepsec && npx deepsec process(setup 的最终扫描已经产出候选集) |
| 全仓(此前已 onboarding) | cd .deepsec && npx deepsec scan && npx deepsec process |
注意 --diff 的参数是 origin/main(针对 merge base 的差异,而不是整个分支祖先),也支持 HEAD~1..HEAD 这类范围;--diff-staged 调查 index 与 HEAD 的差异;--diff-working 调查未提交 + 未跟踪文件。
direct mode 的底层实现
看 process.ts 的 processDirectMode,可以还原它的生命周期:
- 解析文件列表:
resolveFiles()调用git diff --name-only --diff-filter=AMRC <ref>、--cached或ls-files --others --exclude-standard(未跟踪文件)等 git 命令生成 POSIX 相对路径列表(file-sources.ts),并默认按 scanner 的IGNORE_DIRS过滤,避免 PR 里碰dist/**、*.test.ts之类的文件白白烧掉 AI 预算; - 自动创建项目:若项目不在
deepsec.config.ts中,resolveProjectIdForDirect从根目录 basename 派生项目 id(净化并校验,见 resolve-project-id.ts 的PROJECT_ID_RE白名单),再ensureProject()落盘data/<id>/project.json——自动创建是单行且非破坏性的,绝不修改你的deepsec.config.ts; - 范围化扫描:只对列出的文件跑
scanFiles(),让每个路径都有 FileRecord——即使文件没有命中任何 matcher,也把正则命中作为 prompt 的 signals(提示锚点)交给 agent; - 总是处理:对同一批文件跑
process()——即使没有任何 matcher 命中的文件也会被整体审查(无 signals、无 scanner 锚定,纯粹让 agent 读文件); - 可选输出 PR 评论:有新发现时把
--comment-out <path>的 markdown 写入指定路径。
五类文件源(互斥)
--diff <ref|range> git diff --name-only <ref>(如 origin/main、HEAD~1..HEAD)
--diff-staged 调查 index 与 HEAD 的差异
--diff-working 调查未提交 + 未跟踪文件
--files <csv> 调查这个逗号分隔的路径列表
--files-from <path> 从 <path> 读取换行分隔的路径("-" 表示 stdin)
其他旋钮:--no-ignore 绕过默认忽略过滤(测试文件、dist/、node_modules/ 等);--comment-out <path> 仅在有发现时写出 PR 评论形状的 markdown;--project-id <id> 覆盖项目 id;--root <path> 覆盖项目根目录。常规的 --agent、--model、--concurrency、--batch-size、--max-turns、--thinking-level 与标准模式行为一致。
第五步:解读结果
- direct mode(
--diff*)退出码:0= 无净新增发现,1= 至少一个净新增发现(不是错误),其他值 = 运行时错误。被触碰文件上的既有发现(pre-existing)被排除在门禁之外——这一语义让 direct mode 可以直接当 CI 门禁用。 - 总结发现并提议后续动作(都在
.deepsec/内执行):
npx deepsec report # 单项目 markdown + JSON 摘要
npx deepsec revalidate # 重查既有发现,降低误报率
npx deepsec export --format md-dir --out ./findings # 导出为按发现划分的 markdown 目录
退出码的精确语义
从 reviewing-changes.md 与 cli.ts 可以确认这套契约:0 = 本次运行未产出发现;1 = 至少产出一个发现;≠1 = 运行时错误(输入错误、凭证缺失等)。只有净新增发现会计入退出码——重跑一个已有发现的文件不会让构建失败,除非出现了新东西。这正是 process.ts 里 result.findingCount > 0 → process.exit(1) 的实现逻辑。另外还有一个值得注意的细节:若 agent 批次本身报错(缺少二进制、认证失败等),direct mode 也会以退出码 1 失败——"干净运行 0 发现"是绿色 CI 信号,不能让静默的 agent 崩溃伪装成它。
严重级别与净新增发现
pr-comment 的排序实现(pr-comment.ts)揭示了发现模型的关键字段:每个发现带有 severity(CRITICAL / HIGH / MEDIUM / HIGH_BUG / BUG / LOW)、vulnSlug、confidence、description、recommendation、lineNumbers,并由 producedByRunId 标记归属——PR 评论只展示本次运行新产出的发现,已标记为 fixed / false-positive / accepted-risk / duplicate 的都不再浮出水面。
深入:把 deepsec 接成 PR 门禁(CI 工作流)
SKILL.md 提到的 report / revalidate / export 只是结果消费;真正的门禁场景在 reviewing-changes.md 里,它是 deepsec 团队审查自己 PR 的原始工作流,可直接照抄:
name: deepsec
on: pull_request
permissions:
contents: read
jobs:
analyze:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # need history for `git diff origin/<base>`
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: 24, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: npm install -g @anthropic-ai/claude-code
- id: deepsec
env:
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
CLAUDE_CODE_EXECUTABLE: claude
run: |
pnpm deepsec process \
--diff origin/${{ github.event.pull_request.base.ref }} \
--comment-out comment.md
- if: always() && hashFiles('comment.md') != ''
uses: actions/upload-artifact@v4
with:
name: deepsec-comment
path: comment.md
retention-days: 1
comment:
needs: analyze
if: always() && needs.analyze.result == 'failure'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
steps:
- id: dl
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: deepsec-comment
- if: steps.dl.outcome == 'success'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: fs.readFileSync('comment.md', 'utf8'),
});
这个工作流为什么安全
- 双 Job 拆分:
analyze运行 PR 控制的代码(用户的pnpm install、配置、源码),AI gateway 密钥在作用域内,但对仓库无写权限;comment有pull-requests: write,但从不运行任何 PR 代码,只消费经过净化的comment.md产物。恶意 PR 无法在同一个特权步骤里同时"执行任意代码"与"写入仓库"。 - 仅同仓库门禁:
github.event.pull_request.head.repo.full_name == github.repository完全跳过 fork PR。fork 在pull_request事件下本来也拿不到仓库密钥,这个门禁纯粹是 UX 清理。 fetch-depth: 0:git diff origin/<base>需要针对 merge base 解析,默认浅克隆没有历史。npm install -g @anthropic-ai/claude-code:Claude Code CLI 就是 SDK 实际驱动的进程;全局安装 + 设置CLAUDE_CODE_EXECUTABLE: claude可跳过 SDK 内置二进制解析(在某些包管理器下的 Linux 上会失败)。comment.md仅在有发现时上传:--comment-out在绿灯运行下不写任何文件,所以hashFiles检查直接跳过上传,commentjob 也就下载不到产物,保持无操作。
威胁模型注意事项
- 不要把
pull-requests: write授予运行 PR 代码的 job——PR 可以在自己的package.jsonpostinstall 脚本或 CLI 加载的项目配置里加任意代码,两者都在你自己的步骤之前执行; - 生产环境把 action 固定到完整 SHA:示例用
@v4主版本标签仅为可读性,加固部署时应换成各 action 的完整提交 SHA,防止被攻陷的标签切入带密钥的 job; - AI gateway 密钥仍流经 PR 代码:即使拆分了 job,
analyze运行 PR 控制的pnpm install时密钥仍在 env 中,靠author_association门禁来兜底;需要纵深防御的话,给analyze加标签门槛(如if: contains(github.event.pull_request.labels.*.name, 'review-ok'))。
成本控制:宽 diff 很贵
每个文件都要付一次 AI 调查费。对 main 的 PR,范围要收在 merge base(origin/main),不要覆盖整个分支祖先。不值得调查的文件(生成代码、fixtures)加进现有 ignore 模式,或用自定义 --files-from 脚本剔除:
git diff --name-only origin/main \
| grep -v '^generated/' \
| deepsec process --files-from -
什么时候不要用 direct mode
- 大型仓库的初次扫底:完整
scan+process按噪声层级排序、并行更好,且受益于 matcher 门控中的全仓信号;direct mode 是给增量审查用的; - 重新验证既有发现:用
revalidate及其自己的过滤器。
深入:数据布局与可恢复性
SKILL.md 说"一切都在 .deepsec/ 里",architecture.md 给出了精确的磁盘布局:
data/<projectId>/
├── project.json # rootPath、githubUrl(自动管理)
├── INFO.md # 注入 AI prompt 的仓库上下文(手写或 agent 编写)
├── config.json # priorityPaths、promptAppend、ignorePaths(可选)
├── setup/ # 生成的 setup 证据(gitignored)
│ ├── setup-state.json # 阶段检查点、摘要、run ID
│ └── surface-inventory.json# 结构化入口清单
├── files/ # 每个被扫描文件一个 JSON(FileRecord)
│ └── path/to/file.ts.json
├── runs/ # 每个运行一个 JSON(RunMeta)
│ └── 20260429-abcd.json
└── reports/ # 生成的报告(markdown + JSON)
两个关键设计决策让"重跑同一条命令即可恢复"成为现实:
- 一个文件 = 一个 FileRecord。工作单元是源文件而非发现,scanner、processor、revalidator 都作用于文件,原子化的每文件锁与幂等合并自然成立;
- 追加式分析历史。重跑 processor 不会覆盖旧发现——它把新条目追加进
analysisHistory,把新发现(按 slug + title 去重)合并进findings。你甚至可以用不同 agent、prompt 或 model 重跑,得到的是严格改进而非破坏性替换。不同后端可混用:同一条 prompt、同一套 JSON 输出 schema,--agent codex(默认,gpt-5.5)、--agent claude(claude-opus-4-8)、--agent pi(zai/glm-5.2)可以混在一个项目里。
另外值得了解 setup 流程中三个自动化环节(都对应 architecture.md 的 stage details):
- 仓库分析:只读 agent 产出精炼的
INFO.md加一份验证过的结构化表面清单(surface inventory),无效输出有一次修复尝试; - 覆盖度检查:清单 glob 对照 scanner 的忽略文件宇宙展开,检查代表性文件、宽泛表面比例、敏感零覆盖表面、主导语言盲区和新 matcher 广度;
- 生成 matcher:模型输出是严格 JSON 数据,编译时不执行生成代码,拒绝正则/glob 复杂度、示例问题、slug 冲突、遍历与匹配爆炸;验收的规范写入
generated-matchers.ts供审查与提交。
覆盖率暂停是可恢复的:如果 setup 无法覆盖清单中的某个表面,它会在付费 AI 处理前停下,打印精确的清单、状态、生成 matcher 和恢复命令;重跑 setup 最多做两次新的 matcher 修复尝试。
日常命令速查
SKILL.md 只覆盖一次扫描的完整旅程,日常维护则由 README.md 的 Workflow reference 提供:
| 命令 | 作用 |
|---|---|
scan |
用正则 matcher 找候选点(快、免费、无 AI) |
process |
AI 调查;产出发现 + 建议 |
process --diff |
PR 模式:只扫描 + 调查 diff 中变更的文件 |
triage |
轻量 P0/P1/P2 分类(更便宜的模型) |
revalidate |
重查既有发现;检查 git 历史看是否已修复 |
enrich |
补充 git committer 信息 +(配合插件)归属数据 |
report |
单项目 markdown + JSON 摘要 |
export |
按发现的 JSON 或 markdown 目录 |
metrics |
跨项目统计:严重级、按类型的漏洞、TP 数 |
status |
项目镜像的快照 |
sandbox <cmd> |
在 Vercel Sandbox microVM 上运行上述任一命令 |
信任边界与安全模型
SKILL.md 与 getting-started.md 都给出同样的信任提示:把 deepsec 当作用全 shell 权限运行在你的环境里的 coding agent。它设计为在可信输入(你的源码)上运行,但外部依赖或 vendored 代码仍可能引发 prompt injection 担忧。
缓解手段有两条路径:
- CI 守卫模式:扫描不受信任的 PR 时,用上面的双 job 工作流(PR 代码永远拿不到仓库写权限),或 reviewing-changes.md 中的受保护模式;
- 隔离云沙箱:
pnpm deepsec sandbox process --project-id my-app --sandboxes 10 --concurrency 4。沙箱从没见过你的真实模型凭证——主机构留凭证、只在模型提供方服务器注入。Sandbox 大幅限制暴露面:coding agent 的 API key 在沙箱外注入、无法被外带;worker 沙箱的网络出站被限制为 coding agent 主机(bootstrap 阶段允许出站,但不运行 coding agent)。此路径需要 Vercel 账号(沙箱跑在 Vercel 上),但 setup 已核验过 Vercel 连接,无需额外 onboarding;本地工作树会被打包上传,.git被排除。
深入阅读
安装完成后,与已安装 CLI 精确匹配的完整文档会随包发布在 .deepsec/node_modules/deepsec/dist/docs/——getting-started.md、reviewing-changes.md(direct mode、退出码、CI 门禁)、configuration.md、models.md 等。仓库根目录的 docs 目录与之对应。变通 SKILL.md 中命令之前,先读对应文档——不同版本间 flag 与默认值会变化。