Skip to main content

prototyyppi

Experimental Python reference implementation of CSAF 2.1. Validates CSAF advisory documents against the full three-tier test suite (mandatory, recommended, informative) specified in the CSAF 2.1 standard.

Requires Python 3.11 or later.

Install

pip install prototyyppi

The package name on PyPI is prototyyppi (Finnish: "prototype"). The final stable release will be published as csaf.

Manual

The man page provides the full CLI reference. After installation, place it on your MANPATH:

mkdir -p ~/.local/share/man/man1
cp docs/man/prototyyppi.1 ~/.local/share/man/man1/
man prototyyppi

Quickstart

CLI

Validate a CSAF 2.1 advisory and get a human-readable report:

prototyyppi validate advisory.json
File: advisory.json
Overall: FAIL

  [PASS] schema — JSON Schema (CSAF 2.1)
  [FAIL] 6.1.1 — Missing Definition of Product ID
          /product_tree/product_groups/0/product_ids/0: product id `CSAFPID-9080700` is not defined in product_tree
  [PASS] 6.1.2 — Multiple Definition of Product ID
  …

Exit code is 0 for a valid document, 1 for invalid, 2 for usage errors.

Validation levels — run additional test tiers:

# Basic: schema + mandatory (6.1.x) — default
prototyyppi validate advisory.json

# Extended: + recommended (6.2.x)
prototyyppi validate --level extended advisory.json

# Full: + informative (6.3.x)
prototyyppi validate --level full advisory.json

Output formats:

# TC-compatible JSON (matches the OASIS test-result schema)
prototyyppi validate --format json advisory.json

# SARIF 2.2 (GitHub code scanning, VS Code SARIF viewer)
prototyyppi validate --format sarif advisory.json > results.sarif

# GitHub-flavored markdown — paste directly into a GitHub issue or PR comment
prototyyppi validate --format markdown advisory.json

Batch validation:

# All files in a directory
prototyyppi validate advisories/*.json

# Recursive glob (quote to let Python expand it — avoids shell ARG_MAX limits)
prototyyppi validate 'advisories/**/*.json'

# Batch markdown for a GitHub issue
prototyyppi validate --format markdown 'advisories/*.json'

Skip rules — suppress specific tests by ID or from a YAML file:

prototyyppi validate --skip-rules 6.1.9 advisory.json
prototyyppi validate --skip-rules skip.yaml advisory.json

Suppress output:

prototyyppi validate --quiet advisory.json    # hide passing rules
prototyyppi validate --silent advisory.json   # exit code only

Rule catalog:

prototyyppi info rules                         # full list
prototyyppi info rules --only-groups mandatory # filter by tier
prototyyppi info rules --format json           # machine-readable
Spec   ID         Group           Status           Title
--------------------------------------------------------------------------------
2.1    6.1.1      mandatory       implemented      Missing Definition of Product ID
2.1    6.1.2      mandatory       implemented      Multiple Definition of Product ID
…
61 rule(s) in catalog (CSAF 2.1).

Environment info:

prototyyppi info env                    # CSAF support, catalogs, interpreter, platform
prototyyppi info env --level extended   # + runtime config, paths, host OS identity
prototyyppi info env --level full       # + interpreter detail, flags, CPU, resource usage
prototyyppi info env --format json      # machine-readable

For a condensed single-page reference see the Quickstart guide. For tutorials covering validation and document production see the Tutorial index.

Python API — consumer (validate)

from prototyyppi import validate, validate_file

# From a file path
report = validate_file("advisory.json")

# From an already-parsed dict
import msgspec
doc = msgspec.json.decode(open("advisory.json", "rb").read())
report = validate(doc, path="advisory.json")

print("valid:", report.overall_valid)
for result in report.results:
    if not result.passed:
        print(f"[FAIL] {result.id}{result.title}")
        for err in result.errors:
            print(f"       {err.instance_path}: {err.message}")

The ValidationReport is a frozen msgspec.Struct. Pass it to the formatters to render as text, JSON, SARIF, or markdown:

from prototyyppi import to_text, to_tc_json, to_sarif, to_markdown

print(to_text(report))
print(to_tc_json(report))
print(to_sarif(report, version='0.0.0'))
print(to_markdown(report))

Python API — producer (build)

Construct a typed document and call build() to get validated JSON:

from prototyyppi import (
    Document, Distribution, FullProductName, Note, Publisher, ProductStatus,
    ProductTree, Revision, Tlp, Tracking, Vulnerability,
    Metric, MetricContent, cvss31_from_vector, cwe_entry,
)

