Metadata-Version: 2.4
Name: prototyyppi
Version: 2026.8.21
Summary: Prototyyppi makes the Common Security Advisory Framework (CSAF) standard actionable.
Author-email: Stefan Hagen <stefan@hagen.link>
Maintainer-email: Stefan Hagen <stefan@hagen.link>
License-Expression: MIT
Project-URL: Documentation, https://codes.dilettant.life/docs/prototyyppi
Keywords: developer-tools,security-advisory,vex
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: jsonschema>=4.26.0
Requires-Dist: kaava>=2026.6.20
Requires-Dist: msgspec>=0.21.1
Requires-Dist: python-jsonpath>=2.2.1
Provides-Extra: dev
Requires-Dist: pytest>=9.1.1; extra == "dev"
Provides-Extra: fast-schema
Requires-Dist: jsonschema-rs>=0.49.2; extra == "fast-schema"
Provides-Extra: lsp
Requires-Dist: pygls>=2.1.1; extra == "lsp"
Provides-Extra: manifest
Provides-Extra: render
Requires-Dist: jinja2>=3.1; extra == "render"
Provides-Extra: tui
Requires-Dist: textual>=8.2.8; extra == "tui"
Provides-Extra: yaml
Requires-Dist: ruamel.yaml>=0.19.1; extra == "yaml"

# prototyyppi

Prototyyppi makes the Common Security Advisory Framework (CSAF) standard actionable:
Converting, creating, mocking, rendering, validating, and viewing CSAF documents per Python API, CLI, and LSP.

Requires Python 3.11 or later.

## Install

Minimal feature set is installed per: `pip install prototyyppi`. Optional extras activate a faster JSON Schema validation or additional capabilities:

fast-schema
:    allows the use of a compiled json schema validator - typically ten or more times faster then the builtin variant (adds jsonschema-rs).

lsp
:    adds an lsp server to support editor integration (adds pygls).

render
:    enables html and markdown rendering of advisories (adds jinja2).

tui
:    enables the textual user interface used for edit, interactive markdown render, and view mode (adds textual).

yaml
:    enables yaml as edit mode and output format (adds ruamel.yaml).


Installation of extras is by including these as a comma separated list in square brackets. For example:

```bash
pip install prototyyppi[fast-schema,lsp,render,tui,yaml]  # all extras
```

## Manual

The [man page](man/) [prototyyppi.1](man/prototyyppi.1) provides the full CLI reference (place prototyyppi.1 on your `MANPATH`):

```bash
mkdir -p ~/.local/share/man/man1 && cp prototyyppi.1 ~/.local/share/man/man1/
```

## Quickstart

### CLI

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

```bash
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, `1` for invalid document, and `2` for usage errors.

**Validation presets** - run additional test tiers:

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

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

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

**Output formats:**

```sh
# 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:**

```sh
# 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'
```

**Rule filtering** - suppress or allow-list specific rules:

```sh
# Skip by ID or from a YAML file (shown as [SKIP])
prototyyppi validate --skip-rules 6.1.9 advisory.json
prototyyppi validate --skip-rules skip.yaml advisory.json

# Run only specific rules (allowlist; composes with --skip-rules)
prototyyppi validate --only-rules 6.1.1,6.1.2 advisory.json
prototyyppi validate --only-rules rules.yaml advisory.json
```

**Suppress output:**

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

**Performance instrumentation:**

```sh
# Per-rule timing and peak RSS delta - report to stderr
prototyyppi validate --perf advisory.json

# Save a JSON baseline for later comparison
prototyyppi validate --preset full --perf --perf-format json \
    --perf-output baseline.json advisory.json
```

Exit code is unchanged by `--perf`.
On Windows the RSS delta column shows `n/a` (the `resource` module is POSIX-only); wall-clock timing works on all platforms.

**Advisory browser (TUI):**

```sh
# Open an advisory in the interactive terminal browser
prototyyppi view advisory.json

# Multiple files - navigate with arrow keys in the sidebar
prototyyppi view advisories/*.json

# Pipe from the producer API
python build_advisory.py | prototyyppi view
```

Requires `pip install 'prototyyppi[tui]'`.
Five tabs: **Summary** (metadata and conformance status), **Validation** (rule-by-rule table), **Dimensions**
(Appendix C soft-limit report, computed lazily in the background), **Document** (full structured rendering of the CSAF document),
and **Log** (append-only per-file validation record that persists across rule re-runs).
The loading screen streams rule results in real time as validation runs in the background;
the advisory view is available immediately once all files finish loading.
The theme indicator in the header subtitle (e.g. `advisory.json  [textual-dark]`) updates automatically when the theme is changed via
the command palette (`Ctrl+P`).

