Logo

The OpenJS Foundation's CVE Numbering Authority (CNA)

API

Programmatic access to the OpenJS Foundation CNA for member projects. Asynchronous: dispatch an operation, get a correlation_id, poll for the result.

Prerequisites

You get one bucket token per environment per project. A test_nodejs token writes to the staging bucket and hits MITRE staging; a prod_nodejs token writes to the production bucket and hits MITRE production. The environment is baked into the token, so you cannot mix them up in a request.

Authentication

Pass your token as a bearer:

Authorization: Bearer prod_nodejs:a3f1b9...<256 hex chars total>

The server derives your bucket from the token. You cannot act on behalf of another bucket even if you put a different bucket name in the request body.

Endpoints

MethodPathPurpose
POST/dispatchStart an operation. Returns a correlation_id.
GET/runs/{correlation_id}Poll status and read the result location.
GET/healthUnauthenticated liveness check. Returns { status, timestamp }.

Operations

OperationInputsWhat it does
reserve-cve (none) Reserves a CVE ID at MITRE in your bucket's environment and records ownership of the ID in your bucket. Returns the assigned cve_id.
update-cve cve_id, cnaContainer (CVE v5.2) Replaces the CNA Container of an already-published CVE record at MITRE. The cve_id must belong to your bucket and be in PUBLISHED state. Returns MITRE's updated record.
publish-cve cve_id, cnaContainer (CVE v5.2) Atomically creates and publishes the CVE record at MITRE. The cve_id must belong to your bucket and not already be published. The record appears in the public Open Data feed on the next hourly refresh.
get-cve cve_id Returns the current MITRE record for a CVE. Authorised only if the cve_id belongs to your bucket.
list-cve (none) Returns the current MITRE record for every CVE in your bucket.

Your bucket only stores the list of CVE IDs you own. All record content (titles, descriptions, references, state) lives at MITRE; the API just authorises calls to MITRE on your behalf. To read the global list of OpenJS-published advisories without authentication, use the Open Data feed.

Usage

Two examples: a raw curl walkthrough and a Node.js client.

curl

1. Dispatch an operation. Returns a correlation_id immediately; the workflow keeps running in the background.

curl -X POST https://<your-cna-api>.workers.dev/dispatch \
  -H "Authorization: Bearer prod_nodejs:<your-token>" \
  -H "Content-Type: application/json" \
  -d '{ "operation": "reserve-cve", "inputs": {} }'

# -> HTTP 202 Accepted
# { "correlation_id": "5e3a8c14-...", "status": "queued" }

2. Poll for the result.

curl https://<your-cna-api>.workers.dev/runs/5e3a8c14-... \
  -H "Authorization: Bearer prod_nodejs:<your-token>"

# while running:
# { "correlation_id": "5e3a8c14-...", "run_id": 12345, "status": "in_progress", ... }

# when done:
# { "correlation_id": "5e3a8c14-...", "run_id": 12345, "status": "completed",
#   "conclusion": "success",
#   "result": { "cve_id": "CVE-2026-12345" } }

Typical end-to-end time is 30 to 60 seconds. Poll every few seconds until status is completed. On conclusion: "success", the operation's payload is inlined as result, with no need to scrape job logs yourself. The response also carries a url field pointing at the backing workflow run; that link is for OpenJS CNA coordinators (CNA users do not have access to the backing repository, so forward correlation_id and run_id instead if you need help).

Node.js

The canonical Node.js client is @openjsfoundation/cna-tools, a CLI plus library that wraps the dispatch/poll pattern, runs an offline reviewer against MITRE's v5.2 schema before publish, and converts GitHub Security Advisories into CNA Containers. Install it once and use it from your terminal or import it in your own automation:

pnpm add -g @openjsfoundation/cna-tools

openjs-cna config set -x openjs_cna_token    "prod_nodejs:<your-token>"
openjs-cna config set    openjs_cna_base_url "https://cna.openjsf.org"

CVE=$(openjs-cna reserve)
openjs-cna convert https://github.com/owner/repo/security/advisories/GHSA-xxxx-xxxx-xxxx --cve "$CVE" > container.json
openjs-cna review  container.json
openjs-cna publish "$CVE" -f container.json

For programmatic use:

import { CnaClient } from '@openjsfoundation/cna-tools/client'

const cna = new CnaClient({
  token: process.env.OPENJS_CNA_TOKEN,
  baseUrl: process.env.OPENJS_CNA_BASE_URL
})

const { cveId } = await cna.reserveCve()
const record    = await cna.getCve(cveId)
// ...later: cna.publishCve(cveId, container), cna.updateCve(cveId, container), cna.listCves(), etc.

