Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ members = [
"crates/capsec-tokio",
"crates/capsec-tests",
]
exclude = [
"crates/capsec-example-db",
]

[workspace.package]
version = "0.1.0"
Expand Down
31 changes: 31 additions & 0 deletions crates/capsec-core/src/cap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ impl<P: Permission> Cap<P> {
}
}

/// Creates a new capability token for use by `#[capsec::permission]` generated code.
///
/// This constructor is public so that derive macros can create `Cap<P>` for
/// user-defined permission types from external crates. The `SealProof` bound
/// ensures only types with a valid seal token can use this.
///
/// Do not call directly — use `#[capsec::permission]` instead.
#[doc(hidden)]
pub fn __capsec_new_derived() -> Self
where
P: Permission<__CapsecSeal = crate::__private::SealProof>,
{
Self {
_phantom: PhantomData,
_not_send: PhantomData,
}
}

/// Converts this capability into a [`SendCap`] that can cross thread boundaries.
///
/// This is an explicit opt-in — you're acknowledging that this capability
Expand Down Expand Up @@ -91,6 +109,19 @@ unsafe impl<P: Permission> Send for SendCap<P> {}
unsafe impl<P: Permission> Sync for SendCap<P> {}

impl<P: Permission> SendCap<P> {
/// Creates a new send-capable token for use by `#[capsec::permission]` generated code.
///
/// Do not call directly — use `#[capsec::permission]` instead.
#[doc(hidden)]
pub fn __capsec_new_send_derived() -> Self
where
P: Permission<__CapsecSeal = crate::__private::SealProof>,
{
Self {
_phantom: PhantomData,
}
}

/// Returns a new `Cap<P>` from this send-capable token.
///
/// This creates a fresh `Cap` (not a reference cast) — safe because
Expand Down
7 changes: 6 additions & 1 deletion crates/capsec-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! This crate provides the foundational types that the rest of the `capsec`
//! ecosystem builds on:
//!
//! - [`Permission`](permission::Permission) — sealed marker trait for capability categories
//! - [`Permission`](permission::Permission) — marker trait for capability categories
//! - [`Cap<P>`](cap::Cap) — zero-sized proof token that the holder has permission `P`
//! - [`Has<P>`](has::Has) — trait for checking and composing capabilities
//! - [`CapRoot`](root::CapRoot) — the singleton root of all capability grants
Expand Down Expand Up @@ -45,3 +45,8 @@ pub mod error;
pub mod has;
pub mod permission;
pub mod root;

/// Re-export of the seal token module for use by `#[capsec::permission]` macro.
/// Do not use directly.
#[doc(hidden)]
pub use permission::__private;
110 changes: 77 additions & 33 deletions crates/capsec-core/src/permission.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! The sealed [`Permission`] trait and all built-in permission types.
//! The [`Permission`] trait and all built-in permission types.
//!
//! Permissions are zero-sized marker types that encode what kind of I/O a
//! capability token grants. They form a sealed hierarchy — external crates
//! cannot define new permissions, ensuring the set is auditable.
//! capability token grants. Built-in permissions cover filesystem, network,
//! environment, and process operations. Library authors can define custom
//! permissions using `#[capsec::permission]`.
//!
//! # Built-in permissions
//!
Expand All @@ -19,6 +20,18 @@
//! | [`Spawn`] | Process | Execute subprocesses |
//! | [`Ambient`] | Everything | Full ambient authority — the "god token" |
//!
//! # Custom permissions
//!
//! Use `#[capsec::permission]` to define domain-specific permissions:
//!
//! ```rust,ignore
//! #[capsec::permission]
//! pub struct DbRead;
//!
//! #[capsec::permission(subsumes = [DbRead])]
//! pub struct DbAll;
//! ```
//!
//! # Tuples
//!
//! Two permissions can be bundled via a tuple: `(FsRead, NetConnect)` is itself
Expand All @@ -32,12 +45,23 @@
//! meaning a `Cap<FsAll>` can be used anywhere a `Cap<FsRead>` is required.
//! [`Ambient`] subsumes everything.

/// Marker trait for all capability permissions. Sealed to prevent external implementation.
/// Marker trait for all capability permissions.
///
/// Every permission type is a zero-sized struct that implements this trait.
/// The sealed pattern ensures that only the permissions defined in this crate
/// can be used as capability tokens — external crates cannot forge new permissions.
pub trait Permission: sealed::Sealed + 'static {}
/// Built-in permissions are defined in this module. Custom permissions can be
/// defined using the `#[capsec::permission]` derive macro, which generates the
/// required seal token.
///
/// # Direct implementation
///
/// Do not implement this trait manually. Use `#[capsec::permission]` instead.
/// The `__CapsecSeal` associated type is `#[doc(hidden)]` and may change
/// without notice.
pub trait Permission: 'static {
/// Seal token preventing manual implementation. Do not use directly.
#[doc(hidden)]
type __CapsecSeal: __private::SealToken;
}

