第 8 章 · 04 自定义 skill


文档摘要

第 8 章 · 04 自定义 skill 本节摘要:上一节我们理解了 skill 的机制、分类与标准结构。本节动手造——通过两个真实存在于 Strix 仓库的 custom skill,演示「从用到造」的完整闭环。第一个是 :用 trivy 扫 lockfile,把「已知 CVE 的依赖」作为一类一等公民发现上报,它是 Strix「无 PoC 不报告」铁律的唯一显式例外。第二个是 :把 semgrep、ast-grep、gitleaks、trivy 串成一条源码感知的静态分诊流水线,让静态信号去指导动态测试。两个 skill 合起来,完整展示了「为什么写 skill、skill 里放什么、怎么避免反模式」,也是你贡献自己 skill 前最好的范本。

第 8 章 · 04 自定义 skill

本节摘要:上一节我们理解了 skill 的机制、分类与标准结构。本节动手造——通过两个真实存在于 Strix 仓库的 custom skill,演示「从用到造」的完整闭环。第一个是 dependency_cve_scanning:用 trivy 扫 lockfile,把「已知 CVE 的依赖」作为一类一等公民发现上报,它是 Strix「无 PoC 不报告」铁律的唯一显式例外。第二个是 source_aware_sast:把 semgrep、ast-grep、gitleaks、trivy 串成一条源码感知的静态分诊流水线,让静态信号去指导动态测试。两个 skill 合起来,完整展示了「为什么写 skill、skill 里放什么、怎么避免反模式」,也是你贡献自己 skill 前最好的范本。

内容来源:原项目 strix/skills/custom/dependency_cve_scanning.mdstrix/skills/custom/source_aware_sast.md,汉化并套用体系化模板。

⚠️ 本节演示的 skill 用于扫描你自己的仓库。依赖 CVE 扫描虽不动态执行漏洞,但仍属对代码与供应链的安全评估,仅限授权资产。

学习目标

阅读完本节,你应当能够:

  1. 说清 dependency_cve_scanning 解决的核心矛盾:已知 CVE 依赖无法动态 PoC,却是真漏洞。
  2. 解释它为什么是「无 PoC 不报告」铁律的唯一例外,以及这个例外为何仅限 create_dependency_report
  3. 描述 source_aware_sast四件套流水线:semgrep / ast-grep / gitleaks(+trufflehog)/ trivy。
  4. 理解「静态信号指导动态测试」的工作流:从候选到 PoC 的四步。
  5. 从这两个范本中提炼出写自定义 skill 的通用套路

一、两个 skill 的全景定位

这两个 custom skill 都服务于「白盒/源码扫描」场景,但分工不同:

  • source_aware_sast:找动态可验证的漏洞,产出候选 → 动态验证 → 走 create_vulnerability_report(默认铁律)。
  • dependency_cve_scanning:找已知 CVE 的依赖,无需动态 PoC → 走 create_dependency_report(唯一例外)。

💡 两者会协作:source_aware_sast 在静态分诊时,如果撞到已知 CVE 依赖,会主动 load_skill(["dependency_cve_scanning"]) 切到 SCA 工作流。这种「skill 之间互相调用」正是 /custom 类的威力——把专门流程封装成可复用单元。

二、dependency_cve_scanning:给「已知 CVE 依赖」一个名分

核心矛盾:为什么需要这个 skill

Strix 的灵魂是「无 PoC 不报告」——避免误报。但有一类发现天然无法动态 PoC:锁定在 lockfile 里的已知漏洞依赖

举个典型例子:lodash@4.17.4 带一个已知的原型污染 CVE。你想从外部动态打出 PoC?大概率打不动——漏洞代码路径可能根本没被任何端点调用。按默认铁律,它会被「发现了又默默丢掉」,因为它「无法动态利用」。

但这确确实实是个漏洞。它的证据不是 exploit 脚本,而是 「lockfile 条目 + 扫描器输出 + 公开 advisory」。这个 skill 的使命就是:确保这类发现被上报,而不是被丢弃

⚠️ 这是 Strix「无动态 PoC 不报告」规则的唯一显式例外,且仅适用于 create_dependency_report 这个专用工具。不要把这种「静态即证据」的逻辑外推到别的漏洞类——那会重新把 Strix 变成一个误报满天的扫描器。

扫描流程

从仓库根目录运行,输出存到 source-aware 流程共用的产物目录:

ART=/workspace/.strix-source-aware mkdir -p "$ART" # 先记录漏洞库年龄——库过时是一个可见信号,而不是一次"干净的误判" trivy version --format json 2>/dev/null | tee "$ART/trivy-version.json" # 检查 .VulnerabilityDB.UpdatedAt / NextUpdate # lockfile/manifest → 已知 CVE 匹配。 # 先尽力刷新库(有外联的沙箱能拿到最新 CVE);刷新失败则回退缓存,而不是让扫描失败。 # --offline-scan 让逐包 advisory 查询走离线。 trivy fs --scanners vuln --timeout 30m --offline-scan \ --format json --output "$ART/trivy-sca.json" . \ || trivy fs --scanners vuln --timeout 30m --offline-scan --skip-db-update \ --format json --output "$ART/trivy-sca.json" . \ || true

