news 2026/9/27 8:09:13

Deepsec 工作区扩展实战:以 webapp 样例为蓝本编写手写 Matcher 插件与 per-project 覆盖配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Deepsec 工作区扩展实战:以 webapp 样例为蓝本编写手写 Matcher 插件与 per-project 覆盖配置
  • 应用安全
  • 漏洞扫描
  • 人工智能
  • AI Agent

【免费下载链接】deepsec

Deepsec is a security harness for finding vulnerabilities in your codebase powered by coding agents

项目地址:https://gitcode.com/gh_mirrors/deeps/deepsec
点击查看免费下载

本篇文章围绕 Deepsec 仓库中的 samples/README.md 展开,讲解官方提供的samples/参考材料——尤其是samples/webapp/这个虚构的 Acme 库存 Web 应用示例——在一次性初始化(one-shot setup)之后如何进一步扩展扫描工作区。读完本文,你将掌握三类核心实战能力:其一,为INFO.md编写面向 AI 的仓库上下文(认证模型、威胁模型、误报来源);其二,在generated-matchers.ts之外手写需要可执行逻辑的MatcherPlugin(如带负面条件的"无鉴权路由"规则);其三,通过deepsec.config.ts与config.json两种途径为单个项目叠加priorityPaths、promptAppend、ignorePaths等覆盖配置,并理解这些配置在扫描与处理管线中的实际生效机制。

Samples 目录的定位:setup 之后的"扩展参考层"

先看 samples/README.md 对整套示例的定性:它是一份"扩展工作区(extending a workspace)的参考材料",起点永远是npx deepsec init,不要把某个样例复制过来当作初始化器使用。

这背后是 Deepsec 的分层设计:一次性 setup 已经完成了初始化的大部分工作——安装工作区、链接并验证 Vercel/Sandbox 与模型访问、写入INFO.md、评估结构化的 surface inventory,并在覆盖存在缺口时向generated-matchers.ts追加安全的声明式 matcher(见 samples/webapp/README.md)。而samples/展示的是下一层:声明式规则表达不了、需要可执行 matcher 逻辑的规则形态。

# 从仓库根目录开始并完成一次完整 setup npx deepsec init # 之后,当一个 true-positive 需要比声明式 matcher 更丰富的逻辑时, # 参考本样例的 matchers/*.ts 形态,并阅读 docs/writing-matchers.md。 # 将插件注册在 generatedMatchersPlugin 旁边,而不是替换它。

每个样例对仓库测试而言都是自洽完整的;但在真实工作区中,只应复制你需要的 matcher/plugin 思路,同时保留 setup 创建的项目链接、ai路由、生成式 matcher 插件和项目注册。

samples/目前只包含一个条目:

条目内容定位
samples/webapp/一个虚构的 Acme 库存 Web 应用,包含手写 matcher 插件、INFO.md与 per-project 覆盖配置补充 setup 生成的generated-matchers.ts,展示需要可执行 matcher 逻辑的规则

webapp 样例解剖:五份文件,一条阅读主线

webapp/被 samples/webapp/README.md 称为"rich reference"——一个被认真打磨过的扫描工作区长什么样。它对应一个 Next.js 15 单体仓库(内部库存 + 采购应用,约 15 万行),后端 API 在src/api/、服务端库在src/lib/、管理工具在src/api/admin/与src/app/admin/、计费在src/api/billing/,数据库用 Postgres + Drizzle,订阅用 Stripe,认证用 Auth.js(NextAuth v5)。

建议按以下顺序阅读这五份文件:

  1. samples/webapp/package.json —— 把deepsec声明为依赖("deepsec": "^0.1.0","type": "module"),这是"样例即工作区"的基础。
  2. samples/webapp/deepsec.config.ts —— 内联加载INFO.md、通过一个 inline plugin 注册两个自定义 matcher、声明项目级覆盖。
  3. samples/webapp/matchers/webapp-debug-flag.ts 与 samples/webapp/matchers/webapp-route-no-rate-limit.ts —— 两个针对该代码库辅助函数调优的手写 matcher。
  4. samples/webapp/INFO.md —— 注入 AI prompt 的上下文:认证模型、威胁模型、误报来源、值得知道的约定。
  5. samples/webapp/config.json —— 可选的 per-project 配置(priorityPaths、promptAppend、ignorePaths)。

