Every function declares what it is allowed to do.
Pythonic syntax, with the discipline checked statically: a function that does not list Fs or Net in its signature cannot touch the filesystem or the network. From those same signatures the compiler emits a machine-verifiable supply-chain SBOM by construction: a manifest that matches the code, not a scanner's approximation of it.
// the signature IS the contract
fun summarise(stdio: Stdio, fs: Fs, path: String) -> Result<Unit, IoError>
let body = fs.read(path)?
let first = match body.split("\n").get(0)
Some(line) -> line
None -> "(empty)"
stdio.println("first line: ${first}")
return Ok(())
What Capa is for
Every dependency you install today runs with the full authority of the program that pulled it in. A logging library can read your environment. A date parser can open a socket. Nothing in the language stops it: you read the README and you trust.
Capa puts the authority surface in the type system. A function that needs the filesystem says so in its signature; one that does not cannot reach for it, no matter what its body tries to do. The compiler reads the source and emits a manifest of which functions hold which authorities, automatically.
From a signature to an audit artefact
There is no global Stdio or ambient filesystem: you cannot reference what is not in the signature. The compiler reads the same source three ways.
A pure helper holds no authority
No capability in the signature means the compiler forbids any effect in the body. It is provably pure.
fun classify(score: Float) -> String
if score >= 9.5
return "Excellent"
if score >= 6.5
return "Pass"
return "Fail"
Reading files requires Fs
To open a file the function must name Fs in its signature. The capability is the contract, and it is passed in, never ambient.
fun summarise(stdio: Stdio, fs: Fs, path: String) -> Result<Unit, IoError>
let body = fs.read(path)?
stdio.println("read ${body.length()} bytes")
return Ok(())
The compiler emits the proof
capa --manifest lists every function with the capabilities it declared and the ones it can provably never reach. Same input feeds --cyclonedx, --spdx, --vex and --provenance.
{
"name": "summarise",
"declared_capabilities": ["Stdio", "Fs"],
"provably_excluded_capabilities": [
"Clock", "Db", "Env", "Net",
"Proc", "Random", "Serve", "Unsafe"
]
}
Narrow what a function holds, and prove where its data goes
Capabilities say which effects a function may use. Attenuation shrinks them on the way down; information-flow control tracks where labelled data is allowed to travel.
Capabilities can only be narrowed
fs.restrict_to("data/") hands a callee an Fs that only sees one directory, and the narrowing is monotonic by construction: you can never widen. The full story, including runtime path canonicalisation, is in Why Capa.
fun handler(fs: Fs)
let scoped = fs.restrict_to("data/")
load_records(scoped)
# scoped.restrict_to(".."): cannot widen
Where your data can go
Annotate a value @secret and the compiler propagates the label, then flags any secret that reaches a public sink: a warning by default, a hard error under @strict_ifc(). env.get is secret by default. The one auditable bridge is declassify(value, reason: "…"); every use is recorded in the SBOM as declassification_sites, so the disclosure ships with the manifest, not in a code-review thread. What the check does and does not catch.
@strict_ifc()
fun main(stdio: Stdio, env: Env)
let key = env.get("API_KEY").unwrap_or("") # @secret
stdio.println(key) # error: secret → public sink
Real programs written in Capa
Each lives in its own repository, declares its dependencies in capa.toml, and runs through capa install && capa --run …
capa_authgate
An HTTP auth-token service, and the clearest statement of what capability typing buys. The serving process declares exactly {Serve, Env, Clock}, and Serve is inbound-only: it has no method that dials out, so the process holds the authority to be reached and nothing that reaches out. The function that actually touches your token holds less than that. verify_token's manifest is empty, and authgate.capa is byte-for-byte the file that shipped at v0.1.0, before the HTTP front-end existed, because a verifier forced to take now as an Int was already the shape of a request handler.
It ships two compiler-rejected negatives, and the second is the honest one. A closure can capture a capability into a handler whose type is still Fun(Request) -> Response, and that compiles: the type system does not stop it, and the information-flow warning it raises is the same one the innocent version gets. What refuses it is the package capability ceiling one level up, capa --check-capabilities, which fails and names the offending authority, Net.
HTTP service · Serve · v0.2.1capa_claimdesk
An enterprise expense-reimbursement engine that exercises nearly the whole language at once: the claim lifecycle is a typestate, the payment authorization is a linear use-once token, the IBAN is held under information-flow control and reaches the audit ledger only through an audited declassify, and the policy engine dispatches over a List<Rule> via traits and generics. Runs byte-identically on both backends and ships its guarantees as proofs in the SBOM.
widest-coverage showcaseaudit-trail-reporter
AML compliance toolkit. Four detection rules (threshold, watchlist, structuring, velocity), four report sinks, attenuated read+write Fs split.
~1100 lines · 9 filescapa_paymentguard
Payment-security core. Information-flow control proves card data cannot leave unmasked: the only path to a sink is an audited declassify, recorded in the SBOM.
IFC coresbom-watch
SBOM operationaliser. Cross-references a CycloneDX SBOM against a CVE database and a policy file. CI-friendly exit code.
~700 lines · 5 filespolicy-eval
JSON-encoded policy-as-code engine. Tree-walk interpreter over a recursive Condition AST.
~700 lines · 5 filescapa_dataguard
Data-governance pipeline. Every path from PII to output runs through an audited declassify, and the compiler-rejected negative shows what happens when one does not.
IFC governancecapa_configbroker
Capability-secured config and secrets resolver. Secrets reach a log line only through an audited declassify, and its Net capability is narrowed to a single host, which currently bounds the host addressed rather than the host reached.
capability-scopedcapa_ci_pipeline
A CI/release orchestrator built as a multi-package product. Four untrusted third-party actions run as sandbox-confined typed foreign Wasm components; the pure core holds no authority; the whole product's capability surface composes into one SBOM. A compromised build action that silently gains Net is caught twice over: by the signed authority diff and by an organization exclusion policy.
supply-chain productUp and running in one line
pip install capa-language if you have Python 3.10+, a one-line installer for Linux, macOS and Windows, a self-contained binary with .sha256 verification, or a source install: everything lives on the Get started page.
Get startedStable, on SemVer, and on PyPI
1.27.0 is the current release, and Capa is now on the Python Package Index: pip install capa-language. It is published through PyPI Trusted Publishing (OIDC), with no stored API token, and carries a PEP 740 attestation. The distribution name is capa-language; the import package and the capa command are unchanged.
The first stable release shipped on 2026-06-03 as 1.0.0. Minor releases since added byte-reproducible SBOMs and attestations via SOURCE_DATE_EPOCH, the capa test runner, selective import, and a security-hardening line that closed further soundness and supply-chain findings.
1.19.0 (2026-07-20) makes two previously-succeeding builds fail on purpose. A capa.toml the compiler cannot parse is now a refusal (capa: broken capa.toml: …, exit 2) instead of a warning followed by a build that ignored the file, and the capa = ">=X.Y.Z" compiler floor is enforced for the first time after being parsed and ignored since 2026-05-19. What that means for your project. 1.18.0 attached SLSA build provenance to the release binaries and the install scripts; 1.18.1 fixed capa test under a released binary and closed a fail-open in capa install.
Surfaces listed in STABILITY.md follow SemVer. The full inventory is in the CHANGELOG and on the roadmap.
Where to go next
- Why Capa exists : the case for capability typing, against ambient authority, against the supply-chain status quo.
- Adopt Capa alongside your stack : no rewrite. Add one provable Capa piece for source-level SBOM and audit, gated in CI, with a yes/no check callable from Python, JS, Rust or C/C++.
- The capability-recall study : Capa measured head-to-head against a dependency SBOM, Semgrep and CodeQL on 25 pairs; 0 false-clearances against CodeQL's 10.
- Learn (14 chapters) : a guided tour from hello world to the audit artefacts.
- The book : Capa: The Capability-Typed Programming Language, a free PDF (~287 pages, ~2 MB) with exercises and three projects, downloaded straight from here. Source on GitHub.
- Language reference : full syntax and semantics.
- Roadmap : what is done, what is next, what is explicitly out of scope.