Website: https://capa-language.com/
Supply-chain SBOMs generated by the compiler from type-checked capability signatures.
Capa is a small, capability-typed programming language. A function's
signature names the authorities it is handed (Fs, Net, Stdio,
Clock, Random, Env, Db, Proc, Serve, Unsafe), the analyzer
refuses a function that calls a capability that is not in scope, and
the compiler emits a capability manifest, CycloneDX 1.6 and
SPDX 2.3 documents carrying the per-function capability sets, a
VEX document from your @vex claims, and SLSA Build L1
provenance over the source files. The supply-chain artefacts come
from the compiler itself, not from a separate scanner approximating
them after the fact.
The toolchain is a complete Python 3.10+ implementation: lexer,
parser, semantic analyzer, transpiler to Python, runtime, language
server, formatter, documentation generator, and a WebAssembly
Component Model backend (capa --wasm) that compiles the same
source to a .wasm component with a WIT spec per capability, runnable
on any Component-Model-aware runtime or inline through the bundled
wasmtime host. Top-level functions tagged @export() are lifted into
that component's WIT world alongside main, callable directly from a
Component-Model host; this covers scalar (Int/Float/Bool/Unit)
signatures today, with String and composite types across the
boundary still deferred.
$ capa --run examples/grades.capa
=== Roster ===
Ana: 17.5 (Excellent)
Bruno: 13.0 (Pass)
Carla: 8.5 (Fail)
Diogo: 15.5 (Good)
Eva: 11.0 (Pass)
Filipe: 19.0 (Excellent)
Statistics:
Average: 14.083333333333334
Minimum: 8.5
Maximum: 19.0
Passed: 5
Failed: 1A helper that needs no authority is handed none: classify takes a
Float, so a call such as stdio.println(...) in its body is refused
(there is no stdio in scope).
fun classify(score: Float) -> String
if score >= 9.5
return "Excellent"
if score >= 8.0
return "Good"
if score >= 6.5
return "Pass"
return "Fail"
A function that prints needs Stdio; a function that reads files
needs Fs. The signature is the contract:
fun summarise(stdio: Stdio, fs: Fs, path: String) -> Result<Unit, IoError>
let body = fs.read(path)?
match body.split("\n").get(0)
Some(first) -> stdio.println("first line: ${first}")
None -> stdio.println("empty file")
return Ok(())
capa --manifest <file> emits the same information as JSON: per
function, the capabilities its signature holds, with a flag on any
function whose authority is not provable from its types. There is
no "hidden Stdio": the compiler refuses to compile a classify
that suddenly calls stdio.println(...) because classify does
not take stdio: Stdio.
Capabilities can also be attenuated: fs.restrict_to("data/")
returns a fresh Fs whose authority is narrowed to that prefix,
and the narrowing is monotonic by construction.
Capabilities control which effects a function may exercise;
information-flow control constrains where data may flow. Mark
data @secret and the analyzer tracks it towards the public sinks it
recognises (a log line, a network call, a file write; the reference
lists them): by default a flow it detects is a warning that names
the flow, and a function annotated @strict_ifc() turns that warning
into a hard error. The check is not a proof that no secret reaches
an output. Either way the one audited escape hatch is declassify:
fun leak(env: Env, stdio: Stdio)
match env.get("API_KEY") // env.get is @secret by default
Some(key) -> stdio.println(key) // analyzer flags this: @secret to a public sink
None -> stdio.println("no key")
declassify(value, reason: "...") is the single auditable
secret-to-public bridge, and every use on a @secret value is recorded
in the Capa manifest (declassifications per function, counted as
declassification_sites), so the manifest says where, and why, a
program deliberately discloses sensitive data through declassify. The
tour walks through the
rest of the feature set.
# Linux / macOS Apple Silicon (one-liner)
curl -fsSL https://raw.githubusercontent.com/nelsonduarte/capa-language/main/deploy/install.sh | bash# Windows
irm https://raw.githubusercontent.com/nelsonduarte/capa-language/main/deploy/install.ps1 | iex# From source (any platform with Python 3.10+)
git clone https://github.com/nelsonduarte/capa-language
cd capa-language && pip install -e .After install, capa --version should work from any directory.
For the manual binary download, language-server setup, and the
VSCode extension, see docs/getting-started.md.
capa --run file.capa # transpile + execute via Python
capa --check file.capa # lex + parse + semantic check
capa --transpile file.capa # emit Python to stdout
capa --ir --run file.capa # run via the CIR middle-end
# (AST->CIR->Python); falls back
# to the legacy transpiler for
# constructs CIR does not cover
capa --wasm --run file.capa # compile + run on wasmtime
capa --wasm --component --run file.capa
# wrap as a Component Model
# artifact + run via
# wasmtime.component
capa --wasm --component --output app.wasm file.capa
# write a standalone .wasm
# component (WIT embedded)
capa --wit file.capa # emit the WIT spec to stdout
capa --manifest file.capa # JSON capability manifest
capa --cyclonedx file.capa # CycloneDX 1.6 SBOM (caps embedded)
capa --spdx file.capa # SPDX 2.3 (caps embedded)
capa --vex file.capa # standalone VEX document
capa --provenance file.capa # in-toto + SLSA Provenance v1.0
capa --doc file.capa # HTML doc page from /// comments
capa --fmt file.capa # canonical-style rewrite
capa init my-project # project scaffold
capa install # fetch capa.toml dependencies
capa test # run tests/test_*.capa; exit 0 = pass
# (--wasm: Wasm backend; --both: run on
# both backends AND diff their stdout,
# divergence fails; see docs/testing.md)
capa migrate file.capa # Python->Capa hardening progress
# (--json for the machine form;
# see docs/migration.md)
capa lsp # language server (stdio)Arguments after -- are forwarded to the program (visible via
env.args()):
capa --run myprog.capa -- input.json --verboseThese live in standalone repositories, each around 500-1500 lines
of Capa. Dependencies on the seed libraries are declared in a
capa.toml and fetched by capa install; every demo's README
walks through the audit manifest.
| Repo | What it does | What it stresses |
|---|---|---|
| audit-trail-reporter | Reads a JSONL financial transaction log, runs four AML rules (threshold, watchlist, structuring, velocity), emits CSV + JSON + alerts | Multi-module project; capability attenuation (read Fs for data/, write Fs for output); every rule declares no capability |
| sbom-watch | Reads a CycloneDX SBOM + an OSV-style CVE DB + a policy file, emits a risk report. CI-friendly exit code | Cross-source matching shape. Consumes exactly what capa --cyclonedx produces |
| policy-eval | Evaluates a JSON-encoded policy AST (with recursive all_of/any_of/not) against a subject document |
Tree-walk interpreter shape; exercises recursive sum types |
Each demo's --manifest is a good way to see what the capability
discipline catches in practice: the rule functions and the
renderers declare no capabilities; only parsers and writers
ever see Fs.
All three also run end-to-end under the Wasm backend with output
bit-identical to the Python reference path, in both modes:
capa --wasm --run (core wasm on wasmtime) and capa --wasm --component --run (Component Model artifact instantiated via
wasmtime.component, no host-side memory bridges). The JSON
parser is bundled into the guest module so no capa:host/json
import is needed at the Component Model boundary.
The runtime ships built-in types (Result, Option, List,
Map, Set, JsonValue) and ten built-in capabilities (Stdio,
Fs, Net, Env, Clock, Random, Db, Proc, Serve,
Unsafe). Serve (inbound TCP) and Unsafe are Python-backend
only; capa --wasm rejects a program whose signatures reach either.
Full reference in docs/stdlib.md.
Eight seed libraries live in standalone repos and are consumed via the package manager:
| Library | Repo | Surface |
|---|---|---|
capa_cli |
nelsonduarte/capa_cli | argument parser: positionals, flags, options, --help |
capa_csv |
nelsonduarte/capa_csv | RFC 4180 CSV parser, header view, and writer; zero-capability |
capa_datetime |
nelsonduarte/capa_datetime | ISO 8601 parsing + Y/M/D/h/m/s arithmetic, zero-capability |
capa_hash |
nelsonduarte/capa_hash | SHA-256/SHA-224/HMAC-SHA256, zero-capability, with constant-time tag comparison |
capa_http |
nelsonduarte/capa_http | capability-typed HTTP client over urllib, exposed as an Http capability |
capa_log |
nelsonduarte/capa_log | levelled logging (DEBUG/INFO/WARN/ERROR) via a Logger capability over Stdio |
capa_sbom |
nelsonduarte/capa_sbom | CycloneDX + SPDX JSON parsing with capa:* capability queries; zero-capability |
capa_test |
nelsonduarte/capa_test | tiny assertion library for the capa test runner; Stdio-only |
To use any of them in a project:
# capa.toml
[package]
name = "my-project"
version = "0.1.0"
[dependencies]
# For production: pin to an immutable commit SHA. Tags are
# convenient but mutable upstream (a force-push moves them);
# rev = "<sha>" is what audit-grade builds want.
capa_log = { git = "https://github.com/nelsonduarte/capa_log", rev = "<commit-sha>" }
# For development the friendlier tag form works too; ``capa install``
# records the resolved SHA in capa.lock and *refuses* on subsequent
# runs when the upstream tag has been re-pointed at a different
# commit. Pass ``--update`` to accept a new SHA deliberately.
# capa_log = { git = "https://github.com/nelsonduarte/capa_log", tag = "v0.1" }
# For audit-grade builds: add the publisher's GPG fingerprint and
# ``capa install`` runs ``git verify-tag`` against your keyring,
# refusing to install unless the signature matches. Defends against
# account compromise + tag tampering even when the lockfile is empty.
[dependencies.capa_log]
git = "https://github.com/nelsonduarte/capa_log"
tag = "v0.1"
verify_key = "1234 5678 90AB CDEF 1234 5678 90AB CDEF 1234 5678"
# Test/tooling-only deps go under [dev-dependencies]: same schema,
# same validation, installed only when THIS project is the install
# root. Consumers of your package never fetch them. Declare from
# the CLI with `capa add --dev <name> ...`.
[dev-dependencies]
capa_testkit = { git = "https://github.com/user/capa_testkit", tag = "v0.2" }Then capa install materialises the deps under ./vendor/ and
the loader picks them up automatically. See
docs/packages.md for the manifest schema,
lockfile semantics, and resolution order.
capa/ # Python package: compiler + runtime + pkg manager
lexer/ parser/ analyzer/ transpiler/ runtime/
ir/ # CIR + Python and Wasm Component Model backends + WIT emitter
manifest/ docgen/ lsp/ pkg/ cli/
tests/ # 5,600+ unit, end-to-end, and property tests
examples/ # .capa programs (basics, CVE case studies, LLM sandbox)
# (seed libraries now all live in standalone repos; see Standard library section)
docs/ # public website (HTML) + design writeups (.md)
proofs/ # mechanised soundness theorems for lambda_cap (Agda)
benchmarks/ # Capa vs hand-Python micro-benchmarks
Capa-EBNF.md # formal grammar
pyproject.toml # package metadata + optional [test] / [lsp] extras
LICENSE STABILITY.md CONTRIBUTING.md SECURITY.md README.md
Capa ships as 1.32.0 (released 2026-08-22), with the full
security axis (information-flow control, constant-time markers, and
typestate protocols) and the fully functional Wasm backend (see
CHANGELOG.md). The stability commitment in
STABILITY.md is now in effect: post-1.0,
breaking changes to the covered surfaces require a major bump, and
deprecations get one minor release of warning first.
5,600+ tests spanning the lexer, parser, analyzer, transpiler,
LSP, formatter, attribute-schema validation, package manager, the
information-flow / constant-time / typestate checkers, the Wasm
backend (with a Python/Wasm output parity harness), and
Hypothesis-based property tests. The transpiler
suite actually executes the generated Python and checks stdout; the
property suite fuzzes the full pipeline with arbitrary text and
syntax-aware Capa programs. The Wasm backend runs every capability it
supports (Fs, Env, Clock, Stdio, Net, Random, Db, Proc, all but the
Python-only Serve and Unsafe, which it rejects loudly) and the full
language surface with output byte-identical to the Python reference, and
cross-function capability attenuation is enforced by host-side handle
tables for a Capa-emitted artifact; the enforcement lives in the
trusted emitter/host, not the runtime boundary, so the executed
artifact is part of the TCB (see
docs/design/wasm-cap-handles.md
and trust-model.md).
Run them:
pip install -e '.[test]' # hypothesis + PyYAML: without them,
# whole test modules skip rather than fail
python -m unittest discover testsThat is the exact command CI runs, so a green local run means the same
thing as a green CI run. The [test] extra also brings pytest, which is
useful as a selector over the same suite (python -m pytest -k capability, -x, --lf) when you are iterating on one area; the
authoritative full run stays unittest discover. The suite must be run
with the extra installed either way: it was a missing PyYAML that let
eleven supply-chain tests skip while the run printed OK.
Capa also dogfoods its own supply-chain posture in its own build.
The compiler has zero third-party runtime dependencies (it is pure
Python standard library), so the published wheel and sdist are
dependency-free and pip install capa-language pulls nothing from
third parties; the optional extras in
pyproject.toml
are version floors for dev/CI tooling, never runtime pins. In CI, those
dev/CI dependencies (test, wasm, and LSP tooling) are installed from
universal, hash-pinned lockfiles
(requirements-test.lock
and requirements-ci.lock,
both uv-generated) under pip install --require-hashes, so every CI
dependency is verified byte-for-byte against a sha256 and a tampered or
drifted dependency fails the build closed. This is build- and CI-level
reproducibility and tamper-evidence for how Capa is developed, not a
runtime or user-install guarantee (there are no runtime dependencies to
protect). It sits alongside SHA-pinned GitHub Actions, PyPI Trusted
Publishing (OIDC + PEP 740 attestations), and a pip-audit CVE gate on
the dev surface.
The Tier 1 supply-chain artefacts are all shipping today:
| Artefact | Command | Notes |
|---|---|---|
| Capability manifest | capa --manifest |
per-function caps + attributes |
| CycloneDX 1.6 SBOM | capa --cyclonedx |
capability metadata via properties[] |
| SPDX 2.3 SBOM | capa --spdx |
capability metadata via annotations[] |
| VEX | capa --vex |
per-function exploitability claims via @vex(...) |
| SLSA Build L1 | capa --provenance |
in-toto Statement v1 + Provenance v1.0 predicate |
| WIT spec | capa --wit |
one interface per capability the program touches |
| Wasm CM component | capa --wasm --component --output app.wasm |
WIT embedded, canonical ABI |
An empirical study
backs the capability-aware SBOM claim. Under closed-world semantics
Capa emits zero false clearances (0/48), against CodeQL (10/48,
the strongest real dataflow tool tested), Semgrep (12/48), and a
dependency-level SBOM (48/48). On positive attribution Capa ties
CodeQL at 38/48; it does not beat it. The reproducible artefacts live
in evaluation/empirical_study/.
Tier 2 (regulatory mapping) is complete:
docs/regulatory.md covers the EU CRA,
NIS2, DORA (cybersecurity articles), NIST SSDF, and OWASP SCVS
side-by-side; the article-by-article CRA mapping lives in
docs/cra.md.
What each of these artefacts actually guarantees, separating fail-closed from best-effort from trusted premise, is consolidated in the trust model.
The lambda_cap soundness theorems are mechanised in Agda,
no postulate remaining. Roughly 600 lines of self-contained
Agda (no agda-stdlib dependency) cover Progress, Preservation,
Capability Soundness, and a multi-step Manifest Completeness
theorem. CI typechecks the proofs on every push to
proofs/. The full roadmap is at
capa-language.com/roadmap.html.
The marketing + rendered learning pages live at
capa-language.com, source in the
capa-language-website
repo. The deeper Markdown documents below stay here, next to the
code they describe.
For a guided, beginner-friendly path there is a book,
Capa: The Capability-Typed Programming Language:
a hands-on introduction (~278 pages, free PDF) with didactic
chapters, exercises, three practical projects, and an appendix of
exercise solutions, written for compiler v1.12.0. Content is
licensed CC BY-NC 4.0.
| Doc | What it is |
|---|---|
capa-language.com |
landing page, with the case for the language |
capa-language.com/start.html |
install + first program + CLI |
capa-language.com/learn/ |
12-page tutorial sequence |
capa-language.com/manifest.html |
the manifest format + how to read it |
capa-language.com/roadmap.html |
status + what's planned |
docs/getting-started.md |
text version, plus LSP / editor setup |
docs/tutorial.md |
longer walkthrough |
docs/reference.md |
language reference (syntax + semantics) |
docs/stdlib.md |
runtime + library APIs |
docs/packages.md |
capa.toml + capa install + lockfile semantics |
docs/trust-model.md |
what is fail-closed vs best-effort vs trusted premise vs out-of-model |
docs/testing.md |
capa test: discovery, result contract, --both parity diff |
docs/positioning.md |
honest comparison vs Pony, Koka, Roc, Wasm CM, Zero |
docs/semantics.md |
lambda_cap calculus sketch + soundness theorems |
docs/cra.md + regulatory.md |
EU CRA + multi-jurisdiction regulatory mapping |
docs/migration.md |
porting Python code to Capa |
docs/paper-draft.md |
workshop-paper draft |
docs/cve_*.md and docs/demo-event-stream.md |
walkthroughs of real CVEs against Capa |
from capa import Lexer, Parser, analyze, transpile
source = open("program.capa", encoding="utf-8").read()
tokens = Lexer(source, filename="program.capa").lex()
module = Parser(tokens, source=source, filename="program.capa").parse_module()
result = analyze(module, source=source, filename="program.capa")
if not result.ok:
for e in result.errors:
print(e.format())
else:
code = transpile(module, filename="program.capa")
print(code)Questions, ideas, and showing off what you built with Capa all live in GitHub Discussions:
- Q&A for "the analyzer told me X and I don't understand why".
- Ideas for feature requests and "what if Capa had X".
- Show and tell for programs, manifests, integrations.
- Announcements for release notes.
Pull requests welcome; see CONTRIBUTING.md.
For security issues, please use the private vulnerability
reporting channel at
https://github.com/nelsonduarte/capa-language/security/advisories/new;
the disclosure flow is in SECURITY.md.
Dual-licensed under either MIT or
Apache-2.0 at your option. SPDX expression
MIT OR Apache-2.0 (the Rust idiom). See LICENSE
for the rationale and the contribution clause.