运行样例

# 从样例目录运行(monorepo 为测试把 deepsec 符号链接了进来) pnpm deepsec scan --project-id webapp --root ./your-app pnpm deepsec process --project-id webapp

deepsec会从当前工作目录向上查找deepsec.config.ts,所以任意子目录下运行同样有效。

INFO.md:注入 AI 的仓库心智模型

INFO.md 的本质是一份给扫描 Agent 看的仓库安全手册,它会被注入到 AI prompt 中作为仓库上下文。在 packages/core/src/config.ts 中,ProjectDeclaration.infoMarkdown字段的注释明确说明:它注入 Markdown 作为 repo context,取代data/<id>/INFO.md。

样例里的INFO.md展示了四个必备板块:

认证与授权形态(Auth shape)——明确每个 API handler 必须调用auth.has(req.session.user, action, resource)之后才能读写敏感数据,并直接点名代码味道:"绝不直接req.session.user.role === "admin""。同时记录公共 handler 必须包裹withRateLimit(handler, { window: "1m", max: 60 }),而src/api/_internal/与 webhook receiver 各自例外(前者走私有 VPC,后者靠签名校验)。

威胁模型(Threat model)——按攻击吸引力排序:

  1. 跨租户访问(IDOR):每条记录都有companyId/userId,按id读写却不按req.session.user.companyId过滤就是 IDOR;
  2. 权限提升:在src/api/admin/users/promote.ts之外给role字段赋值;
  3. Stripe webhook 重放/伪造:未调用verifyStripeSignature(req)就处理 Stripe 事件;
  4. 供应商凭据外泄:vault.encrypt(value, { context })加密,解密点若省略 context、记录解密值或在 API 响应中返回解密值即为严重问题;
  5. Debug-flag 绕过:NODE_ENV !== "production"解锁/api/_dev/dump-cache等端点。

误报来源(False-positive sources to ignore)——直接给扫描器"划红线":src/scripts/migrations/**(一次性迁移,管理员运行)、src/lib/seed/**(仅开发用)、所有__tests__/与*.test.ts/*.spec.ts、src/api/_internal/health.ts(私有 VPC 内有意不做鉴权)。

值得知道的约定(Conventions)——Drizzle 查询必须用db.query.<table>.findFirst({ where, with })构建器,lint 禁止db.execute(sql\...`);自定义safeRedirect(targetUrl)通过ALLOWED_HOSTS防开放重定向;server actions 位于src/actions/,以"use server"开头且与 API 路由一样必须调用auth.has(...)`。

编写INFO.md的经验:它决定了 Agent 的"先验知识",一份写清认证边界、高价值攻击面与已知误报的INFO.md,能让后续扫描与再验证(revalidate)阶段的判断质量显著提升。

手写 Matcher 插件:两个可运行样例的逐行拆解

INFO.md回答"仓库长什么样",matcher 则回答"扫描器要盯哪些语法形态"。样例中的两个 matcher 都实现了 packages/core/src/plugin.ts 定义的MatcherPlugin接口:

export interface MatcherPlugin { slug: string; description: string; noiseTier: NoiseTier; // "precise" | "normal" | "noisy" filePatterns: string[]; requires?: MatcherGate; // 可选:tech / sentinelFiles 门控 examples?: string[]; // 可选:开发期测试契约,每条必须产生候选 match(content: string, filePath: string): CandidateMatch[]; }

webapp-debug-flag:纯正则 + 环境变量门控

webapp-debug-flag.ts 专门捕获"仅靠环境变量旗标门控的调试表面"。它的业务前提很现实:生产环境偶尔会泄漏NODE_ENV !== "production"(预览部署、env 不严格的 staging、容器默认值),所以这类门控不是真正的授权边界——Agent 的任务是确认被门控的表面是否敏感、是否还有别的守卫。

实现要点:

export const webappDebugFlag: MatcherPlugin = { slug: "webapp-debug-flag", description: "Routes/handlers gated only by env-var debug flags", noiseTier: "normal", filePatterns: ["src/api/**/*.ts", "src/server/**/*.ts"], match(content, filePath): CandidateMatch[] { if (/\.(test|spec)\.(ts|tsx)$/.test(filePath)) return []; return regexMatcher("webapp-debug-flag", [ { regex: /process\.env\.NODE_ENV\s*!==\s*['"]production['"]/, label: "NODE_ENV !== 'production' guard" }, { regex: /process\.env\.NODE_ENV\s*===\s*'"['"]/, label: "NODE_ENV === 'development' guard" }, { regex: /process\.env\.DEBUG_API\b/, label: "DEBUG_API env flag" }, { regex: /process\.env\.ENABLE_INTERNAL_TOOLS\b/, label: "ENABLE_INTERNAL_TOOLS env flag" }, ], content); }, };