// Filesystem

Expand Down Expand Up @@ -84,20 +108,42 @@ pub struct Ambient;

// Permission impls

impl Permission for FsRead {}
impl Permission for FsWrite {}
impl Permission for FsAll {}
impl Permission for NetConnect {}
impl Permission for NetBind {}
impl Permission for NetAll {}
impl Permission for EnvRead {}
impl Permission for EnvWrite {}
impl Permission for Spawn {}
impl Permission for Ambient {}
impl Permission for FsRead {
type __CapsecSeal = __private::SealProof;
}
impl Permission for FsWrite {
type __CapsecSeal = __private::SealProof;
}
impl Permission for FsAll {
type __CapsecSeal = __private::SealProof;
}
impl Permission for NetConnect {
type __CapsecSeal = __private::SealProof;
}
impl Permission for NetBind {
type __CapsecSeal = __private::SealProof;
}
impl Permission for NetAll {
type __CapsecSeal = __private::SealProof;
}
impl Permission for EnvRead {
type __CapsecSeal = __private::SealProof;
}
impl Permission for EnvWrite {
type __CapsecSeal = __private::SealProof;
}
impl Permission for Spawn {
type __CapsecSeal = __private::SealProof;
}
impl Permission for Ambient {
type __CapsecSeal = __private::SealProof;
}

// Tuple permissions

impl<A: Permission, B: Permission> Permission for (A, B) {}
impl<A: Permission, B: Permission> Permission for (A, B) {
type __CapsecSeal = __private::SealProof;
}

// Subsumption

Expand All @@ -114,21 +160,19 @@ impl Subsumes<NetConnect> for NetAll {}
impl Subsumes<NetBind> for NetAll {}
impl<P: Permission> Subsumes<P> for Ambient {}

// Sealed