doc = Document(
    category='csaf_security_advisory',
    title='Acme Corp — Advisory 2026-001',
    publisher=Publisher(category='vendor', name='Acme Corp',
                        namespace='https://acme.example.com'),
    tracking=Tracking(
        id='ACME-2026-SA-001', status='final', version='1',
        initial_release_date='2026-01-15T10:00:00Z',
        current_release_date='2026-01-15T10:00:00Z',
        revision_history=[Revision(date='2026-01-15T10:00:00Z', number='1',
                                   summary='Initial release.')],
    ),
    distribution=Distribution(tlp=Tlp(label='CLEAR')),
    product_tree=ProductTree(full_product_names=[
        FullProductName(name='Widget 1.0', product_id='CSAFPID-W100'),
    ]),
    vulnerabilities=[
        Vulnerability(
            cve='CVE-2026-10001',
            cwes=[cwe_entry('CWE-122')],
            notes=[Note(category='description', text='Heap overflow in widget parser.')],
            product_status=ProductStatus(known_affected=['CSAFPID-W100']),
            metrics=[Metric(
                content=MetricContent(
                    cvss_v3=cvss31_from_vector(
                        'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H'
                    ),
                ),
                products=['CSAFPID-W100'],
            )],
        ),
    ],
)

json_str, report = doc.build()   # raises BuildError on structural violations
assert report.overall_valid
import pathlib
pathlib.Path('advisory.json').write_text(json_str, encoding='utf-8')

build() validates the document and returns (json_str, ValidationReport). The JSON output has sorted keys, two-space indentation, and an auto-injected generator block. See the producer tutorial for step-by-step coverage of all five profiles, CVSS helpers, and build modes.

URL reachability cache

Rules 6.3.06 and 6.3.07 check that URLs in advisory documents resolve to live endpoints. Because HTTP requests are slow and advisory corpora can be large, the validator provides a four-mode URL cache:

Mode Behaviour When to use
run In-memory dedup within the current invocation Default; interactive use
disk Persist results to disk with a configurable TTL CI pipelines
disk-ro Read disk cache; skip on cache miss (no network) Air-gapped or offline builds
none No caching; each URL is checked fresh every run Debugging
# Default: in-memory dedup (no flags needed)
prototyyppi validate --level full advisory.json

# Disk cache with default TTL (24h) and default directory (~/.cache/prototyyppi)
prototyyppi validate --level full --url-cache disk advisory.json

# Custom TTL and cache directory
prototyyppi validate --level full \
    --url-cache disk \
    --url-cache-ttl 7d \
    --url-cache-dir /var/cache/prototyyppi \
    advisory.json

# Read-only: use cache, skip URL rules on miss (no network calls)
prototyyppi validate --level full --url-cache disk-ro advisory.json

# Disable URL rules entirely (shown as [SKIP]; implies --skip-rules 6.3.06,6.3.07)
prototyyppi validate --level full --no-network advisory.json

TTL accepts: 30m, 6h, 7d (minutes, hours, days). The disk cache is stored as a JSON file; entries expire individually based on their write time.

Recommended CI pattern:

prototyyppi validate \
    --level full \
    --url-cache disk \
    --url-cache-dir .cache/prototyyppi \
    --url-cache-ttl 24h \
    'advisories/**/*.json'

Skip rules

Rules can be suppressed individually or via a YAML skip file. Suppressed rules appear as [SKIP] in the output and do not affect the exit code.

CSV on the command line:

prototyyppi validate --skip-rules 6.1.9,6.2.39.02 advisory.json

# May be specified multiple times (processed left to right)
prototyyppi validate --skip-rules 6.1.9 --skip-rules 6.2.39.02 advisory.json

Re-enable a rule skipped by a previous argument or file (prefix with +):

prototyyppi validate --skip-rules base.yaml --skip-rules +6.1.9 advisory.json

YAML skip file — supports structured entries with reason and expiry:

# skip.yaml
- "schema"
- id: "6.1.9"
  reason: "CVSS v3 scorer deviation  under review"
  expires: "2026-12-31"
prototyyppi validate --skip-rules skip.yaml advisory.json

Expired entries still take effect; the validator prints a warning to stderr.

Official examples

The example/ directory contains the official OASIS CSAF 2.1 advisory examples from the CSAF TC repository: six general advisories and thirteen VEX use-case documents.