The client runs on Node.js 22+, uses native fetch, and pulls in only ajv + ajv-formats for schema validation. See its README for the full command reference and error class catalogue (CnaAuthError, CnaTimeoutError, CnaOperationError, CnaReviewBlockedError).

FAQ

What is a bucket?

A bucket is the unit of authorization and storage in this API. Its name is <env>_<project> (e.g. prod_nodejs, test_multer) and the name decides three things at once:

A project typically has two buckets (one prod, one test) and each bucket can hold one or more tokens (see below).

How do I get a token?

Bucket tokens are issued by the OpenJS CNA coordinators. Reach out via security@openjsf.org or #security in openjs-foundation.slack.com. You'll typically get one token per individual on your team, per bucket.

What is the difference between prod_* and test_* buckets?

prod_* tokens hit MITRE production. CVEs reserved or published with them are real and appear on cve.org within ~15 minutes of publication. test_* tokens hit MITRE staging, a completely isolated sandbox where IDs and records are for integration testing only and never reach cve.org.

Can multiple people on my team have access? Should we share one token?

Yes to access, no to sharing. A bucket can hold many tokens, each labelled with the holder's identity (e.g. rafael, antoine, release-bot-1). The preferred model is one token per individual or per automation: the audit log then shows "Rafael reserved CVE-2026-X" rather than "some prod_nodejs token did", and the coordinators can rotate or revoke any one label independently of the others. Don't share a single token across a team; ask for an extra labelled one instead.

Why does dispatching take 30 to 60 seconds?

The API is asynchronous. A dispatch returns a correlation_id in ~1 second, but the workflow run behind it has to provision a GitHub Actions runner, install dependencies, call MITRE, and (for reserve-cve) commit. Poll /runs/{correlation_id} until status is completed.

Can I edit a CVE before publishing it?

No. MITRE has no draft state at the API level. The act of submitting a CNA Container is the act of publishing the record. Pre-publication drafting happens in your own coordination tooling (Vulnogram, HackerOne, GitHub Security Advisories, internal notes); use publish-cve only when you are ready for the record to be public.

I noticed a typo in my published advisory. Can I fix it?

Yes. update-cve is for that case: adding references, fixing typos, updating affected ranges after disclosure. The CVE must already be in PUBLISHED state at MITRE; the API refuses anything else with a clear error.

Can I see CVEs from other projects' buckets?

No. Each token authenticates to exactly one bucket. get-cve and list-cve only return records that your bucket owns. To read the global list of published OpenJS advisories, use the unauthenticated Open Data feed.

How long until my published CVE appears on cve.org?

About 15 minutes after publish-cve returns success. MITRE replicates to cve.org on an internal cadence. To verify directly without waiting for the cve.org UI or our cached feed, query MITRE's public read endpoint:

curl https://cveawg.mitre.org/api/cve/CVE-YYYY-NNNNN

That returns the full v5.2 record as soon as MITRE has it (no authentication required). Our own /security-advisories.json feed picks the new record up on the next hourly site rebuild.

What happens if the API call fails?

HTTP errors at the proxy (400/401/502) are returned immediately and explain the cause. If the underlying workflow run itself fails (e.g. MITRE rejects the payload), the poll returns status: "completed" with conclusion: "failure". Forward the correlation_id and run_id to your OpenJS CNA coordinator; that's everything they need to inspect the run on their side. Operators don't get direct access to the workflow logs by design.

How do I rotate my token?

Contact the OpenJS CNA coordinators. They issue a new labelled token, you cut over to it, then they revoke the old one. The previous token stays valid during the cutover window.

Can I use this for a CNA other than OpenJS?

The architecture is generic and the entire PoC is open-source at UlisesGascon/openjs-cna-api-poc. Fork it, deploy your own Cloudflare Worker, point at your CNA's MITRE credentials, and populate tokens.json with hashes of your own bucket tokens. See docs/architecture.md in that repository for the full design.

One important caveat: that repository is a static snapshot of the design captured in working code. It is not the official OpenJS CNA implementation; the OpenJS production deployment, when it exists, will live in a private OpenJS Foundation repository. The PoC may or may not see ongoing development after the original demo. Treat it as a complete, working starting point: read it to understand the architecture, fork it to bootstrap your own, but don't expect upstream maintenance.

Error responses

StatusMeaning
400Malformed request (missing operation, invalid JSON, etc.)
401Bearer missing, malformed, or unknown
404Unknown correlation_id (or run not yet visible). Retry once after a second.
502Upstream (GitHub) refused the dispatch or a completed+success run could not surface its result (run_result_unavailable); the body includes correlation_id and run_id for the coordinator.

Reading data, not writing it

This API is for writing CVE state (reserving IDs, updating drafts, publishing). To read the public list of OpenJS-published advisories, see the Open Data page. Reads do not require a token.