mod sealed {
pub trait Sealed {}
impl Sealed for super::FsRead {}
impl Sealed for super::FsWrite {}
impl Sealed for super::FsAll {}
impl Sealed for super::NetConnect {}
impl Sealed for super::NetBind {}
impl Sealed for super::NetAll {}
impl Sealed for super::EnvRead {}
impl Sealed for super::EnvWrite {}
impl Sealed for super::Spawn {}
impl Sealed for super::Ambient {}
impl<A: Sealed, B: Sealed> Sealed for (A, B) {}
// Seal token — prevents manual Permission implementation.
// The #[capsec::permission] macro generates the correct seal.
// Users could write this by hand (it's #[doc(hidden)], not private),
// but that's equivalent to writing `unsafe` — they own the consequences.

#[doc(hidden)]
pub mod __private {
/// Proof token that a permission was registered via the capsec derive macro.
pub struct SealProof;

/// Trait bound for the seal associated type.
pub trait SealToken {}
impl SealToken for SealProof {}
}

#[cfg(test)]
Expand Down
16 changes: 16 additions & 0 deletions crates/capsec-example-db/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "capsec-example-db"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Example: user-defined database permissions with capsec"
publish = false

[dependencies]
capsec = { path = "../capsec" }
capsec-core = { path = "../capsec-core" }
duckdb = { version = "1", features = ["bundled"] }

[dev-dependencies]
capsec-core = { path = "../capsec-core" }
110 changes: 110 additions & 0 deletions crates/capsec-example-db/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# capsec-example-db

A real-world example of capsec's user-defined permissions, gating DuckDB operations at compile time.

## What this demonstrates

Library authors can define domain-specific permissions with `#[capsec::permission]` and enforce them through the type system — zero runtime cost, zero chance of forgetting a check.

```rust
#[capsec::permission]
pub struct DbRead;

#[capsec::permission]
pub struct DbWrite;

#[capsec::permission(subsumes = [DbRead, DbWrite])]
pub struct DbAll;
```

Functions declare what they need:

```rust
// This function literally cannot write to the database.
// The compiler rejects it — not a runtime check.
fn run_analytics(db: &CapDb, cap: &impl Has<DbRead>) {
db.query("SELECT * FROM players", cap)?;

// COMPILE ERROR: Has<DbWrite> is not implemented for impl Has<DbRead>
// db.execute("DELETE FROM players", cap);
}
```

## Building and running

This crate is **excluded from the default workspace** because it depends on `duckdb` with the `bundled` feature, which compiles the entire DuckDB C++ engine from source (~500k lines of C++). This keeps `cargo build --workspace`, `cargo test --workspace`, and `cargo clippy --workspace` fast for day-to-day development.

To build, test, or run this crate, target it explicitly with `-p`:

```bash
# Run the example
cargo run -p capsec-example-db --example duck_db_app

# Run the tests
cargo test -p capsec-example-db
```

The first build will be slow (DuckDB compilation). Subsequent builds use the cached artifacts and are fast.

Output:

```
=== DuckDB + capsec: Compile-Time Capability Gating ===
Schema created.

--- Ingestion (write-only) ---
inserted: Alice (score: 950)
inserted: Bob (score: 870)
...

--- Analytics (read-only) ---
ID Name Score
----- --------------- ------
5 Eve 1100
3 Charlie 1020
...

--- Admin (full access) ---
Players below 900: 2
Removed 2 player(s) below threshold
Remaining players: 3
```

## Architecture

| Module | Capability | Can read? | Can write? |
|--------|-----------|-----------|------------|
| `ingest_scores` | `DbWrite` | No | Yes |
| `run_analytics` | `DbRead` | Yes | No |
| `admin_cleanup` | `DbAll` | Yes | Yes |

`DbAll` subsumes `DbRead` and `DbWrite` — extract a `Cap<DbAll>` and it satisfies both `Has<DbRead>` and `Has<DbWrite>` bounds.

## Key patterns shown

- **`#[capsec::permission]`** — define custom permissions in your own crate
- **`subsumes = [...]`** — build permission hierarchies
- **`Cap<DbAll>` subsumption** — extract the concrete token to use both read and write APIs
- **`#[capsec::context]`** — bundle custom permissions into a context struct
- **`#[capsec::requires]`** — annotate functions with custom permission requirements
- **Mixed built-in + custom** — combine `FsRead` and `DbRead` in one context

## Connection to auths

These compile-time permissions mirror the runtime capabilities used by [auths](https://github.com/bordumb/auths). The same vocabulary spans both layers:

| capsec (compile-time) | auths (runtime) |
|----------------------|-----------------|
| `Cap<DbRead>` | `HasCapability("db:read")` |
| Type system: compiles/doesn't | Policy evaluation: Allow/Deny |
| `Subsumes<DbRead> for DbAll` | Delegation narrowing |

capsec prevents the code from exceeding its authority. auths prevents unauthorized callers from reaching the code. Together: defense in depth.

## Tests

```bash
cargo test -p capsec-example-db
```

> **Note:** This crate is in the workspace `exclude` list, not `members`. Use `-p capsec-example-db` explicitly — `--workspace` will not include it.
Loading
Loading