See example/README.md for per-example validation reports and notes on findings.

To refresh the examples from upstream:

python bin/sync_examples.py           # download/update all files
python bin/sync_examples.py --show    # list bundled files and sizes
python bin/sync_examples.py --dry-run # compare with upstream without writing

Bundled catalogs

All external reference data is bundled and kept up to date within the package. No network access is required for validation.

Catalog Bundled version Sync script
CWE v4.9–v4.13 (969 weaknesses) python bin/sync_cwe.py
SPDX 3.28.0 + ScanCode licensedb python bin/sync_spdx.py
SSVC format_version 3 python bin/sync_ssvc.py
CSAF translations v2.1 (de) python bin/sync_translations.py

Life Cycle Management

Three categories of data bundled in the package require periodic maintenance.

Category Location Update trigger
TC fixture data csaf/v21/data/ New TC test suite release
Spec schema files csaf/v21/schema/ Schema normative change
Reference catalogs csaf/v21/{cwe,spdx,ssvc,translations}/ Upstream version release

TC fixture data

The test suite reads fixture files from prototyyppi/csaf/v21/data/{mandatory,recommended,informative}/. These are a snapshot of the OASIS CSAF TC test suite and require a sibling checkout of the TC repository to refresh.

make sync-fixtures csaf_tc_root=../csaf-2.1   # default: csaf_tc_root=../csaf-2.1

After syncing, stage and commit the changed files:

fossil add prototyyppi/csaf/v21/data/
fossil commit -m "sync: TC fixture data YYYY-MM-DD"

CWE catalog

The CWE catalog at prototyyppi/csaf/v21/cwe/catalog.json is built from MITRE's XML distribution. Download the desired CWE versions and run:

python bin/sync_cwe.py --latest-version 4.13 \
    cwec_v4.9.xml cwec_v4.10.xml cwec_v4.13.xml cwec_v4.14.xml cwec_v4.20.xml

The script reads only local XML files; no network access is required. --latest-version sets the version marker used by rule 6.2.24 (defaults to the highest version found in the supplied files when omitted).

SPDX license catalog

The SPDX catalog at prototyyppi/csaf/v21/spdx/catalog.json combines the SPDX license list with the ScanCode license database.

Network (default):

python bin/sync_spdx.py

Air-gapped — supply locally downloaded source files:

python bin/sync_spdx.py \
    --spdx-licenses licenses.json \
    --spdx-exceptions exceptions.json \
    --scancode scancode-index.json

Download sources from the SPDX license list JSON API and the ScanCode licensedb index.

SSVC decision point catalog

The SSVC catalog at prototyyppi/csaf/v21/ssvc/catalog.json is built from the CERTCC/SSVC repository.

python bin/sync_ssvc.py --sync ssvc --sync cvss   # sync both namespaces
python bin/sync_ssvc.py --show                    # print current catalog
python bin/sync_ssvc.py --namespace ssvc --key A --latest-version 3.0.0  # manual override

Syncing requires network access to the GitHub Contents API. For air-gapped environments, review the upstream repository offline and apply changes with --namespace / --key / --latest-version.

CSAF translations catalog

The translations catalog at prototyyppi/csaf/v21/translations/translations.json is maintained by the OASIS CSAF TC.

python bin/sync_translations.py             # fetch and write
python bin/sync_translations.py --show      # print current catalog
python bin/sync_translations.py --dry-run   # compare with upstream without writing

OASIS examples

The official OASIS CSAF 2.1 examples are bundled in example/ and refreshed via bin/sync_examples.py. See the Official examples section for commands and details.

Adding a new bundled catalog

To add a new reference catalog following the established pattern:

  1. Create bin/sync_<name>.py with network and (where possible) air-gap modes.
  2. Create prototyyppi/csaf/v21/<name>/ and add the generated JSON file.
  3. Add package-data globs to pyproject.toml.
  4. Add an lru_cache loader in prototyyppi/csaf/v21/rules/_shared.py.
  5. Add a section provider in prototyyppi/_env.py for info env output.
  6. Add the catalog to the "Bundled catalogs" table in this README.

Fuzzing

Coverage-guided fuzz testing uses AFL via python-afl. Seven harnesses cover the parser and filter functions with the highest attack surface.

