Table of Contents generated with DocToc

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 by cve_authority.tool in project.md (the ASF default is Vulnogram at tools/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 DRAFTREVIEW 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-allocate after the security team has agreed the report is valid and a CVE should be allocated. The pre-allocation consensus discussion happens on the tracker; allocate is 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 which vendor / product / packageName triad 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 via push_update. The Vulnogram adapter accepts the full set at allocation; the MITRE-form adapter only sends title + 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 as cve allocation pending; the next fetch_current_state call upgrades it once the real ID arrives.
  • No-op case. The none adapter raises NotApplicable and the skill skips allocation entirely. The security-cve-allocate skill 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-sync at Step 1e (the reviewer-comment sync) and Step 5b (the state-progression gate before the release-manager hand-off). Also fires from security-issue-invalidate’s Step 0 gate, to refuse closure when a record exists and is past review-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 the allocate input dict; the skills diff this against the tracker body to detect drift.
  • No-op case. The none adapter returns {state: "unknown", fields: {}}. Skills treat unknown as “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-sync at Step 5c, whenever the tracker body has drifted from what fetch_current_state returned. Also fires from security-issue-deduplicate when the kept tracker absorbs credits or references from the merged-in tracker.
  • Inputs.
    • cve_id — as for fetch_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 none adapter returns {added: [], removed: [], changed: []} and writes nothing. The unknown-state return from fetch_current_state also causes the skill to skip push_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-sync at 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 with fetch_current_state to confirm the state landed at public and posts a confirmation comment on the tracker.
  • No-op case. The none adapter raises NotApplicable. 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. The ghsa adapter implements this as a GraphQL publishSecurityAdvisory mutation.

retract(cve_id, reason) to ok

Mark an allocated-but-not-public record as rejected.

  • Lifecycle moment. Fires inside security-issue-invalidate for 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_gate from project.md) before invoking retract. The retracted state is terminal — once a record has been retracted, no subsequent push_update or publish is 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’s CNA_private.justification, CVE.org’s rejectedReason, etc.).
  • Output. ok sentinel as for publish.
  • No-op case. The none adapter raises NotApplicable. 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 of public records (Vulnogram refuses this; CVE.org has a separate REJECT flow), the adapter must raise a distinguishable AlreadyPublic error 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 ADDRESSINGREADY). 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 — calls allocate to 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 (or governance.cve_allocation_gate-gated for non-ASF adopters); the adapter does not enforce that gate itself — the skill does.
  • security-issue-sync — calls fetch_current_state at Step 1e (to surface reviewer-comment signals) and at Step 5b (to gate the release-manager hand-off on the post-push state being review-ready). Calls push_update at Step 5c whenever the tracker body has drifted from the tool’s view. Calls publish at the publication step after the public advisory archive URL has been captured.
  • security-issue-invalidate — uses fetch_current_state at its Step 0 gate to refuse closure when a CVE record exists and is past review-ready. Calls retract when the tracker carries a CVE ID and the consensus decision is “invalid after allocation” (escalates first via governance.escalation_contact, since CVE retraction has public consequences).
  • security-issue-deduplicate — calls push_update on 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 separate retract call 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, #source tab, #json tab, and #email tab are all rooted there. See tools/cve-tool-vulnogram/record.md for 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: DRAFT to REVIEW to READY to PUBLIC, carried inside CNA_private.state on 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_list rather than surfacing comments on the tracking issue directly). This is the cve_authority.reviewer_channel: mailing-list setting in project.md; an adapter could equally well declare reviewer_channel: github-pr (for a CNA tool that uses pull requests as its review surface) or reviewer_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: poll setting in project.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 is vulnogram (resolves to tools/cve-tool-vulnogram/; becomes tools/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 sibling tools/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/new form; the MITRE-form default would be https://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 #email tab.
  • states — the ordered list of generic state verbs the adapter exposes. Adapters with fewer states (the GHSA adapter collapses review-ready and publish-ready into a single pre-publish state; the none adapter has only allocated and unknown) 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 reached public. poll (the ASF default) means the skills check the public archive on every sync; webhook means an external hook updates the tracker directly; manual means a human flips the tracker label and the skills trust it.
  • emits_allocation_email — whether the CVE tool sends an allocation-confirmation email at allocate time. The ASF default is true (Vulnogram emails the project’s security_list on every allocation); the MITRE-form default is also true (the form replies with the allocated ID); GHSA’s default is false (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 is true.
  • reviewer_channel — where reviewer comments arrive. mailing-list (ASF default) means security-issue-sync reads reviewer comments off the security mailing list; github-pr means it reads them off a backing PR; none means 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.

Suggest a change