Expand description
capability-attr — Layer 2 (side-effect / capability safety) typed
capability declarations for Rust.
§The problem this solves
Rust’s safety model is binary: a function is safe or unsafe. Real
systems code spans a spectrum unsafe collapses into one signal —
reading a bounded buffer and writing an arbitrary raw pointer are both
just “unsafe” to the compiler, even though their risk profiles are
wildly different. #[capability(...)] gives that spectrum a
machine-readable, compiler-enforced structure: a function declares the
allocation/I/O/raw-pointer scope it needs, and this crate verifies the
function body doesn’t exceed it.
// COMPILE ERROR: body allocates on the heap, but only `alloc(none)` was
// declared.
#[capability(alloc(none), io(none), ptr(none))]
fn quiet_fn() {
let _buf: Vec<u8> = Vec::new();
}// Compiles clean — every operation in the body is within what was
// declared.
#[capability(alloc(heap), io(display), ptr(none))]
fn log_message(msg: &str) {
let buf: Vec<u8> = msg.bytes().collect();
println!("{}", buf.len());
}This is orthogonal to unsafe, not a replacement for it: unsafe
remains the programmer’s memory-safety promise (Layer 1, unchanged);
#[capability(...)] is the compiler’s side-effect-scope promise
(Layer 2, this crate). See the companion [sensitive-ifc] crate for
Layer 3 (semantic/policy safety — does this function leak a credential,
not just “does it do I/O”).
§Phased scope (this crate implements Phase 1 only, function-level only)
This is a direct, workspace-local implementation of Phase 1 from
docs/aisecurity/capability-rfc-updated.md, requiring no rustc
changes, no nightly compiler — a syn/quote-based proc-macro attribute
that (1) parses declared capabilities from the attribute’s arguments,
(2) walks the annotated function’s body with a syn::visit::Visit
walker (capability_core::inspector::BodyInspector) to detect actual
capability usage, and (3) emits a real compile_error!(...) when
detected capabilities exceed declared ones
(capability_core::check_subset / [error::emit_violation]).
Scoped to function items only, this pass. The RFC also describes
module/trait/impl/crate-level declarations with hierarchical narrowing
(a module’s declaration bounds every function inside it, a trait’s
declaration bounds every impl). That requires tracking capability
state across multiple macro-expansion sites, which a single
#[proc_macro_attribute] invocation cannot see by itself — it is real,
valuable, and explicitly deferred (see this workspace’s
spec/SPEC-00045-*.md), not attempted here.
- Phase 1 (this crate, stable Rust today): function-level declaration + body-inspection + subset-check, described above.
- Phase 2 (deferred, not built this pass): custom Clippy lints
(
declare_lint!) for cross-function capability-flow checking. The RFC frames this as “Phase 2” but it needs Clippy’s internal lint infrastructure — effectively nightly-adjacent in practice, not as “stable” as this phase despite the RFC’s own phase numbering. - Phase 3 (deferred, not built this pass): MIR-level analysis via
rustc_private— nightly-only, out of scope for this crate entirely.
§Vocabulary — see capability-core’s vocabulary module docs
The capability vocabulary implemented here (alloc/io/ptr) is
deliberately reduced from the RFC’s five categories and reshaped for
this project’s real target (git.git, a userspace CLI tool, not
embedded firmware) — see capability_core::vocabulary’s
module-level doc comment for the full reasoning, including why
register(...)/interrupt(...) are dropped entirely rather than
stubbed, and why io(process) exists (with no RFC equivalent) and
outranks io(network) in this crate’s risk ordering. That vocabulary
lives in capability-core now, shared with taint-generate — see
docs/adr/ADR-0005-generate-and-refactor.md.
§Honest scope statement
What this crate catches: a function declaring alloc(none) that
calls Vec::new/Box::new/etc.; a function declaring io(none) that
calls println!/touches std::fs/std::net/spawns a Command; a
function declaring ptr(none) that dereferences a raw pointer for a
read or write. All are real compile_error!(...)s produced by this
crate today — see tests/ui/fail/*.rs and their checked-in .stderr
snapshots for real compiler output, not a description.
What this crate does NOT catch: capability usage inside a function
called by the annotated function (cross-function flow — Phase 2/3);
usage hidden behind a macro that itself expands to an allocating/IO
call (AST-level detection only sees the macro invocation, not its
expansion, unless the macro name itself is recognized — see
capability_core::inspector); a raw pointer write’s actual address
range (Phase 1 has no PAC-style address verification, so every
detected write is conservatively classified ptr(write, any), never
ptr(write, bounded) — see capability_core::PtrBound).
Attribute Macros§
- capability
- Declare a function’s allocation/I/O/raw-pointer capability scope, and verify at compile time that the function body does not exceed it.