Target Harness Functions covered
cvss-vector fuzz/fuzz_cvss_vector.py _parse_cvss_vector, _cvss2/3/4_base_score
vers fuzz/fuzz_vers.py _parse_vls, _get_vers_pairs
purl fuzz/fuzz_purl.py _validate_purl, _purl_base, _purl_version
spdx-expr fuzz/fuzz_spdx_expr.py _parse_spdx_expression
only-groups fuzz/fuzz_only_groups.py _filter_by_groups
skip-rules fuzz/fuzz_skip_rules.py _build_skip_rules (CSV branch)
ttl fuzz/fuzz_ttl.py _parse_ttl

Prerequisites: afl-fuzz and python-afl must be installed and on PATH. On macOS with pyenv, ensure the virtualenv is active so PYTHON_BIN resolves to the real interpreter (not the pyenv shim).

# Run all targets sequentially (default: 60 s each)
make fuzz

# Run a single target
make fuzz-ttl

# Adjust the time budget
make fuzz-cvss-vector fuzz_time=300

# After a run, promote AFL-discovered queue entries into the seed corpus
make fuzz-update-seeds
# Review fuzz/seeds/ and commit any valuable new seeds

Crash and hang findings land in fuzz/findings/<target>/. The seed corpus lives in fuzz/seeds/<target>/. Each target's seed directory contains at least one valid, non-crashing input so AFL can establish a baseline before mutation begins.

Design and requirements

Document Identifier File
Software Requirements Specification PRO-SRS-001 docs/requirements/srs/
Software Design Description PRO-SDD-001 docs/design/sdd/

Both documents follow the MIL-STD-498 DID structure and are rendered into the documentation site alongside the quickstart, tutorial, and example gallery.

Bug Tracker

Feature requests and bug reports go to the todos of prototyyppi.

Primary Source repository

The main source of prototyyppi is on a mountain in Central Switzerland under configuration control (fossil).

Contributions

To share small changes under the repository's license, kindly send a patchset per email using git send-email.

Support

Submit issues at https://todo.sr.ht/~sthagen/prototyyppi or write plain text email to ~sthagen/prototyyppi@lists.sr.ht.

Security Policy

See SECURITY.md for the security policy.

Changes

See docs/releases/ for release summaries and docs/releases/changes/ for the detailed change log.

Coverage

The test suite maintains high branch coverage (≥99%). The HTML report (if generated) is in site/coverage/.

SBOM

Runtime dependency information is published in docs/sbom/ in SPDX 3.0 (JSON-LD) and CycloneDX 1.6 (JSON) formats. See docs/sbom/README.md for the component inventory and validation guide.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

prototyyppi-2026.7.28.tar.gz (318.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

prototyyppi-2026.7.28-py3-none-any.whl (949.7 kB view details)

Uploaded Python 3

File details

Details for the file prototyyppi-2026.7.28.tar.gz.

File metadata

  • Download URL: prototyyppi-2026.7.28.tar.gz
  • Upload date:
  • Size: 318.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for prototyyppi-2026.7.28.tar.gz
Algorithm Hash digest
SHA256 ead3cb1d884b28dea551b293df07e25817ed62b2fd9c019aef47a9f07a59f5d4
MD5 b15a1fbb65651a791099e1eef7c10579
BLAKE2b-256 4d72daadfc73522f9cd84c8efe7542ccc890a385258bd423d7dc31d2b400eb86

See more details on using hashes here.

File details

Details for the file prototyyppi-2026.7.28-py3-none-any.whl.

File metadata

File hashes

Hashes for prototyyppi-2026.7.28-py3-none-any.whl
Algorithm Hash digest
SHA256 495732087130046e2ec398456d445baf4fd28bdd3d241fb1178929d3986ae033
MD5 2860ee773591e69415d0597f31e145cb
BLAKE2b-256 1c3f8e398fca31374e5e0cf500a744d80607398265e4dcd9d28892e176cee3fe

See more details on using hashes here.

Release history Release notifications | RSS feed

2026.9.6

2 files

2026.8.31

2 files

2026.8.30

2 files

2026.8.26

2 files

2026.8.23

2 files

2026.8.22

2 files

2026.8.21

2 files

2026.8.20

2 files

2026.8.19

2 files

2026.8.16

2 files

2026.8.10

2 files

2026.8.9

2 files

2026.8.8

2 files

2026.8.6

2 files

2026.8.4

2 files

2026.8.3

2 files

2026.8.2

2 files

2026.8.1

2 files

2026.7.31

2 files

2026.7.30

2 files

2026.7.29

2 files

This release

2026.7.28 This release

2 files

2026.7.27

2 files

2026.7.26

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page