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
| Purpose | URL |
|---|---|
| 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.
| Path | When | One-liner | Setup |
|---|---|---|---|
| API (default) | Every paste step in the Release-manager checklist below | uv 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 deferred | Open #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#sourceand paste" fallback for users who want the visual confirmation.expired(exit 1) — proposevulnogram-api-setupfirst 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) — proposevulnogram-api-setupwith 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:
DRAFT↔REVIEW— sync-driven via the generator. Thegenerate-cve-jsontool emits the correct state in the JSON based on body-field readiness; sync pushes viavulnogram-api-record-update; Vulnogram accepts the state from the document body. No separate state-flip call.REVIEW→READY— 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.READY→PUBLIC— sync-driven via the dedicatedvulnogram-api-record-publishCLI, fired when the advisory archive URL has been captured. The CLI defaults to refusing the publish unless the current state isREVIEW(widen with--allow-statefor the rare cases where the RM has already moved toREADYmanually).
#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:
- Open the record's
#sourcetab athttps://cveprocess.apache.org/cve5/<CVE-ID>#source. - Copy the CVE JSON from the tracking issue's embedded attachment
(regenerated by the
generate-cve-jsontool on every sync — see the "Record-generator round trip" section below). - Paste into the
#sourceform and Save. - Use the UI action to move the record from
REVIEWtoPUBLIC(or fromDRAFTtoREVIEWfirst 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:
| State | Set by | What it means |
|---|---|---|
DRAFT | Allocation (initial state post-allocation) | Record exists, ID is reserved, but content is still being filled in. Not visible on cve.org. |
REVIEW | Sync, 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. |
READY | Release 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. |
PUBLIC | Sync, via vulnogram-api-record-publish, when the advisory archive URL is captured on the tracker | Record pushed to cve.org. World-readable. The generic tracking-issue lifecycle terminates at the close action once this state has been reached. |
DRAFT → REVIEW — 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 DRAFT →
REVIEW 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).
REVIEW → READY — 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.
READY → PUBLIC — 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 #source → sync 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:
-
(pre-handoff, sync-driven) — record reaches
REVIEWstate. The release manager picks up the tracker with the record already inREVIEWstate. Sync pushes the generator-emitted JSON viavulnogram-api-record-updateand the generator emitsCNA_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 isREVIEW— until then the tracker stays with the remediation developer and a fill missing fields comment names what's blocking. The RM never performs theDRAFT→REVIEWclick. -
(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). Thesecurity-issue-syncskill 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 samevulnogram-api-record-updatecall. As the RM, you wait for the security team's next sync — the round-trip is fully handled there; you only watch the#emailtab for the reviewer thread to close. Skip this step if no reviewer comments arrived (the common case for well-formed records). -
Set
READY. Vulnogram UI action — moves the record fromREVIEWtoREADYand 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). -
Preview the advisory email on the
#emailtab. 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. -
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>, andoss-security@lists.openwall.com— theoss-securitycopy (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 theannounced - emails sentlabel and removefix released. -
Wait for the publication-ready notification comment. The
security-issue-syncskill 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 avendor-advisoryreference, adds theannouncedlabel, and posts a publication-ready notification comment on the tracker (the template body lives inrelease-manager-publication-comment.md). That comment is the explicit go-ahead for steps 7-8. -
(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 avendor-advisoryreference) and then moves the recordREADY→PUBLICvia the dedicatedvulnogram-api-record-publishCLI (which keys off the captured archive URL — the real-world signal that the advisory shipped). The RM does not perform theREADY→PUBLICclick. -
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 theAnnouncedcolumn 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 REVIEW → READY (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.