Vulnogram

July 7, 2026 · View on GitHub

The Vulnogram-side mechanics of maintaining a CVE record from allocation through publication. The generic handling process (steps 13–15 of ../../README.md) describes what has to happen; this file documents the Vulnogram-specific how.

Per-project URL templates live in ../../<project-config>/project.md (cve_tool_record_url_template, cve_tool_source_tab_url_template).

Record URLs

PurposeURL
Record page (human-readable + edit surface)https://cveprocess.apache.org/cve5/<CVE-ID>
#source tab (paste-the-JSON target)https://cveprocess.apache.org/cve5/<CVE-ID>#source
#json tab (rendered view of the stored JSON)https://cveprocess.apache.org/cve5/<CVE-ID>#json
#email tab (preview the advisory email before sending)https://cveprocess.apache.org/cve5/<CVE-ID>#email

The #email tab is the email-preview surface: it renders the advisory exactly as Vulnogram will send it to <users-list> and <announce-list> — same subject, same body, same recipient list. The release-manager checklist in the Release-manager checklist section below calls this out as a load-bearing checkpoint before hitting Send, because the preview surfaces formatting issues (truncation, broken markdown, missing patch links) that the JSON view does not.

The ASF CVE tool requires ASF OAuth; non-security-team members can see a record only after it has moved to PUBLIC (at which point the mirrored record at cve.org/CVERecord?id=<CVE-ID> is the canonical public link per the "Linking CVEs" section of ../../AGENTS.md).

Two record-write paths — API (default) and copy-paste (fallback)

The release manager has two ways to push the CVE JSON onto a Vulnogram record. The API path is the default proposal — it runs from the same shell the rest of the skills do and removes the manual paste round-trip. The copy-paste flow stays documented because (a) ASF-OAuth sessions expire and the API path needs a re-setup once a session ages out, and (b) some operators prefer not to keep a session-cookie file on disk at all.

PathWhenOne-linerSetup
API (default)Every paste step in the Release-manager checklist belowuv run --project <framework>/tools/cve-tool-vulnogram/oauth-api vulnogram-api-record-update --cve-id <CVE-ID> --json-file <path>One-time vulnogram-api-setup per machine — see oauth-api/README.md
Copy-paste (fallback)Operator opted out (tools.vulnogram.api_backend: copy_paste in .apache-magpie-overrides/user.md), or the API path returned SessionExpired and re-setup is deferredOpen #source, paste, Save (see #source paste flow)None

The agentic skills (security-cve-allocate, security-issue-sync) run vulnogram-api-check before proposing the paste step. Possible outcomes drive the proposal:

  • valid (exit 0) — propose the API one-liner; the copy-paste recipe stays available as a one-line "or open #source and paste" fallback for users who want the visual confirmation.
  • expired (exit 1) — propose vulnogram-api-setup first and the API one-liner second, with the copy-paste recipe as the fallback if the operator wants to skip re-setup for this CVE.
  • not-configured (exit 2) — propose vulnogram-api-setup with a one-line "or skip setup and use the copy-paste flow below" hint. Do not force setup on an unwilling operator.

State transitions happen at three different layers depending on which arrow you're crossing:

  • DRAFTREVIEW — sync-driven via the generator. The generate-cve-json tool emits the correct state in the JSON based on body-field readiness; sync pushes via vulnogram-api-record-update; Vulnogram accepts the state from the document body. No separate state-flip call.
  • REVIEWREADY — release-manager UI click. The generator cannot decide this from the tracker body alone (it depends on whether reviewer comments are closed), so it stays a human action.
  • READYPUBLIC — sync-driven via the dedicated vulnogram-api-record-publish CLI, fired when the advisory archive URL has been captured. The CLI defaults to refusing the publish unless the current state is REVIEW (widen with --allow-state for the rare cases where the RM has already moved to READY manually).

#source paste flow

The generic release-manager workflow is "push the final CVE record and close the issue" (step 15). On Vulnogram that decomposes into:

  1. Open the record's #source tab at https://cveprocess.apache.org/cve5/<CVE-ID>#source.
  2. Copy the CVE JSON from the tracking issue's embedded attachment (regenerated by the generate-cve-json tool on every sync — see the "Record-generator round trip" section below).
  3. Paste into the #source form and Save.
  4. Use the UI action to move the record from REVIEW to PUBLIC (or from DRAFT to REVIEW first if the record is still in the pre-review state).