**JSON editor (TUI):**

```sh
# Edit a CSAF advisory file
prototyyppi edit advisory.json

# Choose a file from an in-app directory browser
prototyyppi edit

# Pipe from a script (stdin is re-routed so Ctrl+S works)
python draft_advisory.py | prototyyppi edit
```

Requires `pip install 'prototyyppi[tui]'`.
The editor formats the JSON canonically (2-space indent, POSIX newline) on load.
A **Fields** pane on the right tracks the cursor and shows the CSAF schema field's title, description, type, and all active constraints
(enum values, `format`, `pattern`, `minLength`/`maxLength`, numeric ranges).
Key bindings: `Ctrl+S` save, `Ctrl+Q` quit (unsaved-changes guard), `Ctrl+F` toggle / `F7` narrow / `F8` widen the fields pane,
`Ctrl+Z` undo, `Ctrl+R` redo, `Ctrl+Y` copy current field constraints to clipboard, `Ctrl+P` command palette.

**Form editor (TUI):**

```sh
# Open the schema-driven full document editor
prototyyppi edit --form advisory.json

# Pipe from a script
python draft_advisory.py | prototyyppi edit --form
```

Requires `pip install 'prototyyppi[tui]'`.
The form editor renders every field in the advisory as a purpose-built widget:
enum-constrained fields become `Select` drop-downs (including all CVSS metric fields), and free-text fields become `Input` boxes.
Nested objects and arrays of objects are shown as indented section groups driven by the CSAF 2.1 JSON schema,
with **Add** and **Delete** controls on each array section.
CVSS v2, v3, and v4 objects are rendered from their referenced external schemas; `cvss_v3` resolves the correct sub-schema
(v3.0 or v3.1) by matching the document's `version` field.
A **Hints** pane on the right shows the JSON path, schema title, description, allowed values, type, and constraints for the focused field,
plus the external schema URL for CVSS and other referenced-schema fields.
Below the schema metadata the pane shows editorial guidance from the built-in hints catalog (14 entries covering key CSAF 2.1 fields);
organizations can extend the catalog via `prototyyppi eject hints`, the `[hints]` config section, or the `--hints-dir PATH` flag.
Key bindings: `Ctrl+S` save, `Ctrl+Q` quit, `Ctrl+F` toggle hints pane,  `F5`/`F6` narrow/widen label column, `F7`/`F8` narrow/widen hints pane,
and `Ctrl+Y` copy hints pane to clipboard.

**Advisory renderer:**

```sh
# Render to Markdown - print to stdout (requires prototyyppi[render])
prototyyppi render markdown advisory.json

# Render to a self-contained HTML page (requires prototyyppi[render])
prototyyppi render html advisory.json > advisory.html
prototyyppi render html advisory.json --output advisory.html

# Interactive TUI Markdown browser with TOC pane (requires prototyyppi[tui])
prototyyppi render markdown --interactive advisory.json

# Customise the template
prototyyppi eject template --output my.md.jinja           # Markdown template
prototyyppi eject template --format html --output my.html.jinja  # HTML template
prototyyppi render markdown --style my.md.jinja advisory.json
prototyyppi render html     --style my.html.jinja advisory.json
```

The Markdown renderer produces a structured report with metadata, notes, vulnerabilities,
product tree, references, and revision history.
The HTML renderer produces a single self-contained file with inline CSS, dark-mode support,
and TLP label colour badges.
`render_markdown()`, `render_html()`, and their `*_bytes()` variants are available
as public Python API.

**Language Server Protocol:**

Start the LSP server (requires installation of prototyyppi\[lsp]): `prototyyppi serve lsp`.

The server speaks JSON-RPC 2.0 over stdio (`Content-Length` framing).
It validates the open advisory on every `didOpen`/`didChange` event at the `recommended` preset and pushes diagnostics via `publishDiagnostics`.
Rule IDs are in the diagnostic `code` field.
See the [man page](man/) for per-editor configuration snippets (Neovim, VS Code, Helix, Emacs/eglot).

**Shell completion:**

```sh
prototyyppi completion bash >> ~/.bash_completion    # bash
prototyyppi completion zsh  >> ~/.zshrc              # zsh
prototyyppi completion fish > ~/.config/fish/completions/prototyyppi.fish
```

**Version and info -- machine-readable output:**