trivy 能读懂主流 lockfile/manifest:package-lock.jsonyarn.lockpnpm-lock.yamlpoetry.lockrequirements.txtPipfile.lockgo.mod/go.sumGemfile.lockpom.xml/gradle.lockfileCargo.lockcomposer.lock 等。

💡 「库过时」是信号,不是干净的假象。如果 .VulnerabilityDB.UpdatedAt 已经好几周前(沙箱没外联刷新不了),要在发现的 assumptions 里注明「库可能过时」——这样读者知道「干净」未必真干净。反过来,如果仓库明明有依赖,trivy 却返回零漏洞,视其为可疑:确认库存在(trivy-version.json)、确认 lockfile 存在,再相信结果。

解读结果与上报

trivy-sca.json.Results[].Vulnerabilities[] 的每一条,收集:VulnerabilityID(CVE/GHSA,优先 CVE)、PkgNameInstalledVersionFixedVersionTarget(lockfile 路径)、.Results[].Type(包生态,归一化为小写:npm/pypi/go/maven/rubygems/cargo 等)、CVSSPrimaryURL/references。

(CVE, PkgName, InstalledVersion) 三元组去重,每个 CVE 一份报告,不要批量合并

然后填这些结构化字段(它们驱动专门的依赖报告卡片,别只写进自由文本):

字段 要求
cve 已验证的 CVE-YYYY-NNNNN(必填);只有 GHSA 时去查映射的 CVE,真没有就别用这个工具报
package_name PkgName(必填)
installed_version InstalledVersion(必填)
package_ecosystem 归一化生态名(必填)
fixed_version FixedVersion,无修复版本时留空
advisory_cvss advisory 的公开基础分(0.0–10.0),必填——严重度完全由它决定
cwe advisory 命名了最具体的 CWE-NNN 时填写
assumptions 放可达性/可利用性的说明

⚠️ 三个高频反模式:

  1. create_vulnerability_report 报依赖 CVE——错,它要求 PoC 字段非空,会拒绝;依赖 CVE 必须用 create_dependency_report
  2. 漏填 advisory_cvss——工具会直接拒绝调用,因为它是决定严重度的唯一输入;瞎猜分会同时抬高低危、压低高危。
  3. 因为「没动态复现」就把严重度压到 LOW——错,直接用 advisory 分。

另外,可达性是置信度修饰符,不是闸门:不要因为「没能证明漏洞代码路径可达」就抑制或降级一个已知 CVE。报它、按 advisory 设 advisory_cvss,再用 assumptions 注明可达性(如「漏洞的 template() API 未在应用代码中导入,实际可利用性不确定」)。如果你证明可达甚至串成动态 exploit,那就走 create_vulnerability_report 当普通动态发现报。

三、source_aware_sast:静态信号指导动态测试

设计思想

这个 skill 用于「源码为主」的分析,核心主张是:让静态与结构化信号去指导动态测试——先用一堆静态扫描器把候选捞出来、排好序,再让 agent 对高价值候选做 source-to-sink 追踪和动态 PoC。

它的工作流是「先广后深」:

💡 这条流水线的精髓是「静态信号是线索,不是判决」。skill 的反模式一节明确写了:别把扫描器输出当最终真相、别在低信号匹配上耗满周期、别在没有验证证据的情况下上报纯源码发现。这跟第 1 章「PoC 而非误报」完全一致——静态扫只是帮 agent 把劲使在对的地方。

基线扫描包(推荐一次性跑)

每个仓库先跑一遍基线,再按需收窄。所有产物写进专用目录 /workspace/.strix-source-aware:

ART=/workspace/.strix-source-aware mkdir -p "$ART" # 1) semgrep:确定性规则集(配合 --metrics=off) semgrep scan --config p/default --config p/golang --config p/secrets \ --metrics=off --json --output "$ART/semgrep.json" . # 2) 从 semgrep 扫描范围构建确定性 AST 目标(不靠硬编码路径猜) python3 - <<'PY' import json from pathlib import Path art = Path("/workspace/.strix-source-aware") semgrep_json = art / "semgrep.json"; targets_file = art / "sg-targets.txt" try: data = json.loads(semgrep_json.read_text(encoding="utf-8")) except Exception: targets_file.write_text("", encoding="utf-8"); raise scanned = data.get("paths", {}).get("scanned") or [] if not scanned: scanned = sorted({r.get("path") for r in data.get("results", []) if isinstance(r, dict) and isinstance(r.get("path"), str) and r.get("path")}) bounded = scanned[:4000] targets_file.write_text("".join(f"{p}\n" for p in bounded), encoding="utf-8") print(f"sg-targets: {len(bounded)}") PY # 3) ast-grep:无规则的结构化扫描(无需 sgconfig.yml) xargs -r -n 200 sg run --pattern '$F($$$ARGS)' --json=stream < "$ART/sg-targets.txt" \ > "$ART/ast-grep.json" 2> "$ART/ast-grep.log" || true # 4) 密钥:gitleaks + trufflehog gitleaks detect --source . --report-format json --report-path "$ART/gitleaks.json" || true trufflehog filesystem --no-update --json --no-verification . > "$ART/trufflehog.json" || true # 5) trivy:专注 vuln/misconfig(密钥已由上面覆盖),大仓库调长超时 trivy fs --scanners vuln,misconfig --timeout 30m --offline-scan \ --format json --output "$ART/trivy-fs.json" . || true

⚠️ 几个工程细节,踩过才知道:

  • semgrep --config auto 不要和 --metrics=off 混用——auto 配置依赖在线指标。
  • ast-grep 目标要从 semgrep 范围派生,而不是拍脑袋写路径——上面那段 Python 就是干这个的,保证确定性、避免漏扫。
  • trivy 这里只扫 vuln,misconfig,密钥交给 gitleaks/trufflehog,避免重复。

高价值结构模式

ast-grep 用 $F($$$ARGS) 这类模式做结构匹配时,重点盯这几类高价值模式:

  • 路由处理器附近缺失的鉴权检查
  • 动态拼接的命令/查询
  • 不安全的反序列化或模板执行路径
  • 受用户输入影响的文件/路径操作

JS 侧补充

对前端与 Node 服务,在语言无关的基线之上再叠加:

retire --path . --outputformat json --outputpath "$ART/retire.json" || true eslint --no-config-lookup --rule '{"no-eval":2,"no-implied-eval":2}' \ -f json -o "$ART/eslint.json" . || true

遇到压缩 bundle,先 js-beautify <file> 再 grep;jshint --reporter=unix <file> 可作为 ESLint 过激时的轻量替代。深入挖 bundle 里的端点候选,可切到 katana skill 里的 JS-Snooper / jsniper.sh

把静态信号变成 exploit:四步

skill 给出的转换流程是:

  1. 按影响与可利用性给候选排序。
  2. 对头部候选做 source-to-sink 数据流追踪。
  3. 构造动态 PoC复现疑似问题。
  4. 动态验证成功后才上报

四、从两个范本提炼「写自定义 skill 的通用套路」

把这两个 skill 摊开看,自定义 skill 其实遵循一个清晰的套路:

对照两个范本:

套路环节 dependency_cve_scanning source_aware_sast
痛点 已知 CVE 依赖无法动态 PoC 却是真漏洞 LLM 静态分析浅,需要结构化信号指导
流程 trivy 扫 lockfile 三段式(含回退) 四件套基线扫描包
上报 create_dependency_report 字段表 验证成功才走 create_vulnerability_report
反模式 5 条(用错工具/漏填 cvss/降级等) 3 条(扫描输出非真相/耗低信号/无验证上报)

💡 「反模式」是被低估的一节。很多人写 skill 只写「该做什么」,但真正能防止 agent 跑偏的,是明确写清「千万别做什么」。这两个 skill 的反模式清单,直接对应了 Strix 报告质量的最常见雷区——你写自己的 skill 时,务必把踩过的坑写成反模式。

本节要点回顾

  1. dependency_cve_scanning:用 trivy 扫 lockfile,把「已知 CVE 依赖」作为一等公民发现上报,是「无 PoC 不报告」铁律的唯一显式例外,且仅限 create_dependency_report
  2. 可达性是修饰符不是闸门:不因无法证明可达就抑制/降级已知 CVE;能证明可达或串成 exploit 时,改走动态发现。
  3. source_aware_sast:semgrep/ast-grep/gitleaks(+trufflehog)/trivy 四件套,静态信号指导动态测试,验证成功才上报。
  4. 工程细节:semgrep 不混用 auto 与 --metrics=off;ast-grep 目标从 semgrep 范围派生;trivy 分工只扫 vuln/misconfig。
  5. skill 间协作:source_aware_sast 撞到依赖 CVE 会 load_skill 切到 dependency_cve_scanning——/custom 类的可复用单元威力。
  6. 通用套路:frontmatter → 痛点 → 流程 → 上报字段 → 反模式;反模式一节最防跑偏

会造 skill 之后,最后一步是把这些成果回馈给社区——让更多的人用上你打磨的知识。下一节(也是全书最后一节)讲贡献流程:如何搭建开发环境、提交 PR、把你的 skill 合进上游,完成「从用户到贡献者」的闭环。


发布者: 作者: 灏天文库 转发
评论区 (0)
U