From 0f4aa75f15ae28135650be0b2c2be37719d1897d Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Fri, 10 Jul 2026 16:39:58 -0400 Subject: [PATCH 1/2] major improvements for timing, duplicate reduction, and status changes --- .../skills/auditing-hackerone-vulns/SKILL.md | 128 ++++++++++++------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md b/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md index eb6b9c5d..34bb27ef 100644 --- a/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md +++ b/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md @@ -1,7 +1,7 @@ --- name: auditing-hackerone-vulns description: Audit all open HackerOne-sourced VULN Jira tickets and their linked engineering child items to identify what needs action. Use this skill whenever the user wants to: check VULN ticket status, see which HackerOne findings need status updates, identify vulnerabilities ready to verify or close, run a remediation audit, check "what do I need to do on my VULN tickets today", or get a prioritized view of open vulnerabilities. Outputs a sorted action table with emoji tokens. Always use this skill for HackerOne/VULN remediation tracking and status correlation tasks — don't try to do it from scratch. -allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_issues, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_issue, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_issue_remote_links, Bash(gh api --method GET *), Bash(gh pr view *), Bash(gh release list *), Bash(gh api repos/bitwarden/*/compare/*), Bash(gh search prs *) +allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_issues, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_issue, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_issue_remote_links, Bash(gh api --method GET *), Bash(gh pr view *), Bash(gh api repos/bitwarden/*/compare/*), Bash(gh search prs *) --- ## Action tokens (sorted order in output) @@ -17,34 +17,63 @@ allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search --- +## Tool usage rules — read before executing any step + +- **Only use tools listed in `allowed-tools`.** Do not use `curl`, `wget`, `python3`, `node`, or any interpreter or HTTP client not in the list. +- **Do not write files.** Do not write to `/tmp/`, to any file path, or use heredocs (`cat > FILE << 'EOF'`). Hold all data in-memory between steps. +- **Do not use shell parallelism.** Do not use `&` background processes, `wait`, `declare -A` / `typeset -A` associative arrays, or multi-step bash/zsh scripts. When this skill says "in parallel", it means issue multiple **Claude tool calls** in the same response turn — not shell concurrency. +- **No interpreter pipes.** Do not pipe command output to `python3`, `node`, `ruby`, or any interpreter. Use `--jq` or standalone `jq` for JSON. +- **No error suppression.** Do not add `2>/dev/null` to any command. Silent failures are indistinguishable from empty results and waste follow-up calls. + +--- + ## Step 1 — Query open VULN issues Use `search_issues` with this JQL: ``` -project = VULN AND status not in (Done, Verified) AND "Source" = "HackerOne" ORDER BY priority DESC, updated DESC +project = VULN AND status not in (Done, Verified, Closed, Rejected, Resolved, Canceled) AND "Source" = "HackerOne" ORDER BY priority DESC, updated DESC ``` -Request fields: `summary`, `status`, `description`, `priority`, `created`, `updated` +Request fields: `summary`, `status`, `description`, `priority`, `created`, `updated`, `issuelinks` Paginate if needed (default max 50; use `nextPageToken` to get all). --- -## Step 2 — Find child engineering items for each VULN +## Step 2 — Collect and batch-fetch child engineering items + +**Do not loop over VULNs one at a time.** Instead: + +1. **Extract child keys from `issuelinks`** already returned in Step 1. For each VULN, scan its `issuelinks` array and collect the keys of linked issues that are NOT in the VULN project (those are engineering tickets, not sibling VULNs). + +2. **Deduplicate** the collected keys into a single list. + +3. **Batch-fetch all child items in one JQL call** (split into pages of 50 if needed): + + ``` + issue in (KEY1, KEY2, KEY3, ...) + ``` + + Request fields: `summary`, `status`, `fixVersions`, `project` + +4. Build a lookup map: `VULN key → [child item objects]`. -For each VULN key, run: +5. VULNs whose `issuelinks` array is empty or has only VULN-project siblings have no child items yet → token ➖. + +> **Why**: fetching `issuelinks` in Step 1 and batching child lookups reduces N+1 sequential Jira calls to 2–3 total calls regardless of how many VULNs are open. + +**Fallback**: If `issuelinks` is not populated in the Step 1 response (some Jira configurations omit it), use a batched JQL with parallel tool calls (10–15 VULNs at a time): ``` -issue in linkedIssues("VULN-XXX") +issue in linkedIssues("VULN-1") OR issue in linkedIssues("VULN-2") OR issue in linkedIssues("VULN-3") ... ``` -Request fields: `summary`, `status`, `fixVersions`, `project` +**Do NOT call `get_issue` to look up `issuelinks`** — it does not return linked issues either. Skip straight to the batched JQL above. - A VULN may have **multiple** child items. Collect them all. - Ignore items in the same VULN project (those are sibling VULNs, not engineering tickets). - Child items with `[VULN]` in the summary are the primary engineering tracking items. -- Some VULNs (especially fresh "Ready for Resolution") may have no child items yet → token ➖. --- @@ -52,12 +81,12 @@ Request fields: `summary`, `status`, `fixVersions`, `project` Map Jira statuses to these categories: -| Category | Example statuses | -| --------------- | --------------------------------------------------------------- | -| **Not Started** | To Do, Backlog, Open, New, In Analysis | -| **In Progress** | In Progress, In Development, In Review, Code Review, In Testing | -| **Done** | Done, Closed, Resolved, Completed | -| **Abandoned** | Abandoned, Won't Fix, Duplicate, Canceled | +| Category | Example statuses | +| --------------- | ---------------------------------------------------------------------- | +| **Not Started** | To Do, Backlog, Open, New, In Analysis, Ready for Dev | +| **In Progress** | In Progress, In Development, In Review, Code Review, In Testing, In QA | +| **Done** | Done, Closed, Resolved, Completed | +| **Abandoned** | Abandoned, Won't Fix, Duplicate, Canceled | For VULNs with multiple children: the **highest-priority active child** drives the action token. "In Progress" outranks "Not Started"; "Done" only counts if all non-abandoned children are Done. @@ -65,55 +94,67 @@ For VULNs with multiple children: the **highest-priority active child** drives t ## Step 4 — Search GitHub for PRs linked to child items -**JSON parsing rule** — Always use `gh`'s built-in `--jq` flag or standalone `jq` for JSON parsing. Never pipe to `python3` or any other interpreter — Python is not in this skill's `allowed-tools` and will trigger a permission prompt. If stderr suppression is needed, place `2>/dev/null` _after_ the full `gh api ... --jq '...'` command, not before: +**Only execute this step for child items classified as Done in Step 3.** If every child item for a VULN is Not Started, In Progress, or Abandoned, skip directly to Step 5 for that VULN — there is no PR to find yet and no GitHub call is needed. This gate eliminates the majority of GitHub lookups. + +**Do not trust Jira's `Fix Version` "(Released)" annotation as evidence of deployment.** The field is set aspirationally when the engineering ticket is resolved and is not corrected when the cherry-pick into the release branch is missed. The release tag's actual commit log is the only source of truth. + +**Never fetch release note body text.** Do not call `repos/bitwarden/REPO/releases/tags/TAG` and read the `.body` field. Release note bodies are hundreds of lines of markdown and will flood the context window. + +**JSON parsing rule** — Always use `gh`'s built-in `--jq` flag or standalone `jq` for JSON parsing. Never pipe to `python3` or any other interpreter — Python is not in this skill's `allowed-tools` and will trigger a permission prompt. Do not add `2>/dev/null` to any calls — silent failures look identical to empty results. ```bash -# Correct — 2>/dev/null after --jq, before the next pipe -gh api --method GET "repos/bitwarden/REPO/compare/A...B?per_page=250" \ - --jq '.commits[] | .commit.message | split("\n")[0]' 2>/dev/null \ - | grep "#PR_NUMBER" +# Correct — use --jq, not a separate pipe to python3 +gh api --method GET "search/issues?q=PM-12345+type:pr+org:bitwarden&per_page=10" \ + --jq '.items[] | {number,title,state,mergedAt:.pull_request.merged_at,url:.html_url}' ``` -**PR search** — Use `gh search prs` to find PRs. If that fails, fallback to the GitHub API with `gh api --method GET "search/"` +**PR search** — Use the GitHub Search API to find PRs. You may issue multiple independent search tool calls in the same response turn — one per child item key — rather than one at a time. Do not write a shell script to batch them. ```bash gh api --method GET "search/issues?q=CHILD-KEY+type:pr+org:bitwarden&per_page=10" \ - --jq '.items[] | {number,title,state,mergedAt,url:.html_url}' + --jq '.items[] | {number,title,state,mergedAt:.pull_request.merged_at,url:.html_url}' ``` -For each PR that appears to be the correct fix (match on title/ticket key), get accurate merge details: +Note the field mapping: the Search Issues API returns `pull_request.merged_at`, not `mergedAt`. Always use `.pull_request.merged_at` in the `--jq` filter — otherwise `mergedAt` will be null for every result. + +**Two-attempt cap on PR searches.** If the first search (by child item key) returns no results, try one more search using the VULN key (e.g., `VULN-529+type:pr+org:bitwarden`). If that also returns no results, mark "No PR found" and move on. Do not try keyword searches, repo-scoped retries, or other fallback queries — they rarely succeed and waste significant time. + +**Skip `gh pr view` when merge date is already known.** Only call `gh pr view PR_URL --json state,mergedAt,baseRefName,title` if the search returned `mergedAt: null`. If the search already returned a non-null merge date, you have what you need — do not make a redundant follow-up call. If `gh pr view` also returns `mergedAt: null`, the PR was not merged — record it as closed-without-merge and move on; do not search further. + +**Release list caching** — Fetch releases **once per repo** and reuse the result for all PRs in that repo. Do not re-fetch for each PR. ```bash -gh pr view PR_URL --json state,mergedAt,mergeCommit,baseRefName,title +# Note: gh release list is blocked — use the Releases API directly +gh api --method GET "repos/bitwarden/REPO/releases?per_page=20" \ + --jq '.[] | select(.draft == false and .prerelease == false) | {tagName: .tag_name, publishedAt: .published_at}' ``` -**Determining release inclusion** — Bitwarden's repos (server, clients) use **release branches with cherry-picks**. The merge commit SHA on `main` gets a _new SHA_ when cherry-picked, so `compare/TAG...COMMIT_SHA` always returns "diverged" and is **unreliable**. Do not use it. +**Determining release inclusion** — Bitwarden uses two release strategies. Use the correct method per repo: + +**`bitwarden/server` and `bitwarden/clients` (cherry-pick workflow)** — release branches are cut from `main` and fixes must be **explicitly cherry-picked** to ship. A PR merged before a release was published is **not** automatically in it — the cherry-pick may have slipped. Merge-date inference produces false positives here. -The correct method is to compare consecutive release tags and search for the PR number in commit messages (cherry-picks preserve the original PR number): +Verify cherry-pick presence by searching the release range's commit messages for the PR number. Bitwarden cherry-pick commits preserve the original `(#NNNN)` suffix from the source PR: ```bash -# 1. List non-draft, non-prerelease tags for the relevant repo -gh release list --repo bitwarden/REPO --limit 20 \ - --json tagName,publishedAt,isDraft,isPrerelease \ - | jq '.[] | select(.isDraft == false and .isPrerelease == false)' - -# 2. Find the two consecutive tags that bracket the expected fix release -# (e.g., v2026.4.0 and v2026.4.1) - -# 3. List all commits in that range and grep for the PR number -gh api --method GET "repos/bitwarden/REPO/compare/TAG_PREV...TAG_RELEASE?per_page=250" \ - --jq '.commits[] | .commit.message | split("\n")[0]' \ - | grep "#PR_NUMBER" +gh api --method GET "repos/bitwarden/REPO/compare/PREV_TAG...CANDIDATE_TAG" \ + --jq '.commits[].commit.message' | grep -E "\(#PR_NUMBER\)" ``` -- If the PR number **is found** → the fix is in that release ✅ -- If the PR number **is NOT found** → the fix missed the RC cut and is NOT in that release ❌ +1. From the cached release list, identify the **earliest release where `publishedAt > PR.mergedAt`** — that is the **candidate** release (not yet confirmed). +2. Run the compare-and-grep above against `PREV_TAG...CANDIDATE_TAG`. + - Match found → fix is in that release ✅ + - No match → cherry-pick was missed; fix is on `main` only → mark 🔵 Monitor and flag for engineering follow-up +3. If `PR.mergedAt` is after the latest release's `publishedAt` → not yet released → mark 🔵 Monitor. + +> The earlier "Do not use `compare` for these repos" guidance referred to `MERGE_COMMIT_SHA...TAG` comparisons (which return "diverged" for cherry-picked commits with different SHAs). Tag-to-tag `compare` is fine — it lists what shipped between two releases regardless of cherry-pick SHA rewrites — and is the correct tool here. + +**clients monorepo tags** — only use the tag type relevant to the affected client: `web-vYYYY.M.P`, `browser-vYYYY.M.P`, `desktop-vYYYY.M.P`, `cli-vYYYY.M.P`. Do not check all four types for every PR. -**clients monorepo note**: The `bitwarden/clients` repo publishes separate release tags per client type: `web-vYYYY.M.P`, `cli-vYYYY.M.P`, `browser-vYYYY.M.P`, `desktop-vYYYY.M.P`. A fix deployed in `web-v2026.4.2` does **not** mean the browser extension has it — always check the specific product's tag if the vulnerability affects a specific client. +**Direct-push repos** (e.g., `bitwarden/sm-action`) — commits push directly to release branches without cherry-picks. For these, use `compare/COMMIT_SHA...TAG`: if status is `"behind"` or `"identical"`, the commit is in the release. First check commit count; skip compare if `total_commits >= 500`. -**Simple repos** (e.g., sm-action) use direct pushes without cherry-picks. For those, `compare/COMMIT_SHA...TAG` returning `"ahead"` means the TAG is a descendant of the commit — i.e., the commit IS in the release. +**Never repeat an API call.** Before issuing any `releases` or `search` call, confirm you have not already received a response for that exact endpoint and parameters in this session. If you have, use the cached result. -To confirm a release has been **deployed to production**, check the published date from `gh release list`. If `publishedAt` is in the past and the release is not draft/prerelease, it is live. +To confirm a release has been **deployed to production**, check the `publishedAt` date from the releases API response. If it is in the past and the release is not draft/prerelease, it is live. --- @@ -136,7 +177,8 @@ VULN status "In Progress" or "In Review": VULN status "Remediated": → Cannot determine release? → 🔵 Monitor → PR in an upcoming/unreleased version? → 🔵 Monitor (release pending) - → PR in a released, deployed version? → 🟢 Verify & Close + → PR in a released, deployed version + (verified by commit-presence check, not Jira Fix Version)? → 🟢 Verify & Close ``` The **Remediation Date** should be the date the fix PR was merged to the default branch. From 16a034ce6e68b928f3d545ae58ed2e9d4d49781f Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Mon, 13 Jul 2026 13:09:42 -0400 Subject: [PATCH 2/2] feat(bitwarden-security-engineer): surface Verified VULN tickets for close out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auditing-hackerone-vulns skill previously excluded Verified VULN tickets from the audit query entirely. A Verified VULN has had its fix confirmed in production but its Jira ticket still needs to be moved to Closed, so those tickets silently dropped out of the report. Stop excluding Verified from the JQL and route these tickets to a new 🏁 Close Out action token. Verified tickets skip the child-item and GitHub release checks since the fix is already confirmed, and go straight to the close-out step. Adds the token to the classification table, decision tree, summary counts, and output template. Bumps the plugin to 1.3.0 with changelog entry, and adds "aspirationally" and "heredocs" to the cspell dictionary. --- .claude-plugin/marketplace.json | 2 +- .cspell.json | 2 ++ README.md | 2 +- .../.claude-plugin/plugin.json | 2 +- plugins/bitwarden-security-engineer/CHANGELOG.md | 6 ++++++ .../skills/auditing-hackerone-vulns/SKILL.md | 15 ++++++++++++++- 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8f63488e..585fa7bd 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -54,7 +54,7 @@ { "name": "bitwarden-security-engineer", "source": "./plugins/bitwarden-security-engineer", - "version": "1.2.0", + "version": "1.3.0", "description": "Application security engineering assistant for vulnerability triage, threat modeling, and secure code analysis." }, { diff --git a/.cspell.json b/.cspell.json index c42db5a0..89a58dab 100644 --- a/.cspell.json +++ b/.cspell.json @@ -10,6 +10,7 @@ "appsettings", "architecting", "askable", + "aspirationally", "ASVS", "atlassian", "Bitwarden", @@ -56,6 +57,7 @@ "grype", "hackerone", "hardBreak", + "heredocs", "HMAC", "hotspot", "hotspots", diff --git a/README.md b/README.md index 27ab17a0..0f36ced9 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A curated collection of plugins for AI-assisted development at Bitwarden. Enable | [bitwarden-devops-engineer](plugins/bitwarden-devops-engineer/) | 0.1.4 | DevOps engineering assistant: workflow compliance linting, action security auditing, and org-wide CI/CD remediation | | [bitwarden-init](plugins/bitwarden-init/) | 1.2.0 | Initialize and enhance CLAUDE.md files with Bitwarden's standardized template format | | [bitwarden-product-analyst](plugins/bitwarden-product-analyst/) | 0.1.5 | Product analyst agent for creating comprehensive Bitwarden requirements documents from multiple sources | -| [bitwarden-security-engineer](plugins/bitwarden-security-engineer/) | 1.2.0 | Application security engineering: vulnerability triage, threat modeling, and secure code analysis | +| [bitwarden-security-engineer](plugins/bitwarden-security-engineer/) | 1.3.0 | Application security engineering: vulnerability triage, threat modeling, and secure code analysis | | [bitwarden-software-engineer](plugins/bitwarden-software-engineer/) | 1.0.0 | Software engineer agent for a Bitwarden product team. Implements stories, tasks, and bugs with code quality, performance, security, and team comms in mind. | | [claude-config-validator](plugins/claude-config-validator/) | 1.1.1 | Validates Claude Code configuration files for security, structure, and quality | | [claude-retrospective](plugins/claude-retrospective/) | 1.1.1 | Analyze Claude Code sessions to identify successful patterns and improvement opportunities | diff --git a/plugins/bitwarden-security-engineer/.claude-plugin/plugin.json b/plugins/bitwarden-security-engineer/.claude-plugin/plugin.json index dce4ea8a..9f31fc9d 100644 --- a/plugins/bitwarden-security-engineer/.claude-plugin/plugin.json +++ b/plugins/bitwarden-security-engineer/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "bitwarden-security-engineer", - "version": "1.2.0", + "version": "1.3.0", "description": "Application security engineering assistant for vulnerability triage, threat modeling, and secure code analysis at Bitwarden.", "author": { "name": "Bitwarden", diff --git a/plugins/bitwarden-security-engineer/CHANGELOG.md b/plugins/bitwarden-security-engineer/CHANGELOG.md index ef6a891d..2c860cf0 100644 --- a/plugins/bitwarden-security-engineer/CHANGELOG.md +++ b/plugins/bitwarden-security-engineer/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the `bitwarden-security-engineer` plugin will be document The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2026-07-10 + +### Changed + +- `auditing-hackerone-vulns` skill: stopped excluding `Verified` VULN tickets from the audit query. A Verified VULN has had its fix confirmed in production but the Jira ticket still needs to be moved to `Closed`, and previously these dropped out of the report entirely. They now surface under a new 🏁 Close Out action token so the remaining status flip isn't forgotten. Verified tickets skip the child-item and GitHub release-inclusion steps since the fix is already confirmed. + ## [1.2.0] - 2026-05-08 ### Added diff --git a/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md b/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md index 34bb27ef..8006bd03 100644 --- a/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md +++ b/plugins/bitwarden-security-engineer/skills/auditing-hackerone-vulns/SKILL.md @@ -11,6 +11,7 @@ allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search | 🔴 | **Update VULN Status** | Child item has progressed (In Progress/Review) but VULN is still at a lower status | | 🟡 | **Mark Remediated** | Child item is Done — set Remediation Date to merged PR date and move VULN to Remediated | | 🟢 | **Verify & Close** | Fix is in a release that has already shipped — verify in prod, add Confirmation Date, close HackerOne | +| 🏁 | **Close Out** | VULN is already Verified (fix confirmed in prod) — move the Jira ticket to Closed | | 🔵 | **Monitor** | Work is actively in progress or in a pending release; no action needed yet | | ⚪ | **Waiting** | Child item exists but hasn't started | | ➖ | **No Child Item** | VULN is Ready for Resolution but no engineering ticket linked yet | @@ -32,13 +33,15 @@ allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search Use `search_issues` with this JQL: ``` -project = VULN AND status not in (Done, Verified, Closed, Rejected, Resolved, Canceled) AND "Source" = "HackerOne" ORDER BY priority DESC, updated DESC +project = VULN AND status not in (Done, Closed, Rejected, Resolved, Canceled) AND "Source" = "HackerOne" ORDER BY priority DESC, updated DESC ``` Request fields: `summary`, `status`, `description`, `priority`, `created`, `updated`, `issuelinks` Paginate if needed (default max 50; use `nextPageToken` to get all). +> **Note**: `Verified` is intentionally **not** excluded. A Verified VULN has had its fix confirmed in production but the Jira ticket has not yet been moved to `Closed`. These surface under the 🏁 Close Out token so the remaining status flip doesn't get forgotten. They need no child-item or GitHub lookups — skip Steps 2–4 for them and route straight to 🏁 in Step 5. + --- ## Step 2 — Collect and batch-fetch child engineering items @@ -179,6 +182,9 @@ VULN status "Remediated": → PR in an upcoming/unreleased version? → 🔵 Monitor (release pending) → PR in a released, deployed version (verified by commit-presence check, not Jira Fix Version)? → 🟢 Verify & Close + +VULN status "Verified": + → Fix already confirmed in prod; ticket just needs closing → 🏁 Move to Closed ``` The **Remediation Date** should be the date the fix PR was merged to the default branch. @@ -199,6 +205,7 @@ Use this template. Omit any section (including `
` blocks) that has zero | 🔴 | Need Status Update | {n} | | 🟡 | Ready to Mark Remediated | {n} | | 🟢 | Ready to Verify & Close | {n} | +| 🏁 | Ready to Close Out | {n} | | 🔵 | Monitoring | {n} | | ⚪ | Waiting | {n} | | ➖ | Missing Child Item | {n} | @@ -223,6 +230,12 @@ Use this template. Omit any section (including `
` blocks) that has zero | --------------- | -------- | ------------------------------ | --------------- | --------------- | ------------------------------------ | ------------------------------------------------------------------- | | [VULN-529](...) | High | Summary truncated to ~60 chars | [#3673748](...) | [PM-35250](...) | [#1234](...) → v2026.4.0 ✅ deployed | Verify fix in prod, add Confirmation Date, close HackerOne #3673748 | +## 🏁 Close Out + +| VULN | Priority | Summary | HackerOne | VULN Status | Action | +| --------------- | -------- | ------------------------------ | --------------- | ----------- | -------------------------------------------------- | +| [VULN-442](...) | Medium | Summary truncated to ~60 chars | [#3673748](...) | Verified | Move VULN to Closed (fix already verified in prod) | +
🔵 Monitoring ({n} items — no action needed yet)