cna is a workflow CLI for maintaining CPANSec CNA CVE records.
Run directly from GitHub using flakes:
nix run github:CPAN-Security/cna-tool#cna -- helpAgainst a separate data repo:
nix run github:CPAN-Security/cna-tool#cna -- \
--cpansec-cna-root /path/to/cpansec-cna-cve check CVE-2025-40906Or with environment variable:
CPANSEC_CNA_ROOT=/path/to/cpansec-cna-cve \
nix run github:CPAN-Security/cna-tool#cna -- check CVE-2025-40906The flake app wires the CVE JSON schema automatically; a local cve-schema
submodule checkout is not required for nix run.
Install into your user profile (common usage pattern):
nix profile install github:CPAN-Security/cna-tool#cna
cna --helpUpgrade later:
nix profile upgrade cnaIt helps you:
- initialize CVE YAML records
- validate/lint authoring quality
- generate CVE 5.x JSON
- render announcement text
- import JSON back to YAML with round-trip guard
- reconcile local CNA data with cve.org records
This repository also contains CVE data (cves/, reserved/, announce/, encrypted/).
- Perl
v5.42 - Project dependencies installed in your environment
- Git repository checkout (branch-aware behavior is built in)
For a local checkout, install dependencies from cpanfile with cpm:
cpm install --with-test --show-build-log-on-failureInitialize the cve-schema submodule (needed for local scripts/cna schema validation):
git submodule update --init --recursive cve-schemaThen run tests:
prove -lr tCanonical entrypoint:
scripts/cna <command> [options]Global option:
--cpansec-cna-root PATHIf set, cna runs as if started from that data repo root.
You can also use:
CPANSEC_CNA_ROOT=/path/to/data-repoCreate reserved/CVE-YYYY-NNNN on main.
scripts/cna init CVE-2026-12345 Some::ModuleSensitive CVEs:
scripts/cna init --encrypted CVE-2026-12345 Some::Moduleinit behavior:
- checks
reserved/<CVE>unless--force - suggests branch
CVE-YYYY-NNNN--module-slug - branch switch prompt is only offered on clean
main - on dirty
main, it asks whether to continue without switching - can prefill metadata from MetaCPAN (interactive)
- always writes
repo:in stub (MetaCPAN value or placeholder)
scripts/cna check CVE-2026-12345If cves/<CVE>.json exists, check also verifies it matches the current YAML
projection and warns with json_out_of_date when stale.
Changed files only:
scripts/cna check --changed --format githubStrict mode (lint warnings become blocking):
scripts/cna check CVE-2026-12345 --strictWrite JSON next to YAML:
scripts/cna build CVE-2026-12345Emit to stdout only:
scripts/cna emit CVE-2026-12345
scripts/cna emit CVE-2026-12345 --cna-container-onlyStdout:
scripts/cna announce CVE-2026-12345Write to default file:
scripts/cna announce CVE-2026-12345 --writeWrite to chosen path:
scripts/cna announce CVE-2026-12345 --output /tmp/CVE-2026-12345.txtscripts/cna import CVE-2026-12345
# or
scripts/cna import /path/to/CVE-2026-12345.jsonDisable round-trip guard (not recommended):
scripts/cna import /path/to/CVE-2026-12345.json --no-guardscripts/cna reconcile CVE-2026-12345Notes:
reconcileonly uses local sources undercves/.- It does not read or operate on
encrypted/.
With custom API base:
scripts/cna reconcile CVE-2026-12345 --api-base https://cveawg.mitre.org/api/cveVerbose:
scripts/cna reconcile --verboseinit generates a stub with required fields and commented optionals.
Only .yaml is supported for CVE YAML files (.yml is ignored).
Optional sections are shown as comments (not pre-populated), including:
cwesimpacts(CAPEC)solutionmitigationfilesroutinestimelinecredits
title and description can include {{VERSION_RANGE}}.
Example:
title: Some::Module {{VERSION_RANGE}} for Perl has an issue
description: |
Some::Module {{VERSION_RANGE}} for Perl has an issue.
More details.{{VERSION_RANGE}} is derived from affected:
"<= 1.0"->versions through 1.0"1.2 <= 1.3"->versions from 1.2 through 1.3"1.5 < *"or"1.5 <= *"->versions from 1.5"1.5"->versions 1.5
Multiple ranges are joined with commas, e.g.:
affected:
- "<= 1.0"
- "1.2 <= 1.3"
- "1.5 < *"becomes:
versions through 1.0, from 1.2 through 1.3, from 1.5
If template syntax is malformed or an unsupported token is used, conversion warns. Unsupported tokens are left unchanged in output text.
timeline[].time accepts:
YYYY-MM-DD- full timestamp (
YYYY-MM-DDTHH:MM:SSZor offset form)
Date-only values are normalized in CVE output to midnight UTC (T00:00:00Z).
- Schema/validation errors are blocking.
- Lint findings are advisory by default.
--strictmakes lint findings blocking.
There is an additional wording lint to keep title/description lead text aligned with announce-style version phrasing.
For embargoed records:
- initialize with
--encrypted - data lives under
encrypted/ - network access is blocked in encrypted context
- encrypted operations are refused on
main - writes to
encrypted/are guarded by git-crypt checks announcerefuses encrypted sourcesreconcileignoresencrypted/and only works fromcves/
For commands that accept optional CVE (check/build/emit/announce/reconcile single target), resolution order is:
- explicit CLI CVE
CPANSEC_CNA_CVE- branch name prefix (
CVE-...)
scripts/cna init [--force] [--encrypted] <CVE> <Module>
scripts/cna check [CVE] [--changed] [--format text|github] [--strict]
scripts/cna build [CVE] [--strict] [--force]
scripts/cna emit [CVE] [--strict] [--cna-container-only]
scripts/cna announce [CVE] [--write|--output PATH] [--force]
scripts/cna import <CVE|PATH.json> [--force] [--no-guard]
scripts/cna reconcile [CVE] [--api-base URL] [--verbose]