The OpenJS Foundation's CVE Numbering Authority (CNA)
Programmatic access to the OpenJS Foundation CNA for member projects. Asynchronous: dispatch an operation, get a correlation_id, poll for the result.
<bucket>:<secret> where the bucket is <env>_<project> (e.g. prod_nodejs or test_nodejs) and the secret is 256 random hex characters (1024 bits of entropy). Treat it like a password.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.
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.
| Method | Path | Purpose |
|---|---|---|
POST | /dispatch | Start an operation. Returns a correlation_id. |
GET | /runs/{correlation_id} | Poll status and read the result location. |
GET | /health | Unauthenticated liveness check. Returns { status, timestamp }. |
| Operation | Inputs | What 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.
Two examples: a raw curl walkthrough and a Node.js client.
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).
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).
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:
prod_* goes to MITRE production; test_* goes to MITRE staging.A project typically has two buckets (one prod, one test) and each bucket can hold one or more tokens (see below).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Status | Meaning |
|---|---|
400 | Malformed request (missing operation, invalid JSON, etc.) |
401 | Bearer missing, malformed, or unknown |
404 | Unknown correlation_id (or run not yet visible). Retry once after a second. |
502 | Upstream (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. |
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.