Table of Contents generated with DocToc
tools/cve-tool/
tools/cve-tool/
Capability: contract:cve-authority
Kind: interface
Vendor: agnostic
Prerequisites
- Runtime: None — this directory is a Markdown contract spec; no executable code ships here. It is read by the framework, not run.
- CLIs / credentials / network: Provided entirely by the resolved adapter — the sibling
tools/cve-tool-<name>/directory named bycve_authority.toolinproject.md(the ASF default is Vulnogram attools/cve-tool-vulnogram/). See that adapter for its concrete prerequisites.
What this is
The framework’s CVE-tooling adapter contract. Today every CVE-aware
skill in this repository — security-cve-allocate,
security-issue-sync, security-issue-invalidate,
security-issue-deduplicate — speaks Vulnogram. They embed
Vulnogram’s URL prefix (cveprocess.apache.org), Vulnogram’s
#source / #json / #email tab shape, and Vulnogram’s DRAFT →
REVIEW to READY to PUBLIC state machine directly in the
skill prose. That is fine for the ASF — the foundation runs a single
CNA tool and the skills can afford to be Vulnogram-shaped — but it
forecloses every other plausible adopter. A project running CVE.org’s
direct submission portal, the MITRE form for non-CNAs, GHSA’s own
CNA flow, or no CVE allocation at all has no seam to plug into.
This contract is that seam. It defines the methods every CVE-tool
backend must implement, the lifecycle moments at which the skills
fire each method, and the generic state verbs the skills use to
talk about a CVE record without baking in any one tool’s vocabulary.
Skills consume the contract; the adapter consumes the wire protocol
of whichever CNA tool the adopter actually uses.
The contract is read by the framework, not by humans during normal
operation. New adopters declare cve_authority.tool: <adapter> in
projects/<project>/project.md; the skills resolve that to a
sibling directory under tools/ and call the methods named here.
Adopters who run Vulnogram inherit the ASF defaults verbatim.
Today’s adapters
Only one CVE-tool adapter ships today:
| Adapter | Directory | Status | Notes |
|---|---|---|---|
| Vulnogram (ASF) | tools/cve-tool-vulnogram/ |
Shipping | The reference implementation. PR4 of the ASF-pluggable security flow renames the directory to tools/cve-tool-vulnogram/ to match the contract’s adapter-naming rule. Not renamed in this PR. |
| CVE.org direct submission | tools/cve-tool-cve-org-direct/ (planned) |
Placeholder | For adopters who are themselves a CNA and submit records straight to cve.org via the CVE Services API rather than through an intermediate CNA tool. |
| MITRE form | tools/cve-tool-mitre-form/ (planned) |
Placeholder | For adopters who are not a CNA and request CVE IDs via the MITRE CVE Request form. Allocation is asynchronous and operator-mediated; fetch_current_state will frequently return unknown until the form’s email reply arrives. |
| GHSA-as-CNA | tools/cve-tool-ghsa/ (planned) |
Placeholder | For adopters who already drive their advisory flow through GitHub Security Advisories (GHSA) and use GitHub as the CNA. allocate becomes a GHSA draft create; publish becomes a GHSA publish. |
| None | tools/cve-tool-none/ (planned) |
Placeholder | The disable-CVE-allocation backend. All methods are no-ops returning a synthetic not-applicable result. For adopters who triage security issues but do not allocate CVEs (e.g. an internal product with no public distribution). |
The four placeholder adapters do not exist on disk yet; they exist
in this table so the contract can describe the surface they will fit
into when an adopter actually needs one. PR4 of the ASF-pluggable
security flow will create tools/cve-tool-vulnogram/ (the rename
target) but will not pre-create the other three — those land when an
adopter implements them.
Interface
Every CVE-tool adapter exposes five methods. The names here are the generic verbs the skills use; an adapter is free to name its internal CLIs whatever fits its tool’s vocabulary, as long as the skill-facing surface uses these names.
allocate(scope, fields) to cve_id
Reserve a CVE ID and create the initial record.
- Lifecycle moment. Fires once per tracking issue, inside
security-cve-allocateafter the security team has agreed the report is valid and a CVE should be allocated. The pre-allocation consensus discussion happens on the tracker;allocateis the step that turns that consensus into a reserved ID. - Inputs.
scope— a string identifying the project scope under which the CVE belongs. Concretely for ASF: the scope label (affects: airflow/affects: providers/affects: chart) drives whichvendor/product/packageNametriad the adapter writes into the initial record. For other adopters this is whatever string their CVE tool needs to route the allocation to the right CNA pool.fields— a dict of the tracker’s body fields at the moment allocation fires:title,description,affected_versions,cwe,severity,credits,references. The adapter is free to ignore fields its tool cannot accept at allocation time and fill them in later viapush_update. The Vulnogram adapter accepts the full set at allocation; the MITRE-form adapter only sendstitle+affected_versions+ a short description in the initial form submission.
- Output. The allocated CVE ID as a string in canonical
CVE-YYYY-NNNN+form (CVE-2026-12345). If the adapter cannot allocate synchronously (the MITRE-form case), it returns a provisional placeholder string and the skill stores that on the tracker ascve allocation pending; the nextfetch_current_statecall upgrades it once the real ID arrives. - No-op case. The
noneadapter raisesNotApplicableand the skill skips allocation entirely. Thesecurity-cve-allocateskill detects this and posts a “this adopter does not allocate CVEs; closing without an ID” comment on the tracker instead.
fetch_current_state(cve_id) to {state, fields}
Read the current record state and the fields the tool stores.
- Lifecycle moment. Fires inside
security-issue-syncat Step 1e (the reviewer-comment sync) and Step 5b (the state-progression gate before the release-manager hand-off). Also fires fromsecurity-issue-invalidate’s Step 0 gate, to refuse closure when a record exists and is pastreview-ready. - Inputs. The CVE ID. Nothing else — the adapter is responsible for resolving the tool-specific URL or API call from the ID.
- Output. A two-field dict:
state— one of the generic state verbs defined below (allocated,review-ready,publish-ready,public,retracted,unknown). The adapter is responsible for mapping its tool’s native state vocabulary onto these verbs.fields— a dict of the fields the tool currently stores for the record. Shape matches theallocateinput dict; the skills diff this against the tracker body to detect drift.
- No-op case. The
noneadapter returns{state: "unknown", fields: {}}. Skills treatunknownas “do not gate progression on tool state”; they fall back to the tracker body’s view.
push_update(cve_id, fields, state_transition=None) to diff
Write field updates to the record, optionally with a state move.
- Lifecycle moment. Fires inside
security-issue-syncat Step 5c, whenever the tracker body has drifted from whatfetch_current_statereturned. Also fires fromsecurity-issue-deduplicatewhen the kept tracker absorbs credits or references from the merged-in tracker. - Inputs.
cve_id— as forfetch_current_state.fields— the new desired fields. The adapter is responsible for translating these into whatever wire format its tool accepts (JSON record for Vulnogram, web-form fields for the MITRE form, GraphQL mutation for GHSA).state_transition(optional) — one of the generic state verbs. When supplied, the adapter performs the state move as part of the same write; when omitted, the adapter only writes fields and leaves state untouched. The Vulnogram adapter today embeds the state inside the JSON body and so does both in one PUT; other adapters may need a separate API call after the field write. The contract does not constrain how — only that the call appear atomic from the skill’s point of view.
- Output. A diff dict —
{added: [...], removed: [...], changed: [...]}— that the skill can show the user as part of the sync confirmation. Empty diff means the record was already up to date; the skill suppresses the confirmation prompt in that case. - No-op case. The
noneadapter returns{added: [], removed: [], changed: []}and writes nothing. Theunknown-state return fromfetch_current_statealso causes the skill to skippush_update— the adapter has nowhere to push to.
publish(cve_id) to ok
Move the record to its terminal public state.
- Lifecycle moment. Fires inside
security-issue-syncat the publication step, after the public advisory archive URL has been captured on the tracker. The captured archive URL is the real-world signal that the advisory has actually shipped to the users-list; before that point the adapter would be publishing a record before the world has heard about the issue, which is the opposite of the desired ordering. - Inputs. The CVE ID.
- Output.
ok(a sentinel; the skill does not inspect the return body — failure raises). The skill follows up withfetch_current_stateto confirm the state landed atpublicand posts a confirmation comment on the tracker. - No-op case. The
noneadapter raisesNotApplicable. The skill treats this as “this adopter does not publish CVEs; the tracker close is the only terminal action” and proceeds to the archive-from-board step directly. Theghsaadapter implements this as a GraphQLpublishSecurityAdvisorymutation.
retract(cve_id, reason) to ok
Mark an allocated-but-not-public record as rejected.
- Lifecycle moment. Fires inside
security-issue-invalidatefor trackers that already carry a CVE ID. CVE retraction is governance-sensitive; the skill requires explicit confirmation from a release-vote-gating role (governance.cve_allocation_gatefromproject.md) before invokingretract. The retracted state is terminal — once a record has been retracted, no subsequentpush_updateorpublishis valid. - Inputs.
cve_id— as above.reason— a short string captured from the tracker discussion explaining the retraction. The adapter is responsible for writing this into the appropriate field of its tool’s record (Vulnogram’sCNA_private.justification, CVE.org’srejectedReason, etc.).
- Output.
oksentinel as forpublish. - No-op case. The
noneadapter raisesNotApplicable. The skill falls back to “close the tracker as invalid; there is no CVE record to retract.” For adapters whose tool does not support retraction ofpublicrecords (Vulnogram refuses this; CVE.org has a separate REJECT flow), the adapter must raise a distinguishableAlreadyPublicerror and the skill escalates to the configured governance contact.
Generic state verbs
The skills speak in generic verbs about a CVE record’s lifecycle. The adapter is responsible for mapping its tool’s native states onto these verbs. The verbs are:
| Verb | Meaning | Vulnogram-native state |
|---|---|---|
allocated |
Record exists, ID is reserved, content is being filled in. Not visible publicly. | DRAFT |
review-ready |
Record content is complete and ready for CNA review. Reviewer comments may arrive at this state. Not visible publicly. | REVIEW |
publish-ready |
Record content is final, reviewer comments addressed, staged for the advisory-send step. The advisory emails are dispatched from the CVE tool while in this state. Not visible publicly. | READY |
public |
Record pushed to cve.org and world-readable. Terminal in the success path. |
PUBLIC |
retracted |
Record marked rejected post-allocation. Terminal in the failure path. | REJECTED (in CNA_private.state) |
unknown |
The adapter cannot determine the state (network failure, asynchronous tool that hasn’t replied yet, none backend). Skills treat this as “fall back to the tracker body’s view.” |
n/a |
The map is adapter-internal. Skills never write DRAFT or
REVIEW — they write allocated and review-ready. An adapter
that needs a more granular internal model is free to introduce
sub-states inside its tool/ directory, as long as the
contract-facing methods normalise on the verbs above.
The DRAFT to REVIEW transition is sync-driven (the tracker body
fields determine readiness) in the Vulnogram adapter; the
REVIEW to READY transition is release-manager-driven because the
adapter cannot tell from the tracker body alone whether reviewer
comments are still pending. Other adapters may collapse these two
transitions into one (the GHSA adapter has no separate
review/publish-ready distinction) or split them further (a
hypothetical multi-stage CNA tool with REVIEW to ADDRESSING →
READY). The contract does not constrain how the adapter maps
its internal states onto the four generic verbs — only that the
verbs are what the skills see.
Skills that consume this contract
The CVE-tool contract is consumed by four skills today:
security-cve-allocate— callsallocateto reserve the CVE ID and create the initial record, then writes the ID back onto the tracker’s CVE tool link body field. The skill is PMC-gated (orgovernance.cve_allocation_gate-gated for non-ASF adopters); the adapter does not enforce that gate itself — the skill does.security-issue-sync— callsfetch_current_stateat Step 1e (to surface reviewer-comment signals) and at Step 5b (to gate the release-manager hand-off on the post-push state beingreview-ready). Callspush_updateat Step 5c whenever the tracker body has drifted from the tool’s view. Callspublishat the publication step after the public advisory archive URL has been captured.security-issue-invalidate— usesfetch_current_stateat its Step 0 gate to refuse closure when a CVE record exists and is pastreview-ready. Callsretractwhen the tracker carries a CVE ID and the consensus decision is “invalid after allocation” (escalates first viagovernance.escalation_contact, since CVE retraction has public consequences).security-issue-deduplicate— callspush_updateon the kept tracker’s CVE record after merging in credits and references from the duplicate tracker. The duplicate tracker’s CVE record (if any) is the subject of a separateretractcall once the dedup decision has been confirmed.
These four skills are the only consumers in the current shape of
the framework. Future skills that need to touch a CVE record
(e.g. a hypothetical security-cve-rotate-credit action) will
extend the contract rather than reaching past it into a specific
adapter.
ASF default — Vulnogram
The ASF reference adapter lives at tools/cve-tool-vulnogram/.
It is the only adapter shipping today, and the only adapter the
skills are tested against. Key properties of the ASF default:
- URL prefix:
https://cveprocess.apache.org/cve5/<CVE-ID>. The record page,#sourcetab,#jsontab, and#emailtab are all rooted there. Seetools/cve-tool-vulnogram/record.mdfor the canonical URL table. - Email preview URL:
https://cveprocess.apache.org/cve5/<CVE-ID>#email. Renders the advisory exactly as Vulnogram will dispatch it to the users-list and announce-list — same subject, same body, same recipient list. The release-manager checklist calls this out as a load-bearing checkpoint before the advisory-send step. - Source tab URL:
https://cveprocess.apache.org/cve5/<CVE-ID>#source. The copy-paste fallback target for the JSON record. The default write path is the OAuth-authenticated API (vulnogram-api-record-update); copy-paste is the documented fallback when the OAuth session has expired or the operator has opted out. - State machine:
DRAFTtoREVIEWtoREADYtoPUBLIC, carried insideCNA_private.stateon the CVE 5.x record. The adapter maps these onto the generic verbs as shown in the state-verb table above. - Reviewer-comment channel: mailing-list (the ASF CNA
reviewers email the project’s
security_listrather than surfacing comments on the tracking issue directly). This is thecve_authority.reviewer_channel: mailing-listsetting inproject.md; an adapter could equally well declarereviewer_channel: github-pr(for a CNA tool that uses pull requests as its review surface) orreviewer_channel: none(for a CNA tool with no separate review step). - Publication propagation: poll (the skills check the public
advisory archive on every sync rather than waiting for a
webhook). This is the
cve_authority.publication_propagation: pollsetting inproject.md.
Rename pending. PR4 of the ASF-pluggable security flow renames
tools/cve-tool-vulnogram/ to tools/cve-tool-vulnogram/ to match the
contract’s adapter-naming rule. This PR does not perform the
rename — the existing skill prose and the cve_authority.tool: vulnogram setting in project.md continue to point at
tools/cve-tool-vulnogram/ until PR4 lands.
Configuration
Every adopter declares its CVE-tool choice in
projects/<project>/project.md under the cve_authority block.
The shape is:
cve_authority:
tool: vulnogram # vulnogram | cve-org-direct | mitre-form | ghsa | none
allocate_url: "https://cveprocess.apache.org/cve5/new"
record_url_template: "https://cveprocess.apache.org/cve5/{cve_id}"
source_tab_url_template: "https://cveprocess.apache.org/cve5/{cve_id}#source"
email_preview_url_template: "https://cveprocess.apache.org/cve5/{cve_id}#email"
states: [allocated, review-ready, publish-ready, public]
publication_propagation: poll # poll | webhook | manual
emits_allocation_email: true
reviewer_channel: mailing-list # mailing-list | github-pr | none
Field-by-field:
tool— names the adapter directory the skills resolve to. The ASF default isvulnogram(resolves totools/cve-tool-vulnogram/; becomestools/cve-tool-vulnogram/after PR4). Adopters using a different CVE-tool backend pick one of the other four enumerated values, each of which is expected to resolve to a siblingtools/cve-tool-<name>/directory.allocate_url— the URL the human operator opens to begin the allocation flow (for tool-mediated allocation paths) or the API endpoint the adapter POSTs to (for fully automated paths). The Vulnogram default is the/cve5/newform; the MITRE-form default would behttps://cveform.mitre.org/.record_url_template— the per-record URL pattern. The{cve_id}placeholder is the only token the skills substitute. Skills use this for the “open the CVE record” links they post on the tracker.source_tab_url_template— the copy-paste fallback target. Optional for adapters that have no copy-paste fallback (e.g. GHSA — there is no JSON form to paste into). When the field is null, the skills suppress the copy-paste fallback proposal.email_preview_url_template— the advisory-email preview URL. Optional for adapters whose CVE tool does not dispatch the advisory itself; when null, the release-manager checklist omits the preview step. The ASF default points at the#emailtab.states— the ordered list of generic state verbs the adapter exposes. Adapters with fewer states (the GHSA adapter collapsesreview-readyandpublish-readyinto a single pre-publish state; thenoneadapter has onlyallocatedandunknown) declare a shorter list. Skills branch on the declared list when deciding which lifecycle steps apply.publication_propagation— how the skills learn that a record has reachedpublic.poll(the ASF default) means the skills check the public archive on every sync;webhookmeans an external hook updates the tracker directly;manualmeans a human flips the tracker label and the skills trust it.emits_allocation_email— whether the CVE tool sends an allocation-confirmation email atallocatetime. The ASF default istrue(Vulnogram emails the project’ssecurity_liston every allocation); the MITRE-form default is alsotrue(the form replies with the allocated ID); GHSA’s default isfalse(no email — the allocation result is the API response). Skills that wait on this email surface a “do not close this tracker until the allocation email lands” hint when the flag istrue.reviewer_channel— where reviewer comments arrive.mailing-list(ASF default) meanssecurity-issue-syncreads reviewer comments off the security mailing list;github-prmeans it reads them off a backing PR;nonemeans there is no separate review step.
The contract does not constrain how the adapter implements any of
these settings — only that the settings are present and that the
adapter respects them. Adapters are free to add their own
tool-specific sub-keys under cve_authority.<adapter-name>: (e.g.
cve_authority.vulnogram.asf_org_id, cve_authority.vulnogram.cna_private_owner)
for fields the contract does not surface.