Reference

Language reference

Full specification of the syntax and semantics of Capa. For a guided introduction see the Learn track; for the built-in APIs see the standard library.

1Lexical structure

Encoding. UTF-8 required. Identifiers may contain any Unicode letter, digit and _, but must start with a letter or _. Indentation is significant, à la Python: implicit INDENT / DEDENT / NEWLINE tokens; inside ( [ { a newline is suppressed. A line beginning with . continues the previous one (method chaining).

comments
// Line comment
/// Doc comment (attaches to the next declaration)
/** Block doc comment (same role) */

Keywords: fun let var if then elif else match while for in break continue return import as const type trait impl capability pub true false and or not consume self Self extern typestate linear become. Reserved for future use: async await yield defer where mut.

TypeExamples
Integer42, 1_000_000, 0xff, 0o755, 0b1010
Float3.14, 2.0, 1e10
String"hello", "a\nb", "x = ${x}"
Char / Bool'a', '\n' · true, false
List / Tuple[1, 2, 3] · (1, "a"), ()
Rangea..b (exclusive), a..=b (inclusive)

Interpolation: ${expr} inside a string is parsed as a Capa expression; $$ is a literal $. Nested string literals inside interpolation are not supported.

2Type system

Primitives: Int, Float, String, Bool, Char, Unit. Compound: List<T>, tuples, Fun(T1,T2)->Ret, Map<K,V>, Set<T>, Option<T>, Result<T,E>.

user types
type Person { name: String, age: Int }

type Shape =
    Circle(Float)
    Rectangle(Float, Float)
    Square(Float)

fun first<T>(xs: List<T>) -> Option<T>
    return xs.first()

Variants without a payload are constants, used without (). Ok, Err, Some, None are reserved. Generics use local inference, so the caller rarely supplies explicit args. Cross-statement inference: let xs = [] is List<TyVar> and the first use pins the type. TyUnknown and TyVar are compatible with any type.

Linear types

A linear type is a must-consume struct: a value of it has to be used (consumed) exactly once. It cannot be silently dropped or aliased into two live copies, the same ownership discipline capabilities enjoy, applied to ordinary data. Use it for a resource whose closing or handing-off must not be forgotten (a handle, a receipt, a one-shot token).

linear type Ticket { id: Int }

Typestates

A typestate declaration names a type together with the finite set of named states a value of it moves through, one per indented line; the optional { … } block is the data every state carries. A transition is written become(value, State), where the target is a bare state name. This lets the type track, for example, a claim lifecycle (Created → Connected → Closed) so an operation valid only in one state cannot be reached from another.

typestate Socket { fd: Int }
    Created
    Connected
    Closed

3Statements

bindings & control flow
let name = "Ana"          # immutable, inferred
let age: Int = 30        # explicit type
var counter = 0           # mutable
let (a, b) = pair()        # tuple destructuring

if cond
    body1
elif cond2
    body2
else
    body3

for x in iter
    body

match scrutinee
    pat1 -> body1
    pat2 -> body2

Assignment is only valid for var. Any expression can stand as a statement (its value is discarded).

4Expressions

Operator precedence (decreasing): call / index / field . () []; unary not -; * / %; + -; range .. ..=; comparison; and; or; try ?.

expressions
# if as expression: `then` is the discriminator
let cat = if cond then e1 else e2

# match expression, inline form
let r = match scrutinee { pat1 -> e1, pat2 -> e2 }

# lambdas
fun (x: Int) -> Int => x * 2

# the ? operator propagates Err
let a = fs.read("a")?

5Pattern matching

PatternSyntaxMatches
Wildcard / Identifier_ · xAny value · binds to x
Literal42, "x", trueEquality
VariantNone · Some(x)Singleton · match + bind
Struct / TuplePerson { name, age } · (a, b)Match + bind
Or-patterna | b | cAny alternative

Or-patterns may bind, provided every alternative binds the same names with compatible types (Add(n) | Sub(n) -> n). Guards: x if x > 0 -> …. Exhaustiveness is checked: every variant of a sum type, or a catch-all _.

6Capabilities

Capabilities are primitive types representing access to system resources (Stdio, Fs, Net, Env, Proc, Clock, Random, Db, Unsafe). They are only accessible via function parameters; there are no global instances. The discipline has three layers:

  • Structural. Capabilities cannot appear in struct fields, variant payloads, return types, constants, bindings, generic args or tuples, only parameters. One relaxation: cap-bearing structs implementing a user-defined capability may hold built-in caps as fields.
  • Flow. No aliasing (the same cap cannot fill two arg slots of one call); capability parameters must be used (or prefixed _).
  • Linearity. consume marks ownership transfer; consumed variables are tracked across fork/merge in if and match.

Attenuation: every built-in capability has an attenuator returning a fresh, narrower instance:

CapabilityAttenuatorSemantics
Netrestrict_to(host)Allowed host set, monotonic intersection
Fsrestrict_to(prefix)Allowed path prefix, monotonic
Envrestrict_to_keys(keys)Allowed key set, monotonic intersection
Clockrestrict_to_after(t)Active only after timestamp
Randomwith_seed(seed)Deterministic sequence

Foreign components

An extern component declaration types an external WebAssembly Component-Model component and the methods called across the boundary. component and from are contextual (they stay usable as ordinary identifiers); only extern is reserved. Each call is capability-checked at the boundary exactly like an ordinary function, and at runtime the Wasm sandbox confines the component to precisely the capability set its signatures declare. Unsafe cannot be a foreign-component parameter (the compiler rejects it at --check): the Python-only FFI escape hatch can never reach a sandboxed component. A resource ceiling (CPU and memory) is applied per call with --foreign-fuel / --foreign-memory-cap.

extern component Bureau from "vendor/bureau.wasm"
    fun submit(net: Net, payload: String) -> String

7Information-flow control

A two-point lattice: @public (default) below @secret. A label attaches to a type expression, so it can appear on parameters, bindings, return types and struct fields. A value's label is the join of every label flowing into it (operators, interpolation, field reads, indexing, ?). env.get is secret by default; fs.read is intentionally not.

CapabilitySink methods
Stdioprint, println, eprintln
Net / Fs / Dbget, post · write · exec, query

Warn-then-enforce: a violation is a warning by default, a hard error under @strict_ifc() (which also catches implicit flows). The single auditable bridge is declassify(value, reason: "…"), recorded in the SBOM. Labels cannot be shed by repackaging into aggregates or mutable containers. The analysis is intra-procedural with whole-aggregate granularity.

8Imports

import forms
import util                       # sibling: ./util.capa
import sinks.csv_sink             # nested
import util as U                  # alias
import util (greet as hi, Color)  # selective + rename

Visibility

Top-level items are private by default. Mark a function, constant, type, trait or capability pub to expose it to importers; anything without pub is callable only from inside the same file. Selective import import foo (a, b as c) brings only the listed pub symbols into scope, the hygienic way to resolve a name collision between two dependencies (e.g. capa_csv and capa_cli both exporting parse).

Module search paths

The loader resolves import x.y in order: importing-file directory, CAPA_PATH entries, ./vendor/, the parent of every path= entry in capa.toml, ./libraries/, then the root-file directory. The importer-relative path always wins. Set CAPA_PATH (separated by ; on Windows, : elsewhere) to add roots. Python interop uses py_import / py_invoke, both gated by Unsafe.

9The main program

The entry point is a function called main that may take one or more capabilities; the runtime instantiates them at boot. If main returns Result<(), E>, an Err causes a non-zero exit code.

fun main(stdio: Stdio, fs: Fs, env: Env)
    let argv = env.args()
    stdio.println("received ${argv.length()} arguments")

10Attributes

AttributeKeysRole
@securitycve, cwe, severity, fixed_inLink a function to a known security history.
@deprecatedreason, since, use, removed_inMark an API as superseded.
@auditeddate, by, scope, notesRecord a manual security audit.
@vexcve, status, justification, detailPer-function CycloneDX VEX exploitability claim.
@strict_ifc(none)Opt this function into fail-closed information-flow checking: a secret-reaches-public-sink flow becomes a hard error instead of a warning, and implicit flows are caught.
@constant_time(none)Require this function to be constant-time: the analyzer rejects any control-flow decision or index that depends on a @secret value (CWE-208 timing leaks).

@vex status accepts the CycloneDX vocabulary (not_affected, affected, fixed, in_triage, false_positive, exploitable, resolved, resolved_with_pedigree).

11Compiler CLI

FlagOutput
repl / testREPL with caps pre-bound · discover and run tests/test_*.capa.
--run / --transpile / --watchExecute · print generated Python · re-run on change.
--wasm [--component]Compile through CIR to WebAssembly; wrap as a Component Model component.
--wasi experimentalEmit a component for stock WASI Preview 2 runtimes (for example wasmtime) instead of the dedicated capa:host. Gives mitigation Levels 1 and 2 but not Level 3; dynamic paths need an operator grant --preopen <dir>. See the roadmap.
--manifestCapa-native JSON: per-function caps, attributes, Unsafe crossings, args_flow.
--cyclonedx / --spdxCycloneDX 1.5 / SPDX 2.3 SBOM with per-function capability metadata.
--vex / --provenanceCycloneDX VEX per @vex · SLSA L1 provenance bound to the source SHA-256.

Supply-chain and governance

These operate over a project (they need a capa.toml root) and are byte-reproducible; the signable ones wrap their output in a content_integrity envelope carrying a sha256 digest, and the compiler holds no keys.

FlagOutput
--manifest-digestThe canonical, content-addressable manifest: the same data as --manifest, serialised key-sorted and wrapped with a sha256 content_integrity envelope. Byte-reproducible across runs and machines.
--compose-sbomThe composed capability SBOM for the whole product: attribute each function to its owning package, walk the dependency DAG, and roll the capability surface up bottom-up. An unanalyzable subtree composes as a visible authority-UNKNOWN element that dominates the join.
--check-capabilitiesCI gate. Compose the product SBOM and verify every package against its declared capa.toml [capabilities] ceiling; exit non-zero on a violation, naming the offending capability and the dependency edge. An authority-UNKNOWN subtree fails closed unless allow_unknown = true.
--check-policiesCI gate. Compose the product SBOM and verify it against the organization compliance policies in the product-level capa-policy.toml; exit non-zero on any policy failure with a per-violation message.
--conformance-reportEmit the signed conformance report: evaluate every declared capa-policy.toml policy over the composed capability graph and record the per-policy pass/fail results in a content_integrity envelope.
--capability-diff <old> <new>Emit a signed authority changelog between two capability artifacts: per exported function and for the product, which capabilities were gained (widening) or lost (narrowing), and any operator-grant / authority-unknown transition. Functions match by stable identity, so a line move produces no entry. Takes no .capa file.
--fail-on-wideningCI gate for --capability-diff. Exit non-zero when the changelog contains any widening (authority gained, a guarantee lost, or an operator grant added) or an authority-UNKNOWN transition. A pure narrowing or no-change exits 0.

Sandbox and resource ceilings

FlagOutput
--allow-host <host>[:get|:post]With --wasi, an operator-declared Net grant letting the component reach <host> (the Net analogue of --preopen), unblocking a dynamic, argv-derived URL the compiler otherwise rejects fail-closed. Repeatable; :get grants read only, :post write only, no suffix both. Recorded in the SBOM as operator-declared.
--wasm-memory-cap <pages>With --wasm, cap the emitted linear memory at this many 64 KiB pages; the allocator then traps at a deterministic ceiling instead of a host-dependent OOM point. Default 256 pages (16 MiB); 0 skips the cap.
--foreign-fuel <N>With --wasm --run, bound the CPU an untrusted foreign component may burn per call to N fuel units (~1 per Wasm instruction). A spin then traps cleanly instead of hanging the host. Default 1e9; 0 skips the bound.
--foreign-memory-cap <MiB>With --wasm --run, cap the linear memory an untrusted foreign component may grow to, in MiB. A runaway self-allocation is refused instead of OOM-ing the host. Default 256 MiB; 0 skips the bound.

12Differences from Python

CapaPython
Capabilities required for I/OGlobals such as print, open
Types checked at compile timeDuck typing
Exhaustive match checkedRuntime match, no exhaustiveness
Mutation only with var / consumeEverything mutable
Manifest / SBOM emitted by compilerManual via external tools

13Known limitations

  • String literals are single-line (use \n); nested string literals inside interpolation are unsupported.
  • No asynchronous I/O operations.
  • if/match in block-body lambdas need => before the indented block.
  • Multi-line match inside parentheses requires the inline { } form.

For the full roadmap see the roadmap page and TODO.md.