```sh
prototyyppi version                      # plain-text version string
prototyyppi version --format json        # {"version": "2026.8.10", "package": "prototyyppi"}
prototyyppi version --format yaml        # same fields in YAML
prototyyppi info version --format json   # same content via info subcommand group
prototyyppi config explain --format json # effective config as a JSON dict (dataclasses.asdict)
prototyyppi config explain --format yaml # same as YAML
```

**Configuration:**

```sh
prototyyppi config eject --format toml              # generate a ready-to-edit config template
prototyyppi config explain                          # show effective values with source provenance
prototyyppi config explain --format json            # machine-readable config dict
prototyyppi config doctor                           # validate all discovered config files
```

User config lives in ~/.config/prototyyppi/config.toml; project config in .prototyyppi.toml (or .yaml/.json) in the project root.
CLI flags override both.
Multi-word keys are written in kebab-case in the template (skip-rules, url-cache, etc.); both kebab-case and snake\_case are accepted when reading.

The `[ui]` section controls TUI appearance across all interactive screens:

```toml
[ui]
theme = "gruvbox"     # Textual theme name; try also: nord, dracula, catppuccin-mocha
dialog-style = "text" # "text" for ASCII [ Label ] buttons, "native" for Textual Button
```

Set `PROTOTYYPPI_UI_THEME` and `PROTOTYYPPI_UI_DIALOG_STYLE` to override per-invocation.

The `[log]` section enables audit logging of CLI and TUI operations (off by default):

```toml
[log]
level = "info"   # debug, info, warning, error, critical, or off
sink  = "stderr" # stderr (default), stdout, or a file path
```

Set `PROTOTYYPPI_LOG_LEVEL` and `PROTOTYYPPI_LOG_SINK` to override per-invocation.
See the [Configuration tutorial](tutorial/configuration/) for all supported keys.

**Rule catalog:**

```sh
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:**

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

**Mock advisory generator:**

```sh
# Generate a single document (base profile, default size 0.05)
prototyyppi mock

# Specific profile; seed printed to stderr for reproduction
prototyyppi mock --profile security-advisory --seed demo42

# Write to a file and validate at basic preset
prototyyppi mock --profile vex --validate --seed workshop1 advisory.json

# Richer document (more optional fields)
prototyyppi mock --size 0.5 --profile security-advisory

# Batch: three documents as RFC 7464 JSON Text Sequences on stdout
prototyyppi mock --count 3 --seed batch1

# Batch to separate files (doc-1.json, doc-2.json, doc-3.json)
prototyyppi mock --count 3 --seed batch1 'doc-{}.json'

# Pin specific fields with a YAML overlay
cat > overlay.yaml << 'EOF'
/document/publisher/name: "ACME Corp"
/document/title: "Test Advisory"
EOF
prototyyppi mock --template overlay.yaml --seed pinned
```

The resolved seed is always printed to stderr.
Repeat a run exactly by passing `--seed <resolved-seed>`.
Mock advisories satisfy the CSAF 2.1 JSON Schema and all mandatory profile-based rules but contain synthetic data and
MUST NOT be published as genuine advisories.

See the [Mock advisories tutorial](tutorial/mock/) for step-by-step coverage of all profiles, overlays, and batch output.

For a condensed single-page reference see the [Quickstart guide](quickstart/).
For tutorials covering validation and document production see the [Tutorial index](tutorial/).

### Python API - consumer (validate)

```python
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:

```python
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:

```python
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](tutorial/producer/) 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                    |

```sh
# Default: in-memory dedup (no flags needed)
prototyyppi validate --preset full advisory.json

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

# Custom TTL and cache directory
prototyyppi validate --preset 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 --preset 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 --preset 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:**

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

## Document dimension report (`measure`)

The `measure` subcommand measures advisory documents against the informative soft limits defined in Appendix C of the CSAF 2.1 specification -
file size, array lengths, string lengths, URIs, dates, and enumerations.
It does not perform conformance validation.

Each dimension is rated `ok` (at or below the caution threshold), `caution` (above threshold but within the limit), or `exceeds` (above the limit).
The overall posture is the worst-case rating across all dimensions.

```sh
# Single file - human-readable text
prototyyppi measure advisory.json

# Structured output
prototyyppi measure --format json advisory.json
prototyyppi measure --format yaml advisory.json

# SARIF 2.2 - for GitHub code scanning or VS Code SARIF viewer
prototyyppi measure --format sarif 'advisories/**/*.json' > measure.sarif

