capsec/
├── crates/
│ ├── capsec-core/ # Zero-cost capability tokens, permission traits, Has<P>
│ ├── capsec-macro/ # #[requires], #[deny], #[main], #[context] proc macros
│ ├── capsec-std/ # Capability-gated wrappers for std::fs, std::net, etc.
│ ├── capsec/ # Facade crate — re-exports everything, owns examples
│ ├── cargo-capsec/ # Static audit CLI tool
│ └── capsec-tests/ # Adversarial security tests
├── docs/ # Additional documentation
├── .capsec.toml # Default audit configuration
└── rust-toolchain.toml # Pinned Rust version (compile-fail tests depend on this)
Key files:
| File | Purpose |
|---|---|
capsec-core/src/permission.rs |
Permission types (FsRead, NetConnect, etc.) |
capsec-core/src/has.rs |
Has<P> trait + subsumption impls |
capsec-core/src/cap.rs |
Cap<P> zero-sized token |
capsec-std/src/fs.rs |
Gated filesystem operations |
cargo-capsec/src/authorities.rs |
Audit knowledge base — patterns, categories, risk levels |
cargo-capsec/src/detector.rs |
Matching engine that produces findings |
cargo-capsec/src/parser.rs |
Rust source parser (syn-based) |
The audit tool's knowledge base lives in crates/cargo-capsec/src/authorities.rs. Each entry maps a call pattern to a category, risk level, and description.
There are two matching strategies:
AuthorityPattern::Path — for qualified function calls like std::fs::read or File::open:
Authority {
pattern: AuthorityPattern::Path(&["std", "fs", "read"]),
category: Category::Fs,
subcategory: "read",
risk: Risk::Medium,
description: "Read arbitrary file contents",
}The detector matches by suffix — &["fs", "read"] will match std::fs::read, use std::fs::read; read(...), and aliased imports.
AuthorityPattern::MethodWithContext — for method calls that are only meaningful in context (e.g., .output() is only a process call if Command::new is in the same function):
Authority {
pattern: AuthorityPattern::MethodWithContext {
method: "output",
requires_path: &["Command", "new"],
},
category: Category::Process,
subcategory: "spawn",
risk: Risk::Critical,
description: "Execute subprocess and capture output",
}Add your Authority struct to the vec![] in build_registry(). Group it with related entries (filesystem patterns together, network patterns together, etc.).
| Level | When to use |
|---|---|
Low |
Read-only metadata, unlikely to leak secrets (fs::metadata, env::current_dir) |
Medium |
Can read data or create resources (fs::read, env::var, File::open) |
High |
Can write, delete, or open network connections (fs::write, TcpStream::connect) |
Critical |
Can destroy data or execute arbitrary code (remove_dir_all, Command::new) |
Add a test fixture in crates/capsec-tests/tests/audit_evasion.rs that exercises your new pattern. The test should:
- Create a temporary
.rsfile with the call you want to detect - Run the parser and detector against it
- Assert the finding has the correct category, risk, and description
#[test]
fn detects_your_new_pattern() {
let source = r#"
fn example() {
some_crate::dangerous_call();
}
"#;
let findings = scan_source(source);
assert!(findings.iter().any(|f| f.description == "Your description"));
}cargo test -p cargo-capsec
cargo test -p capsec-testsAll PRs must pass:
cargo test --workspace # All unit + integration tests
cargo clippy --workspace # No warnings
cargo fmt --check # Formattedcapsec uses trybuild to prove security guarantees at the compiler level. These tests live in two places:
crates/capsec/tests/compile_fail/— API-level guarantees (wrong cap rejected, cap is !Send, etc.)crates/capsec-tests/tests/compile_fail/— adversarial attacks (forgery, escalation, sealed traits)
Each test is a .rs file that must fail to compile, paired with a .stderr file containing the exact expected error output.
- Create a
.rsfile in the appropriatecompile_fail/directory:
/// Description of what this test proves.
use capsec::prelude::*;
fn main() {
// Code that MUST NOT compile
}- Generate the
.stderrsnapshot:
TRYBUILD=overwrite cargo test -p capsec --test compile_tests-
Review the
.stderr— confirm it fails for the right reason. -
Commit both the
.rsand.stderrfiles.
Important: The .stderr snapshots are tied to the exact rustc version in rust-toolchain.toml. Bumping the toolchain will require regenerating them. See docs/contributing/compile-fail-tests.md for details.
- One concern per PR. A new authority pattern, a bug fix, or a refactor — not all three.
- Include tests. New authority patterns need integration tests. New type-system features need compile-fail tests.
- Run
cargo capsec auditagainst the repo itself before submitting — capsec dogfoods its own tool. - Keep the security model intact.
Cap<P>must remain unforgeable and!Send.Permissionmust remain sealed.Cap::new()must remainpub(crate). Any change that weakens these guarantees needs discussion in an issue first. #[must_use]convention. Any new function or method returningResult<T, CapSecError>,Cap<P>,SendCap<P>, orCapRootmust have#[must_use]. Exception: if the return type itself already carries#[must_use](e.g.,Cap<P>does), a bare#[must_use]on the function is redundant and clippy will flag it — skip it in that case. The goal: discarding a capability check or proof token should always produce a compiler warning.- Update docs if you change public API. The facade crate's
lib.rsdoc comments and crate READMEs should stay current.
capsec provides four macros that work together:
| Macro | Purpose |
|---|---|
#[capsec::context] |
Generates Has<P> and CapProvider<P> impls on a struct, turning it into a capability context |
#[capsec::main] |
Injects CapRoot creation into a function entry point |
#[capsec::requires] |
Validates that a function's parameters satisfy declared permissions |
#[capsec::deny] |
Marks a function as capability-free; violations are promoted to critical by the audit tool |
When developing macros in capsec-macro:
- All generated code uses fully qualified
capsec_core::*paths (notcapsec::*) - Permission type validation must stay in sync with
capsec-core/src/permission.rs - The
resolve.rsmodule maps shorthand paths (fs::read) to full types - Add compile-fail tests in
capsec/tests/compile_fail/for error cases - Add runtime tests in
capsec-tests/tests/type_system.rsfor happy paths