Deepsec 实战指南:从 /deepsec 到全仓库漏洞扫描的完整 Runbook

原创2026-09-25 14:17:231,464 阅读
文章标签:应用安全漏洞扫描人工智能AI Agent

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 可以看到它的整体工作流:

  1. 快速正则扫描:免费、无 AI 参与,用 matcher 标记候选文件;
  2. AI 深度调查:Agent 逐个调查候选文件,记录带严重级别的发现;
  3. 重新验证与导出:发现可被 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 或 codex CLI 已登录时,跳过一切模型凭证配置(无 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,可以还原它的生命周期:

  1. 解析文件列表: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 预算;
  2. 自动创建项目:若项目不在 deepsec.config.ts 中,resolveProjectIdForDirect 从根目录 basename 派生项目 id(净化并校验,见 resolve-project-id.ts 的 PROJECT_ID_RE 白名单),再 ensureProject() 落盘 data/<id>/project.json——自动创建是单行且非破坏性的,绝不修改你的 deepsec.config.ts;
  3. 范围化扫描:只对列出的文件跑 scanFiles(),让每个路径都有 FileRecord——即使文件没有命中任何 matcher,也把正则命中作为 prompt 的 signals(提示锚点)交给 agent;
  4. 总是处理:对同一批文件跑 process()——即使没有任何 matcher 命中的文件也会被整体审查(无 signals、无 scanner 锚定,纯粹让 agent 读文件);
  5. 可选输出 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 检查直接跳过上传,comment job 也就下载不到产物,保持无操作。

威胁模型注意事项

  • 不要把 pull-requests: write 授予运行 PR 代码的 job——PR 可以在自己的 package.json postinstall 脚本或 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)

两个关键设计决策让"重跑同一条命令即可恢复"成为现实:

  1. 一个文件 = 一个 FileRecord。工作单元是源文件而非发现,scanner、processor、revalidator 都作用于文件,原子化的每文件锁与幂等合并自然成立;
  2. 追加式分析历史。重跑 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 与默认值会变化。

登录后查看全文
deepsec