A function's signature names the capabilities it is handed.
Pythonic syntax, with the discipline checked statically: the compiler refuses a call on Fs or Net in a function that has none in scope. From those same signatures the compiler emits a supply-chain SBOM: the capabilities each function holds, derived from the code rather than written beside 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 works on an Fs value derived from the one main received: no constructor, literal, global or import produces one. 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: a call on a capability that is not in scope is refused. The compiler reads the same source three ways.
A 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 >= 6.5
return "Pass"
return "Fail"
Reading files requires Fs
This function opens a file, so it names 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 record
capa --manifest lists every function with the capabilities it declared and the ones the compiler found no path to. The same per-function sets feed --cyclonedx and --spdx.
{
"name": "summarise",
"declared_capabilities": ["Stdio", "Fs"],
"provably_excluded_capabilities": [
"Clock", "Db", "Env", "Net",
"Proc", "Random", "Serve", "Unsafe"
]
}
Narrow what a function holds, and check 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 a secret it finds reaching 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 Capa manifest (each function's declassifications list), so the deliberate 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 through an audited declassify, and the policy engine dispatches over a List<Rule> via traits and generics. Runs byte-identically on both backends and records what each function holds, and each audited disclosure, in its manifest.
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 reports a flow it detects from card data to a sink: a build error in process (@strict_ifc) and a warning elsewhere. Each sanctioned disclosure is an audited declassify, recorded in the manifest.
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. Each deliberate disclosure of PII goes through an audited declassify, and a compiler-rejected negative shows the check refusing a direct flow under @strict_ifc.
IFC governancecapa_configbroker
Capability-secured config and secrets resolver. A secret's deliberate disclosure to a log line goes 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 core package holds no capability; the whole product's capability surface composes into one SBOM. A compromised build action that silently gains Net is caught twice over: by the signable 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.32.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 SOURCE_DATE_EPOCH support, so SBOMs and attestations can be rebuilt and diffed, 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 auditable 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 (288 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.