# Batch - text report with grand total footer
prototyyppi measure 'advisories/**/*.json'
```

**Adjusting the caution threshold:**

```sh
# Rate a dimension caution when it exceeds 75% of the limit (default: 50%)
prototyyppi measure --caution-fraction 0.75 advisory.json
```

**Selecting the taxonomy version:**

```sh
# v21 (default), v20, or the alias v21csd02
prototyyppi measure --spec-version v20 advisory.json
prototyyppi measure --spec-version v21csd02 advisory.json
```

**Exit codes:** `0` (all ok or caution), `1` (any exceeds, or a file could not be read), `2` (usage error).

The four official OASIS Appendix examples (`example/appendix/`) all pass at `ok` posture.
See [example](example/) for the full per-file dimension reports.

**Single-pass combined conformance + dimension report:**

The command `validate --with-measure` runs both pipelines on the same decoded document - no second parse.
The dimension block is appended to each file's conformance output.

```sh
# Single file: conformance + dimensions in one invocation
prototyyppi validate --with-measure advisory.json

# Also fail on any exceeds posture (default: dimension posture does not affect exit code)
prototyyppi validate --with-measure --measure-exit advisory.json

# Machine-readable output with embedded measure key
prototyyppi validate --format json --with-measure advisory.json

# SARIF with two runs per file (validation run + measure run)
prototyyppi validate --format sarif --with-measure advisory.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:**

```sh
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 `+`):

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

**YAML skip file** - supports structured entries with reason and expiry:

```yaml
# skip.yaml
- "schema"
- id: "6.1.9"
  reason: "CVSS v3 scorer deviation - under review"
  expires: "2026-12-31"
```

```sh
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 official OASIS CSAF 2.1 advisory examples from
the [CSAF TC repository](https://github.com/oasis-tcs/csaf/tree/master/csaf_2.1/examples/csaf):
six general advisories, four normative appendix examples (collapsing product paths, examples 11–14), and thirteen VEX use-case documents.

See [example](example/) for per-example validation reports and notes on findings.

To refresh the examples from upstream:

```sh
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

Four 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                     |
| Official examples  | `example/`                               | New CSAF TC release (main, appendix, or VEX) |

### 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.

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

After syncing, run the test suite to confirm all fixture-driven tests still pass:

```bash
python -m pytest test/ -q
```

New fixtures for rules that were previously `not_implemented` will cause new tests to be collected; updated fixtures may change pass/fail counts.
Investigate any unexpected failures before committing.

Once the test suite is green, stage and commit the changed files:

```bash
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:

```bash
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):**

```bash
python bin/sync_spdx.py
```

**Air-gapped** - supply locally downloaded source files:

```bash
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.

```sh
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.

```sh
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/` (main advisories),
`example/appendix/` (Appendix normative examples), and `example/csaf_vex/` (VEX use cases).
All three groups are refreshed via `bin/sync_examples.py`.

```sh
python bin/sync_examples.py             # download/refresh all groups
python bin/sync_examples.py --show      # print bundled filenames and sizes
python bin/sync_examples.py --dry-run   # compare with upstream without writing
python bin/sync_examples.py --no-appendix  # skip appendix/ group
python bin/sync_examples.py --no-vex       # skip csaf_vex/ group
```

After syncing, stage and commit the changed files:

```sh
fossil add example/
fossil commit -m "sync: OASIS CSAF examples YYYY-MM-DD"
```

### 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](https://github.com/google/AFL) via
[python-afl](https://github.com/jwilk/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`                                    |
| `prefix`      | `fuzz/fuzz_prefix.py`      | `_disambiguate`, `_expand_option_values`        |

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).

```sh
# 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

Requirements and design specification are both presented in the MIL-STD-498 SRS and SDD structures.

Software Requirements Specification
:    PRO-SRS-001 - [SRS](requirements/srs/)

Software Design Description
:    PRO-SDD-001 - [SDD](design/sdd/)

## Bug Tracker

Feature requests and bug reports go to the [todos of prototyyppi](https://todo.sr.ht/~sthagen/prototyyppi).

## Primary Source repository

The main source of `prototyyppi` is on a mountain in Central Switzerland under configuration control ([fossil](https://fossil-scm.org/)).

## Contributions

To share small changes under the repository's license, kindly send a patchset per email using [git send-email](https://git-send-email.io).

## 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 [releases](releases/) for release summaries and [releases/changes](releases/changes/) for the detailed change log.

## Coverage

The test suite maintains a branch coverage above 80% during prototyping and documented at [coverage/](coverage/).

## SBOM

Runtime dependency information is published in SPDX 3.0 (JSON-LD) and CycloneDX 1.6 (JSON) formats.
See [SBOM](sbom/) for a human readablke summary of the component inventory and a validation guide.