值得注意的细节:match内部自行排除测试文件(/\.(test|spec)\.(ts|tsx)$/),因为测试代码里到处是环境变量门控却并非漏洞;noiseTier: "normal"表明"模式选出有用的审查候选,由 AI 做最终判别"(对应 writing-matchers.md 的噪声分层表)。

webapp-route-no-rate-limit:负面条件与跨行上下文

webapp-route-no-rate-limit.ts 是声明式 matcher 做不了的典型:它需要"文件里没有某个辅助函数"这种负面判断,以及多正则、逐行上下文窗口。

业务前提:该代码库的约定是每个公共 handler(src/api/**)都用withRateLimit(handler, { window, max })包裹导出,或调用rateLimiter.check(...);跳过包裹的 handler 是滥用/成本放大(cost amplification)的候选。

export const webappRouteNoRateLimit: MatcherPlugin = { slug: "webapp-route-no-rate-limit", description: "Public API handler not wrapped in withRateLimit / rateLimiter.check", noiseTier: "normal", filePatterns: ["src/api/**/route.ts", "src/api/**/handler.ts"], match(content, filePath): CandidateMatch[] { if (/\.(test|spec)\.(ts|tsx)$/.test(filePath)) return []; if (/\/_internal\//.test(filePath)) return []; if (/\/webhooks?\//.test(filePath)) return []; const HAS_RATE_LIMIT = /\bwithRateLimit\s*\(|\brateLimiter\s*\.\s*check\s*\(|\bratelimit\s*\.\s*limit\s*\(/; if (HAS_RATE_LIMIT.test(content)) return []; // 逐行扫描,找出导出的 HTTP 方法/handler 声明, // 并以 i±1 到 i+6 为窗口生成带行号与片段的候选 ... }, };

三个关键设计:

  1. 路径级排除先于内容判断:_internal/(私有 VPC,有意不加限流)与webhooks?/(靠签名校验代替限流)直接在文件路径上排除,这与INFO.md的约定一一对应;
  2. 负面条件:先探测"整份文件是否出现了限流辅助函数",出现则整体返回空;没有出现才逐行找export ... GET|POST|PUT|DELETE|PATCH|handler声明;
  3. 上下文窗口:为每个匹配行构造start = i-1、end = i+6的片段,让 AI 在审查候选时能看到导出签名附近的代码,matchedPattern字段则注明"文件内导出的 handler 没有限流包裹"。

为什么这类 matcher 必须手写?writing-matchers.md 明确列出了声明式 matcher 的边界:它被刻意限制为"安全的正则扫掠"。当规则需要负面条件("无鉴权辅助函数的路由声明")、对同一文件的多组相关搜索、语法感知的预处理或上下文窗口、组织特有的语义(以代码形式评审),或准备把规则贡献回公共框架时,就应该写 TypeScriptMatcherPlugin。

插件注册与命名空间

两个 matcher 通过 deepsec.config.ts 中一个 inline 插件注册:

const webappPlugin: DeepsecPlugin = { name: "webapp-internal", matchers: [webappDebugFlag, webappRouteNoRateLimit], }; export default defineConfig({ ai: { mode: "gateway", provider: "vercel" }, projects: [/* ... */], plugins: [webappPlugin], });

这与你发布一个 npm 插件是完全相同的形态,只是定义在用户自己的配置文件里(docs/plugins.md 称之为 inline-plugin)。需要留意两点命名约束:slug 全局唯一(setup 生成器会拒绝与 built-in、插件及同次响应内部的碰撞;手写变体应使用独立 slug,不要依赖替换顺序);slug 冲突时插件胜出——插件注册在 built-in 之后,同名覆盖可用于把内置 matcher 换成更紧的组织特有版本。

Per-project 覆盖配置:两条路径与一条优先级规则

样例展示了两种给单个项目加覆盖的途径,这是"为特定代码库调优扫描"的最后一环。

路径一:deepsec.config.ts的 ProjectDeclaration

ProjectDeclaration在 packages/core/src/config.ts 中定义:

export interface ProjectDeclaration { id: string; root: string; githubUrl?: string; // https://github.com/owner/repo/blob/branch infoMarkdown?: string; // 注入 prompt 的 Markdown,取代 data/<id>/INFO.md promptAppend?: string; // 追加到该项目 AI prompt 的自由文本 priorityPaths?: string[]; // 应优先处理的路径前缀 }

样例配置:

projects: [ { id: "webapp", root: "./your-app", githubUrl: "https://github.com/acme/webapp/blob/main", infoMarkdown: fs.readFileSync(path.join(here, "INFO.md"), "utf-8"), promptAppend: "Pay extra attention to /api/admin/* and /api/billing/* surfaces.", priorityPaths: ["src/api/admin/", "src/api/billing/", "src/lib/auth/"], }, ],

路径二:config.json的运行时覆盖

config.json 是可选的每项目 JSON 配置,运行时落在data/webapp/config.json(在 packages/core/src/paths.ts 中,dataDir(projectId)与projectConfigPath(projectId)把每个项目的配置隔离在data/<projectId>/目录下;扫描器在 packages/deepsec/src/commands/scan.ts 通过projectConfigPath(opts.projectId)读取,sandbox 分区器在 packages/deepsec/src/sandbox/partitioner.ts 同样解析这份 JSON):

{ "_comment": "可选。运行时写入 data/webapp/config.json。等价字段也可在 deepsec.config.ts 的 ProjectDeclaration 上设置(priorityPaths、promptAppend);两者同时存在时,本文件胜出。", "priorityPaths": [ "src/api/admin/", "src/api/billing/", "src/api/auth/", "src/lib/auth/", "src/lib/vault/" ], "promptAppend": "Cross-tenant access via missing companyId scoping is the highest-impact bug shape in this codebase. Always check that DB queries filter by req.session.user.companyId.", "ignorePaths": ["**/legacy/**", "**/migrations/**", "**/seed/**"] }

三个字段的语义值得逐一对齐到扫描管线:

  • priorityPaths:路径前缀列表,处理(process)阶段优先扫描这些前缀下的文件。样例里把admin、billing、auth、vault列为优先——这正好对应INFO.md威胁模型中的高价值攻击面(权限提升、凭据外泄、跨租户)。
  • promptAppend:以自由文本追加到该项目 AI prompt。样例给出了一条极强的提示:"跨租户访问(缺失 companyId 作用域)是本代码库影响最大的 bug 形态,始终检查 DB 查询是否按req.session.user.companyId过滤"——这是在把INFO.md里的威胁模型再次强化成 Agent 的即时指令。
  • ignorePaths:忽略的 glob 列表,与INFO.md的误报来源清单(migrations、seed、legacy)形成双重保险。

优先级规则:文件覆盖声明

关键规则写在 config.json 的_comment里:等价字段如果同时出现在ProjectDeclaration与config.json,本文件(config.json)胜出。这给了你两个使用层次:把稳定的、跨环境不变的覆盖写进deepsec.config.ts(随配置版本化),把可能按环境/时间变化的覆盖放进data/<projectId>/config.json(运行时生成、不随仓库走)。

与 one-shot setup 的分工:什么归生成器,什么归手写

最后回到 samples/README.md 反复强调的分层,把它与 writing-matchers.md 的规则对照,就得到一份清晰的决策清单:

setup 生成器负责的(交给generated-matchers.ts):setup agent 盘点仓库入口表面、运行内置 matcher、执行确定性覆盖策略;只为具体缺口(未覆盖的内部 RPC 注册表、队列消费家族、框架路由原语)生成 matcher 提案。这些提案以严格数据形式存在,经compileDeclarativeMatchers编译(模型写的 TypeScript 永远不被执行),并受安全契约约束:唯一 kebab-case slug、受限的相对 glob、有界的正则(仅i/m/im旗标)、每条必须真实命中的 examples、以及声称关闭的 surface ID。校验会拒绝未知字段、遍历性/全捕获 glob、重复 slug、空正则、反向引用、lookbehind、指数回溯形态与超大重复。编译后还会执行"爆炸策略":覆盖过广的 matcher 会被移除。这份文件要 review 并提交,但 setup 清单与 setup-state 文件是可再生的,应 gitignore。

什么时候保留或编辑一个生成的 matcher(writing-matchers.md):当它的文件作用域和正则描述的是稳定的仓库原语、且候选数与对应入口点数量接近时保留;当 glob 跟随生成代码而非真实入口家族、正则命中的是偶然标识符而非框架形态、examples 不代表真实语法、它声称覆盖实际没覆盖到的表面、或手写 matcher 能更精确表达条件时,编辑或删除它。改完跑一遍聚焦检查再全量对账:

pnpm deepsec scan --matchers <slug> pnpm deepsec setup

手写层负责的(本样例的matchers/):上述所有"需要逻辑"的规则——负面条件、多组相关搜索、上下文窗口、组织特有语义。此外,writing-matchers.md 建议:一个被再验证为 true-positive 的发现若显示出稳定的兄弟模式(sibling pattern),而 setup 的入口点覆盖没有建模它,也值得手写一个 matcher。规范的放置位置是放在生成插件旁边:

.deepsec/ ├── deepsec.config.ts ├── generated-matchers.ts └── matchers/ ├── my-route-no-auth.ts └── my-internal-rpc.ts

用additiveinline plugin 注册(plugins: [generatedMatchersPlugin, projectMatchers]),绝不删除generatedMatchersPlugin。手写 matcher 的examples字段是可执行文档:扫描器的 matcher 示例套件(packages/scanner/src/tests/matcher-examples.test.ts 会迭代注册表里每个 matcher 并断言每个 example 都能产生候选)会把你的每个子正则都变成一条 CI 检查,因此要为每个子模式覆盖语法变体。噪声层级按 writing-matchers.md 的表选择:precise(命中语法本身就是强漏洞信号)、normal(选出有用候选、AI 判别)、noisy(紧边界入口家族内每个文件都值得审)。

提交与演进建议

  • 提交deepsec.config.ts、generated-matchers.ts和matchers/;不要提交生成出来的data/<id>/setup/证据(可再生)。
  • 从样例复制思路时,只拿需要的 matcher/plugin 形态,保留 setup 创建的项目链接、ai路由、生成 matcher 插件与项目注册(samples/README.md 的原话)。
  • 若你的规则属于公共框架或通用弱点形态,优先把它贡献给 Deepsec 内置 matcher 注册表,而不是留在组织私有副本里(writing-matchers.md 的贡献指南;注册表位于 packages/scanner/src/matchers/index.ts)。
  • 完整的插件契约(matchers / notifiers / ownership / people / executor 五个槽位、last-wins 与 additive 的解析规则)可进一步阅读 docs/plugins.md 与 packages/core/src/plugin.ts。

一句话总结这套样例的用途:INFO.md教 Agent 理解代码库,matchers/教扫描器盯住代码形态,config.json与ProjectDeclaration教管线把火力集中到高价值攻击面——三者都建立在npx deepsec init生成的工作区之上,而不是替代它。

  • 应用安全
  • 漏洞扫描
  • 人工智能
  • AI Agent

【免费下载链接】deepsec

Deepsec is a security harness for finding vulnerabilities in your codebase powered by coding agents

项目地址:https://gitcode.com/gh_mirrors/deeps/deepsec
点击查看免费下载

相关推荐

上一篇:终极指南:用AB Download Manager实现高效文件下载与智能管理
下一篇:README.md 安装说明

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 8:06:08

极简交互的减法艺术:如何通过移除 3 个冗余配置让留存翻倍

极简交互的减法艺术&#xff1a;如何通过移除 3 个冗余配置让留存翻倍在软件产品设计的进化史上&#xff0c;开发者和产品经理最容易陷入的一个致命陷阱是&#xff1a;“试图通过不断在【设置页面】里增加开关&#xff0c;来讨好每一个提出小众需求的用户”。 在最初的版本中&a…

作者头像 李华
网站建设 2026/9/27 8:02:51

使用 metrics-collectd 将 Java 应用指标实时上报到 Collectd

可观测性后端 【免费下载链接】metrics :chart_with_upwards_trend: Capturing JVM- and application-level metrics. So you know whats going on. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/met/metrics 点击查看 免费下载 导读 metrics-collectd 是 Metrics 生…

作者头像 李华