PUBLIC is the terminal state — Vulnogram pushes the record to cve.org via the CNA feed once the state lands there.

State machine

Vulnogram wraps every record in a CNA_private envelope whose state field drives the visibility + CNA-feed push. The four states the generic skills interact with:

StateSet byWhat it means
DRAFTAllocation (initial state post-allocation)Record exists, ID is reserved, but content is still being filled in. Not visible on cve.org.
REVIEWSync, via the generator (post-2026 convention; see below)Ready for CNA review. ASF CNA reviewers may leave reviewer comments at this point (see "Reviewer-comment signal" below). Still not on cve.org.
READYRelease manager once review feedback is addressed (or immediately after REVIEW if no feedback arrived)Content is final and the record is staged for the advisory-send step. The advisory emails are dispatched from Vulnogram while in READY. Still not on cve.org.
PUBLICSync, via vulnogram-api-record-publish, when the advisory archive URL is captured on the trackerRecord pushed to cve.org. World-readable. The generic tracking-issue lifecycle terminates at the close action once this state has been reached.

DRAFTREVIEW — sync-driven via the generator. The generate-cve-json tool decides REVIEW versus DRAFT automatically based on the readiness of the tracker's body fields — see the _is_cna_ready_for_review helper in generate-cve-json/src/…/cve_json.py. When all required fields (CVE ID, title, description, affected versions, CWE, non-Unknown severity, at least one credit, at least one reference) are populated, the generated JSON carries CNA_private.state = "REVIEW"; when any field is missing, it carries "DRAFT". Sync pushes the generated JSON verbatim via vulnogram-api-record-update, and Vulnogram accepts the state field from the document body — no separate state-flip API call is needed. The release manager never has to click DRAFTREVIEW manually; the security-issue-sync skill gates the RM hand-off on the post-push state being REVIEW (see security-issue-sync Step 2b Two-stage gate).

REVIEWREADY — release-manager UI click. Happens once any reviewer comments have been addressed (via the body-field round-trip described in the Record-generator round trip section below), or immediately after REVIEW when no comments arrive. READY is the state Vulnogram expects when the release manager triggers the advisory email send. The generator does not emit READY directly because it cannot tell, from the tracker body alone, whether reviewer comments are still pending — that judgement stays with the RM.

READYPUBLIC — sync-driven via vulnogram-api-record-publish, fired when the advisory archive URL has been captured on lists.apache.org/list.html?<users-list> (the real-world signal the advisory has actually shipped). This transition was historically a manual RM click because it triggers the CNA-feed dispatch to cve.org; the post-2026 convention drives it from sync because the captured archive URL is the same signal a human would use to decide it is safe to flip.

PUBLISHED is sometimes used as a synonym for PUBLIC in older Vulnogram documentation; the current action is literally labelled PUBLIC in the UI.

Reviewer-comment signal

ASF CNA reviewers leave comments on REVIEW-state records. Those comments do not surface on the tracking issue directly — Vulnogram notifies by email to the project's security_list instead, with the CVE ID in the subject line. The security-issue-sync skill's Step 1e reads those emails (Gmail search recipe lives in ../gmail/search-queries.md) and surfaces each open reviewer comment in Step 2b as an actionable body-field proposal on the tracker.

The round trip is: reviewer leaves comment in Vulnogram → Vulnogram emails security_list → sync skill reads email → sync skill proposes a tracker body-field update → user confirms → generate-cve-json re-emits the JSON attachment on the next sync → release manager re-pastes the updated JSON into #sourcesync detects the comment is resolved on the next run. This indirection keeps the single source of truth on the tracking issue body (which the skills can read + write) rather than inside Vulnogram (which is OAuth-gated and not readable from skill context).

The cveprocess.apache.org/cve5/<CVE-ID>.json endpoint exists but is behind ASF OAuth and is not readable from agent-skill context — the security-issue-sync skill therefore never curls it; Gmail is the load-bearing signal source.

Record-generator round trip

The generate-cve-json tool under generate-cve-json/ reads a tracker issue and emits a paste-ready CVE JSON record in the exact shape Vulnogram's #source tab accepts — CNA container, CNA_private envelope, sorted references, deterministic byte output. The tool's behavioural contract lives in generate-cve-json/SKILL.md; the local-setup / test workflow lives in generate-cve-json/README.md.

Skills that update the tracker body call the tool with --attach so the embedded CVE JSON in the tracker body stays in lock-step with the body fields — every sync, every allocate-CVE wire-back, every dedupe merge regenerates the attachment. The release manager then has one canonical JSON to paste into #source at step 15.

