Metadata-Version: 2.4
Name: prototyyppi
Version: 2026.7.26
Summary: Experimental CSAF Implementation.
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: msgspec>=0.21.1
Requires-Dist: python-jsonpath>=2.2.1
Requires-Dist: PyYAML>=6.0.3
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Provides-Extra: manifest

# prototyyppi

Experimental Python reference implementation of [CSAF 2.1](https://docs.oasis-open.org/csaf/csaf/v2.1/csaf-v2.1.html).
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

```sh
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](docs/man/prototyyppi.1) provides the full CLI reference.
After installation, place it on your `MANPATH`:

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

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

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

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

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

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

**Suppress output:**

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

**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 --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](docs/quickstart/README.md).
For tutorials covering validation and document production see the [Tutorial index](docs/tutorial/README.md).

### 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](docs/tutorial/producer/README.md) 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 --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:**

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

> **Note:** Rules 6.3.06 and 6.3.07 (URL resolution) are pending implementation.
> The URL cache infrastructure is in place and will be activated automatically
> when these rules are implemented.
> The `--no-network` flag and all `--url-cache` options are wired up and ready.

## 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 the 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 and thirteen VEX use-case documents.

See [example/README.md](example/README.md) 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` |

## Design and requirements

| Document                            | Identifier  | File                                                      |
|:------------------------------------|:------------|:----------------------------------------------------------|
| Software Requirements Specification | PRO-SRS-001 | [docs/requirements/srs/](docs/requirements/srs/README.md) |
| Software Design Description         | PRO-SDD-001 | [docs/design/sdd/](docs/design/sdd/README.md)             |

Both documents follow the MIL-STD-498 DID structure
and are rendered into the documentation site alongside the
[quickstart](docs/quickstart/README.md), [tutorial](docs/tutorial/README.md),
and [example gallery](example/README.md).

## 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 [docs/releases/](docs/releases/README.md) for release summaries
and [docs/releases/changes/](docs/releases/changes/README.md) 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.
