关于 perform-security-review
用于检查 PR、提交、时间范围、本地改动或分支差异的 Bitwarden 安全评审技能,规定差异选择、扫描证据收集、并行专项评审及报告处理流程。
主要功能
- 01PR、提交、时间范围及本地评审模式
- 02经过校验的分支基准解析
- 03扫描证据收集
- 04按领域并行开展 Agent 评审
- 05报告输出与差异文件清理规则
适用场景
- 01检查 PR 或提交中的安全问题
- 02评估近期或未提交的仓库改动
- 03结合扫描背景生成安全评审报告
MCP 相关
该工作流会调用 `aikido:issues` 技能获取 Aikido 发现项,并将预先收集的证据纳入评审;还会通过 GitHub API 查询 secret scanning 和 Dependabot 告警。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bitwarden/ ai-plugins --skill perform-security-review安装后在 Claude Code 中输入/perform-security-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/base-ref-resolution.mdsecurity-review-rubric.mdtool-grants.md详细介绍
参数
--base-ref <ref>:仅用于分支比较模式。使用此 ref 作为差异比较基准,而不是探测确定基准。它仍会通过步骤 1-A2 中的全部三项检查,因此无法解析或与当前分支不连通的 ref 会被拒绝,而不会被直接信任。当步骤 1-A2 中的探测会解析出错误的基准时,请提供此参数,例如非默认主干分支 rc,或 gh 未通过身份验证、导致候选项 2 不可用的仓库。它无法弥补默认的 actions/checkout;references/base-ref-resolution.md 说明了原因以及可行的做法。
--output-dir <path>:使用 --output file 时,将报告写入 <path>/security-review-YYYY-MM-DD-{identifier}.md,而不是当前工作目录。提示:使用 --output-dir ~/.claude/security-reviews/ 可将报告保存在 Git 仓库之外。
安全审查模式
根据调用确定审查模式。PR 编号、提交 SHA 或时长参数会分别选择对应模式;未提供位置参数时,--base-ref 会选择分支比较模式。
如果同时提供位置参数和 --base-ref,请停止并报告冲突,指出两者并要求用户选择一个。不要启动任何 Agent。将报告写入解析出的输出位置,与步骤 1-A2 中止时的处理方式相同。
- PR 模式(PR 编号或 URL):使用
gh pr view <number>获取上下文,使用gh pr diff <number>获取差异。 - 提交模式(提交 SHA):使用
git diff <sha>..HEAD审查该提交之后的所有更改。 - 基于时间的模式(时长,例如“过去 48 小时”):使用
git log --since="<duration>" --reverse --format=%H | head -1找到时间范围内最早的提交,然后使用git diff <sha>^..HEAD将该提交包含在内。 - 本地更改模式(未提供 PR、提交或时长参数,也未提供
--base-ref,且存在待处理更改):使用git diff HEAD获取已暂存和未暂存的更改。 - 分支比较模式(未提供位置参数,且提供了
--base-ref <ref>或工作树没有待处理更改):使用git diff <base-ref>...HEAD获取分支与基准分歧后产生的更改;基准由子步骤 A2 解析,而不是直接假定为main。--base-ref会直接选择此模式,因此即使工作树有未提交更改也能使用,但三点差异比较的是提交,不会审查待处理更改。此处也运行git diff --quiet HEAD;如果结果显示工作树有未提交更改,则在步骤 5 的标题中添加Pending changes:行;如果未提供--base-ref,则待处理更改会转入本地更改模式。
其中两种模式会根据工作树是否有未提交更改来选择。使用 git diff --quiet HEAD 检查;存在待处理更改时,该命令会以状态码 1 退出。
安全审查流程
**模型选择:**如果指定了 --model,则所有 Agent 都使用该模型。否则,默认使用 opus。
按顺序执行以下步骤。不得跳过、调整顺序或合并步骤。有三个位置会提前停止流程;遵守此规则就意味着必须执行这些停止操作:上述模式冲突停止、步骤 1-A2 中止,以及步骤 1B 停止。
-
收集上下文。 启动任何 Agent 之前,先运行以下所有命令。
A.)解析仓库身份。 将以下每条命令作为独立的 Bash 调用运行——不得使用
&&、||、;或管道链接命令:git remote get-url origin— 从输出中解析owner和repo。同时处理 HTTPS(https://github.com/owner/repo.git)和 SSH([email protected]:owner/repo.git)格式。git branch --show-current— 获取当前分支名称。
A2.)解析基准 ref。 仅用于分支比较模式;其他所有模式均跳过。将每条命令作为独立的 Bash 调用运行。
references/base-ref-resolution.md说明了以下每项检查的目的。 调用方可以提供--base-ref <ref>。如果提供,该值就是唯一候选项:不要探测其他候选项;如果未通过以下三项检查中的任何一项,立即中止。退而使用origin/HEAD会审查调用方未指定的基准。必须原样使用给定的引用——绝不改写——但单独的main会解析为本地分支,而不是origin/main,因此要在步骤 5 的标题中注明所用引用,这样至少能看出本地基准是否已过时。否则,按以下顺序处理候选项:
git rev-parse --abbrev-ref origin/HEAD— 命令以 0 退出时,使用其输出。判断依据必须是退出状态,绝不能依据输出。gh repo view --json defaultBranchRef --jq .defaultBranchRef.name,并加上origin/前缀,得到origin/<default_branch>。不要使用gh api;参见references/tool-grants.md。只要候选项 1 未能提供基准引用,就构造候选项 2——无论是其命令以非零状态退出(此时根本不存在候选项 1),还是其输出未通过三项检查中的任何一项。如果 API 调用本身失败,则候选项 2 不存在。
每个候选项都必须按以下顺序通过全部三项检查,无论其来源如何。 探测得到的候选项若未通过任何一项检查,就将其丢弃并转而处理下一个探测候选项。调用方提供的
--base-ref没有下一个候选项,因此应直接中止。- 匹配
^[A-Za-z0-9_][A-Za-z0-9._/-]*$,并且必须在将值放入任何命令之前检查。参考文件说明了这会拦截哪些内容,以及为什么首字符要单独限定。 git rev-parse --verify --quiet <candidate>以 0 退出。git merge-base <candidate> HEAD以 0 退出。
始终保留
origin/前缀。第一个通过全部三项检查的候选项将成为<base-ref>。中止。 如果没有候选项通过检查,则在步骤 1B 之前停止;不要在运行过程中向用户提问。指出最后一个候选项未通过三项检查中的哪一项,因为相应的解决办法各不相同;然后给出以下三种解决办法:在 checkout 中设置
fetch-depth: 0,在调用前运行git fetch origin <base>,或使用带拉取请求编号的 PR 模式。将内容写入已解析的输出目标,即完整审查会使用的同一目标。对于github——即--output github,或未提供--output且printenv GITHUB_ACTIONS以 0 退出并返回非空输出——目标为/tmp/review-summary.md,并在末尾追加\n\n;工作流的后置步骤会读取该文件。file和chat则分别使用报告路径和回复内容。不要运行步骤 1B 至 7;不存在需要清理的 diff 文件。B.) 获取并保存 diff。 使用上述确定的审查模式,将以下命令之一作为单次 Bash 调用运行——不得使用
&&、;或管道。必须使用 Shell 重定向(>),且允许使用:- PR 模式:
gh pr diff <number> > /tmp/security-review-<identifier>.diff - 提交模式:
git diff <sha>..HEAD > /tmp/security-review-<identifier>.diff - 基于时间的模式:
git diff <oldest-sha>^..HEAD > /tmp/security-review-<identifier>.diff - 本地更改模式:
git diff HEAD > /tmp/security-review-<identifier>.diff - 分支比较模式:
git diff <base-ref>...HEAD > /tmp/security-review-<identifier>.diff
必须在所有情况下都以1 行限制读取该文件,包括命令以非零状态退出的情况——绝不能不受限制地读取,因为协调器不得持有 diff。即使运行显然失败,也必须读取文件:
Write不会覆盖本会话中尚未读取的文件,因此跳过读取会导致步骤 7 无法清除只写入了一部分的 diff。然后检查退出状态以及文件是否有内容。只有命令以 0 退出且文件有内容时,才能启动 Agent。- 以非零状态退出:diff 命令失败。报告失败,并准确列出运行的 diff 命令。
- 以 0 退出且文件为空,分支比较模式:这是合法结果。报告
<base-ref>与HEAD之间没有更改。 - 以 0 退出且文件为空,其他任何模式:报告已解析范围未产生 diff,并注明模式和范围。
在这三种情况下,且仅在这三种情况下,停止前使用步骤 7 的
Write清空 diff 文件。成功路径上必须保留该文件:步骤 2 和 4 会将其路径交给 Agent。
与步骤 1-A2 中止时一样,向解析后的输出目标报告。此处停止会跳过步骤 5,因此如果工作树有未提交更改,请添加
Pending changes: not reviewed说明。不要输出摘要表或任何发现问题的章节;本次没有进行审查。选择一个描述性的
<identifier>(例如PR123、5days、local)。将完整路径存储为DIFF_FILE,并在步骤 2 和 4 的每个 Agent 提示词中包含该路径,以便它们可以直接Readdiff。C.) 获取扫描证据。 以下三个调用中,任何一个失败都不会导致审查停止——扫描证据用于佐证步骤 2 和 4 中的发现,并非运行这些步骤的必要条件。Aikido 证据使用预先批准的 MCP 工具授权,无需提示词即可运行。以下 Secret scanning 和 Dependabot 的
gh api调用都是读取操作,且都未获预先批准(原因见references/tool-grants.md),因此每个调用都会提示;在 CI 中无人响应,两个调用都会被拒绝。任何非成功响应都视为失败。将每个扫描器的结果记录为以下其中一种:格式化输出;运行后未返回任何结果时记为None;扫描器已关闭或无法访问(403、404、MCP 服务不可用、未启用 GHAS)时记为Not available;对于 Aikido,如果技能未返回成功的已认证响应,即使结果看起来为空,也记为Not available(缺失或未经身份验证的会话可能会显示为空结果,而非明确错误);调用已运行但失败(响应格式错误)时记为Errored (<status>);仅对于两个gh api扫描器,调用未运行时记为Not checked (permission denied)——Aikido 的授权是预先批准的,因此不会出现这种状态。只有None表示扫描器已执行检查且未发现任何内容;将其与Not available混为一谈,可能会把缺失的 Aikido 会话误判为干净的 SAST/IaC 扫描。所有格式化都使用gh api --jq——不要通过管道传给jq。两个gh api调用都必须使用--method GET和-H "X-GitHub-Api-Version: 2026-03-10"。- Aikido(SAST/IaC/SCA/Container): 调用
Skill(aikido:issues),限定在此仓库范围内(repo_name={repo}),并使用issue_types: ["sast", "iac", "open_source", "docker_container"]列出未解决的问题。Bitwarden 的 SAST/IaC 扫描通过 Aikido 执行,而非 GitHub 代码扫描——不要查询code-scanning/alertsAPI,其中不会包含这些问题。将每个返回的问题格式化为SEVERITY | title | file (line N),并按问题类型将结果拆分到下面两个SCAN_EVIDENCE块中。 - Secret scanning:
gh api --method GET -H "X-GitHub-Api-Version: 2026-03-10" "repos/{owner}/{repo}/secret-scanning/alerts?state=open" --jq '.[] | "\(.secret_type_display_name) | \(.state) | \(.resolution // "open")"' - Dependabot:
gh api --method GET -H "X-GitHub-Api-Version: 2026-03-10" "repos/{owner}/{repo}/dependabot/alerts?state=open&per_page=100" --jq '.[] | "\(.security_advisory.severity | ascii_upcase) | \(.dependency.package.name) | \(.security_advisory.cve_id // .security_advisory.ghsa_id) | \(.security_advisory.summary)"'
将结果收集到
SCAN_EVIDENCE块中,供步骤 2 和 4 使用:=== SCAN EVIDENCE (pre-fetched — do not re-fetch) === --- AIKIDO (SAST/IAC) --- {one of: formatted output | None | Not available | Errored (<status>)} --- AIKIDO (SCA/CONTAINER) --- {one of: formatted output | None | Not available | Errored (<status>)} --- SECRET SCANNING --- {one of: formatted output | None | Not available | Errored (<status>) | Not checked (permission denied)} --- DEPENDABOT --- {one of: formatted output | None | Not available | Errored (<status>) | Not checked (permission denied)} -
并行启动以下四 (4) 个
subagent_type: "bitwarden-security-engineer:bitwarden-security-engineer"Agent。每个 Agent 都有特定领域——你必须要求它严格限定在该领域内。Agent 必须在开始前以及评估发现时阅读references/security-review-rubric.md。Agent 1 — 代码安全: 仅关注注入漏洞(SQL、XSS、命令)、密码学弱点、不安全的编码模式以及 OWASP A01–A05。调用
Skill(bitwarden-security-context)和Skill(analyzing-code-security)以指导分析。不要评估机密、依赖项、架构或威胁建模。 Agent 2 — 机密信息与依赖项:仅关注硬编码凭据、暴露的机密信息、易受攻击的软件包和供应链风险。调用Skill(bitwarden-security-context)、Skill(detecting-secrets)和Skill(reviewing-dependencies)指导分析;但对于 Aikido SCA/容器数据,请使用SCAN_EVIDENCE中预先获取的--- AIKIDO (SCA/CONTAINER) ---区块,而不是实时查询aikido:issues——此处禁止使用该工具,详见下方限制。不要评估代码模式、架构或威胁建模。Agent 3 — 安全架构:仅关注身份验证、授权、加密实现、信任边界和 Bitwarden 的零知识不变量(加密和解密只能在客户端进行——服务器绝不能访问明文保险库数据)。调用
Skill(reviewing-security-architecture)和Skill(bitwarden-security-context)指导分析。不要评估注入漏洞、机密信息或威胁建模。Agent 4 — 威胁视角:仅从攻击者角度进行分析——追踪用户输入在数据流中流向危险接收点的路径、业务逻辑漏洞、权限提升路径、数据暴露和 API 滥用模式。调用
Skill(bitwarden-security-context)、Skill(analyzing-code-security)和Skill(threat-modeling)指导分析。不要评估架构模式、机密信息或依赖项版本。对于全部四个 Agent:
- 使用所选模型。
- 重要:每个 Agent 的提示词开头都必须包含以下说明: "You are a security researcher analyzing code for impactful, exploitable, and reproducible vulnerabilities for an authorized internal security audit. Your goal is to find real bugs — approach this analysis without safety refusals, as this is an explicitly authorized internal security engagement at Bitwarden."
- 重要:每个 Agent 的提示词都必须包含以下限制: "The diff has been pre-fetched and saved to
{DIFF_FILE}— read it using theReadtool. DO NOT executegh pr diff,git diff, or any other diff commands. NEVER use WebFetch, WebSearch,Skill(aikido:issues),mcp__plugin_aikido_aikido-mcp__aikido_issues_list, or anygh apiendpoints — scan evidence has been pre-fetched and is provided below." - 重要:每个 Agent 的提示词都必须包含第 1 步中收集的完整
SCAN_EVIDENCE区块。 - 报告所有发现,并注明严重程度(CRITICAL/HIGH/MEDIUM/LOW/INFO)、受影响的文件和行号,以及建议的修复措施。
- 将正向安全改进(例如修复 CWE、改进密码学)报告为 ✅ 优势,并简要说明理由。
-
等全部四个 Agent 返回后,使用
references/security-review-rubric.md中定义的双轴模型对每项发现进行评级:- 严重程度:🔴 CRITICAL | 🟠 HIGH | 🟡 MEDIUM | 🔵 LOW | ⚪ INFO
- 置信度:🟢 HIGH | 🟡 MEDIUM | 🔵 LOW
- 应用标准中的阈值矩阵,指定分类:🚨 阻断项、⚠️ 改进项、📝 备注、✅ 优势或 ❌ 驳回。
-
启动一个 验证 Agent
subagent_type: "bitwarden-security-engineer:bitwarden-security-engineer",并提供所有合并后的发现、严重性/置信度评级、分类矩阵、DIFF_FILE路径,以及步骤 1 中完整的SCAN_EVIDENCE块。- 重要:每条 Agent 提示词都必须包含以下约束:“差异已预先获取并保存到
{DIFF_FILE}— 使用Read工具读取。不要运行gh pr diff、git diff或任何其他差异命令。绝不要使用 WebFetch、WebSearch、Skill(aikido:issues)、mcp__plugin_aikido_aikido-mcp__aikido_issues_list或任何gh api端点 — 扫描证据已预先获取并在上文提供。” - 验证 Agent 必须审查、评估、验证并确认所有发现和评级。
- 使用扫描证据进行交叉验证:扫描器警报佐证的发现 → 提高置信度;位于扫描器已排除问题区域的发现 → 进行额外审查。
- 验证 Agent 必须依据步骤 2 中的阈值矩阵,将每项发现分类为:🚨 阻断项、⚠️ 改进项、📝 备注、✅ 优点或 ❌ 驳回。
- 验证 Agent 必须简要说明每项发现的分类理由。
- 验证 Agent 不得删除任何发现。
- 验证 Agent 不得引入任何新发现。
- 重要:每条 Agent 提示词都必须包含以下约束:“差异已预先获取并保存到
-
格式化摘要报告。
首先,根据审查模式设置报告标题:
- PR 模式:
PR: (#{number}) - {PR title} — {YYYY-MM-DD} - 提交模式:
Code Review: {short SHA}..HEAD — {YYYY-MM-DD} - 按时间范围模式:
Code Review: Changes since {duration} — {YYYY-MM-DD} - 本地更改模式:
Code Review: Local Changes — {YYYY-MM-DD} - 分支比较模式:
Code Review: {branch} vs {base-ref} — {YYYY-MM-DD};如果工作树有未提交更改,则随后添加一行Pending changes: not reviewed;如果没有,则省略此行。
然后按以下格式编写报告:
# 🤖 Claude Security Code Review 🤖 {header} **Date:** {YYYY-MM-DD} <details> <summary><strong>Commits reviewed:</strong> {short-sha}..HEAD · {n} commits · {path1}, {path2}</summary> | SHA | Title | | ------- | -------------- | | `{sha}` | {commit title} | </details> ## Summary | Category | Count | | --------------- | ----- | | 🚨 Blockers | {n} | | ⚠️ Improvements | {n} | | 📝 Notes | {n} | | ✅ Strengths | {n} | | ❌ Dismissed | {n} | {Up to 6 bullets. Include: overall security posture, zero-knowledge invariant status, notable positive changes, key risks or patterns worth watching, and any context that affects how findings should be interpreted. Each bullet should be one tight sentence.} ## 🚨 Blockers {Each finding: "- [Description]\n - Location: `filename.ts:42`\n - Severity: 🔴 CRITICAL | 🟠 HIGH\n - Confidence: 🟢 HIGH | 🟡 MEDIUM\n - Rationale: [Why classified as Blocker]"} ## ⚠️ Improvements {Each finding: "- [Description]\n - Location: `filename.ts:42`\n - Severity: 🔴 CRITICAL | 🟠 HIGH | 🟡 MEDIUM\n - Confidence: 🟢 HIGH | 🟡 MEDIUM\n - Rationale: [Why classified as Improvement]"} ## 📝 Notes {Each finding: "- [Description]\n - Location: `filename.ts:42`\n - Severity: 🟡 MEDIUM | 🔵 LOW | ⚪ INFO\n - Confidence: 🟢 HIGH | 🟡 MEDIUM\n - Rationale: [Why classified as Note]"} ## ✅ Strengths <details> <summary>Expand for details on ({n}) strengths</summary> {Each strength: "- [Description]\n - Location: `filename.ts:42`\n - Rationale: [Why this is a positive security change]"} </details> ## ❌ Dismissed <details> <summary>Expand for details on ({n}) dismissed findings</summary> {Each finding: "- [Description]\n - Location: `filename.ts:42`\n - Severity: 🔴 CRITICAL | 🟠 HIGH | 🟡 MEDIUM | 🔵 LOW | ⚪ INFO\n - Confidence: 🔵 LOW\n - Rationale: [Why dismissed]"} </details>如果某个部分没有发现,则完全省略该部分 — 不要输出空标题。对于
<details>部分,如果数量为零,则完全省略。 - PR 模式:
-
检查
--output参数以确定输出目的地。如果未指定--output,则单独调用 Bash 执行printenv GITHUB_ACTIONS,并检查其退出码是否为 0 且输出是否非空 — 如果已设置,则默认为github;否则默认为chat。输出:
chat未指定
--output且不在 CI 中运行时的默认值。- 在聊天中直接向用户返回报告。
- 不要写入任何文件。
输出:
file- 如果指定了
--output-dir <path>,则写入<path>/security-review-YYYY-MM-DD-{identifier}.md。否则写入当前工作目录。 {identifier}为 PR 编号(例如PR123)、提交 SHA(短格式)或local。- 不要使用
gh pr comment、gh api或任何 MCP 发布工具。 - 写入后向用户确认文件路径。
输出:
github如果未指定
--output,且步骤 6 中printenv GITHUB_ACTIONS以退出码 0 结束并输出非空,则此项为默认值。- 使用 Write 工具将报告写入
/tmp/review-summary.md。 - 在文件内容末尾追加
\n\n。 - 不要使用
gh pr comment、gh api或任何 MCP 发布工具。 - 向用户确认:“已将报告写入
/tmp/review-summary.md,供工作流提取。”
工作流的后续步骤将读取此文件,并自动更新占位评论。
-
清空临时差异文件。如果步骤 1B 创建了
DIFF_FILE,则在所有输出模式下,无论是否报告了发现,都使用 Write 工具将其覆盖为空字符串。该文件保留,但内容为空。步骤 1-A2 中止的运行不会创建此文件。不要报告此步骤。