Capability manifest
A machine-readable description of every function's declared authorities, attached metadata, and boundary crossings, emitted directly by the compiler. The same compile also produces CycloneDX 1.5, SPDX 2.3, CycloneDX VEX, and SLSA Build L1 attestations from the same source.
A concrete example
Take a small Capa program:
@security(
cve: "CVE-2024-99999",
severity: "high",
fixed_in: "0.2.0",
description: "previous implementation was vulnerable to a timing attack"
)
fun verify_token(token: String, expected: String) -> Bool
return token == expected
fun fetch_user(net: Net, id: String) -> Result<String, IoError>
return net.get("https://api.example.com/users/${id}")
fun token_length(token: String) -> Int
return token.length()
Run the manifest emitter:
$ capa --manifest verify.capa
Trimmed output:
{
"capa_version": "1.2.0",
"schema_version": 1,
"functions": [
{
"name": "verify_token",
"source_name": "verify_token",
"source_container": null,
"source_module_index": null,
"declared_capabilities": [],
"provably_excluded_capabilities": [
"Clock", "Db", "Env", "Fs", "Net",
"Proc", "Random", "Stdio", "Unsafe"
],
"has_unsafe": false,
"attributes": [
{
"name": "security",
"args": {
"cve": "CVE-2024-99999",
"severity": "high",
"fixed_in": "0.2.0",
"description": "..."
}
}
]
},
{
"name": "_capa_m2__fetch_user",
"source_name": "fetch_user",
"source_container": null,
"source_module_index": 2,
"declared_capabilities": ["Net"],
"provably_excluded_capabilities": [
"Clock", "Db", "Env", "Fs",
"Proc", "Random", "Stdio", "Unsafe"
],
"has_unsafe": false,
"attributes": []
}
],
"summary": {
"total_functions": 3,
"functions_with_capabilities": 1,
"functions_crossing_unsafe": 0
}
}
Three things to notice. First, declared_capabilities is exactly what the function's signature carries: verify_token declares nothing; fetch_user declares Net. The list is sound by construction because Capa's analyzer enforces it: a function cannot use a capability it did not list.
A side note on the name fields. name is the loader-time identifier (the form the loader uses for cross-module collision stability; non-pub items imported from another module get a _capa_m{N}__ prefix). source_name, source_container, and source_module_index are the source-level form, the way the function was actually written. CycloneDX and SPDX emitters display the source-level form in their public name / qualname fields, and keep the loader-time identifier as the bom-ref so two same-source-named helpers from different imports do not collapse into one entry. For a root-module function, source_name equals name and source_module_index is null.
Second, provably_excluded_capabilities is the complement. The compiler emits the set of capabilities the function is provably incapable of touching. ``verify_token`` declares nothing, so it provably cannot reach the network, the filesystem, the environment, the clock, or any other built-in. The proof is voided to an empty list when Unsafe is in scope (the escape hatch can side-step the discipline).
Third, attributes attach metadata that travels with the function: a CVE history (@security), an audit timestamp (@audited), a deprecation marker (@deprecated), or a per-function VEX exploitability claim (@vex).
Recorded declassifications
Capa's information-flow control makes a @secret value reaching a public sink (a log line, a network call, a file write) a compile-time violation. The only sanctioned way across the boundary is an explicit declassify(value, reason: "..."). Every one of those bridges is recorded in the manifest, so the SBOM becomes a machine-checkable record of exactly where, and why, a program discloses sensitive data: generated by the compiler, not asserted by hand.
Each function carries a declassifications array. Take a function that masks a card number before logging it:
{
"name": "log_payment",
"source_name": "log_payment",
"source_container": null,
"source_module_index": null,
"declared_capabilities": ["Stdio"],
"declassifications": [
{
"reason": "PCI DSS 3.4: display only the last four PAN digits",
"value": "mask_pan(pan)",
"pos": "13:17"
}
]
}
Each entry is an object with three fields. reason is the string-literal reason given at the declassify call site, recorded verbatim. value is a source-like stringification of the value being declassified (here, mask_pan(pan)). pos is the line:col of the declassify call, so an auditor can jump straight to the disclosure point in source.
The summary carries the program-wide count:
"summary": {
"total_functions": 3,
"functions_with_capabilities": 1,
"functions_crossing_unsafe": 0,
"declassification_sites": 4
}
declassification_sites is the total number of declassify sites across the program (an integer). It answers a question regulators ask directly: PCI DSS, for instance, expects you to show every point where cardholder data is disclosed and the justification for it. The flagship capa_paymentguard example (a payment-security core) ships an SBOM whose declassifications entries list exactly its disclosure points, each with the clause that authorises it.
The v1 attribute catalogue
Four attributes are recognised. The analyzer rejects unknown names, unknown keys, and duplicates; the schema is fixed so downstream consumers can rely on it.
@security
Link a function to a known security history. Keys: cve, cwe, severity, fixed_in, description.
@security(
cve: "CVE-2024-12345",
severity: "high",
fixed_in: "0.2.0"
)
@deprecated
Mark an API as superseded. Keys: reason, since, use, removed_in.
@deprecated(
reason: "timing attack",
since: "0.2.0",
use: "verify_token"
)
@audited
Record a manual security audit. Keys: date, by, scope, notes.
@audited(
date: "2026-05-11",
by: "Nelson Duarte",
scope: "full"
)
@vex
Attach a per-function CycloneDX VEX exploitability claim. Keys: cve, status, justification, detail. Surfaces in --vex output and embeds in --cyclonedx.
@vex(
cve: "CVE-2024-99999",
status: "not_affected",
justification: "code_not_reachable",
detail: "replaced in 0.3.0"
)
The same source, in standard formats
The native --manifest JSON is compact and easy to consume from a Capa-aware tool. For interoperability with the broader supply-chain tooling ecosystem, the compiler emits four additional documents from the same source:
capa --cyclonedx file.capa· A valid CycloneDX 1.5 SBOM. Each function becomes alibrarycomponent with a deterministicbom-ref; per-function capability metadata is flattened into the standardproperties[]array under thecapa:*namespace, includingcapa:declared_capabilityandcapa:provably_excluded_capability. Ingested by Dependency-Track, OSV-Scanner, syft, sbom-utility, and any other CycloneDX-aware tool.capa --spdx file.capa· The SPDX 2.3 companion. Same content, SPDX shape; per-function capability metadata is exposed viaannotations[]. Useful when downstream consumers standardise on SPDX rather than CycloneDX.capa --vex file.capa· A standalone CycloneDX VEX document driven by the@vexattributes. The same VEX block is also embedded in--cyclonedxoutput. Theaffects[]of each entry pinpoints the function the claim was made on, not the package, so VEX granularity matches Capa's manifest granularity.capa --provenance file.capa· A SLSA Build L1 provenance attestation: an in-toto Statement v1 envelope wrapping a SLSA Provenance v1.0 predicate. Thesubjectrecords the SHA-256 of the source file, binding the attestation to the exact bytes that produced the build.capa --doc file.capa· A self-contained HTML page for human readers. Doc comments (///,/** */), signatures with capability badges, and attached attributes all rendered without external resources or JavaScript.
The examples/ directory has runnable demos for each: examples/manifest_demo.capa, examples/vex_demo.capa, and the case studies that exercise the full chain.
Byte-reproducible. The --cyclonedx, --spdx, --vex, and --provenance artefacts are byte-for-byte identical for the same source on any machine and OS once SOURCE_DATE_EPOCH is set: their identifiers (CycloneDX serialNumber, SPDX documentNamespace, provenance invocationId) derive from the source SHA-256, and that one variable pins the only otherwise-varying field, the build timestamp. An auditor can recompile and diff the output to confirm an artefact came from the source it names. With the variable unset, timestamps record real wall-clock time, so determinism is opt-in.
How to use it
- CI gate. Run
capa --manifest src/*.capain CI, diff the output against a baseline, and fail the build if a function unexpectedly gains a capability or crosses theUnsafeboundary. The capability surface of a library becomes a tracked artefact. Theexamples/sbom_diff.capaauditor program does the equivalent for CycloneDX SBOMs across releases, reporting per-function widenings, narrowings, additions, and removals. - Audit evidence. Generate the manifest at release time and ship it alongside the binary. An auditor can verify the claims without re-running the compiler.
- Policy enforcement.
examples/sbom_capability_audit.capareads an SBOM plus a per-function policy file and flags any function whose declared capabilities exceed the policy's allowance.
The manifest is a stable contract. Versioned via schema_version; consumers should refuse to read manifests with a schema version they do not recognise. v1 is the current version.
What the manifest does not tell you
- Whether a capability holder behaves well. A function that legitimately receives
Netcan do whateverNetallows, including misuse. The manifest documents the permission surface; it does not predict behaviour. - Anything beyond an
Unsafeboundary. Once a value crosses into Python interop, the discipline ends. The manifest marks the crossing (has_unsafe: true); content beyond it must be audited separately. - Dependencies you did not write. v1 only describes the program you compile. Per-package manifests exist for every dep installed through
capa.toml; aggregating them into a single graph-wide view (one manifest covering the program plus its transitive deps) is the remaining tooling step.
Regulatory mapping
The manifest's structure was designed with the audit requirements of several frameworks in mind: CRA (EU Cyber Resilience Act), NIS2, DORA, NIST SSDF, and OWASP SCVS. A rendered overview lives on the regulatory page; the article-by-article correspondence lives in:
docs/cra.md· CRA deep-dive (Annex I obligations ↔ manifest fields).docs/regulatory.md· Multi-jurisdiction comparative table: 8 Capa artefacts × 5 frameworks, four-level classification (direct / indirect / partial / out of scope).
These documents are the place to look when you need to point a specific clause to a specific manifest field.