Release-manager checklist

When the <upstream> release containing a fix ships, the security-issue-sync skill swaps the tracker's pr merged label to fix released, reassigns the issue to the release manager, and posts an explicit release-manager hand-off comment on the tracker (the template body lives in release-manager-handoff-comment.md). The numbered checklist below is the standalone authoritative recipe the comment links to — keep them in lock-step when one changes.

The flow has two writes in the common case (no reviewer comments) and three when reviewer comments arrive. Each write lands on the record via either the API (default — see Two record-write paths) or copy-paste (fallback). The instructions below show the API form inline; the copy-paste form is the "open #source, paste, Save" flow described in #source paste flow:

  1. (pre-handoff, sync-driven) — record reaches REVIEW state. The release manager picks up the tracker with the record already in REVIEW state. Sync pushes the generator-emitted JSON via vulnogram-api-record-update and the generator emits CNA_private.state = "REVIEW" when all the required body fields are populated. The hand-off comment the RM receives only fires once sync confirms the post-push state is REVIEW — until then the tracker stays with the remediation developer and a fill missing fields comment names what's blocking. The RM never performs the DRAFTREVIEW click.

  2. (conditional) Body-field round-trip after reviewer comments. ASF CNA reviewers may leave comments while the record sits in REVIEW (see Reviewer-comment signal above). The security-issue-sync skill detects them automatically and proposes matching body-field updates on the tracker; the security team confirms and the embedded JSON regenerates. Sync re-pushes via the same vulnogram-api-record-update call. As the RM, you wait for the security team's next sync — the round-trip is fully handled there; you only watch the #email tab for the reviewer thread to close. Skip this step if no reviewer comments arrived (the common case for well-formed records).

  3. Set READY. Vulnogram UI action — moves the record from REVIEW to READY and stages it for the advisory-send step. This is the first state-flip the RM performs; the generator cannot do it (it cannot tell from the tracker body alone whether reviewer comments are closed).

  4. Preview the advisory email on the #email tab. The preview renders the advisory exactly as Vulnogram will send it — same subject, same body, same recipient list. The preview is the load-bearing checkpoint for formatting issues (truncation, broken markdown, missing patch links) that the JSON view does not surface. Always preview before sending. If anything needs to change, edit the corresponding body field on the tracker, wait for the JSON to regenerate, re-paste in #source, and re-preview.

  5. Send the advisory emails. The Send these Emails button on Vulnogram's OSS/ASF Emails tab dispatches, in one action, to <users-list>, <announce-list>, and oss-security@lists.openwall.com — the oss-security copy (required per the ASF security-committers policy) is sent automatically alongside the ASF lists, so there is no separate manual send. On the tracker, add the announced - emails sent label and remove fix released.

  6. Wait for the publication-ready notification comment. The security-issue-sync skill scans the public users-list archive for the CVE ID on every run. Once it finds the archived advisory, it populates the tracker's Public advisory URL body field, regenerates the CVE JSON to carry the archive URL as a vendor-advisory reference, adds the announced label, and posts a publication-ready notification comment on the tracker (the template body lives in release-manager-publication-comment.md). That comment is the explicit go-ahead for steps 7-8.

  7. (sync-driven) — record reaches PUBLIC. Fires automatically once the publication-ready notification has landed. On its next pass, sync re-pushes the regenerated JSON (now carrying the archive URL as a vendor-advisory reference) and then moves the record READYPUBLIC via the dedicated vulnogram-api-record-publish CLI (which keys off the captured archive URL — the real-world signal that the advisory shipped). The RM does not perform the READYPUBLIC click.

  8. Close the tracker. Sync's apply step closes the tracker as completed (do not update labels) and archives the project-board item afterwards (per the archive-from-board recipe in ../github/project-board.md). The RM's last touchpoint is the wrap-up comment sync posts on the tracker — archive from the Announced column on the board and (conditionally) close the milestone if the just- closed tracker was the last open issue on it.

RM-side write count is zero in the common case (no reviewer comments): sync handles steps 1, 2, 6, 7, 8; the RM clicks REVIEWREADY (step 3), previews the advisory (step 4), and sends it (step 5). When reviewer comments arrive, the round-trip in step 2 is also handled by sync; the RM still performs only steps 3–5. The hand-off comment (template at release-manager-handoff-comment.md) walks the RM through their three actions without invoking any uv run commands; keep both in lock-step when one changes.