diff --git a/docs/specs/object-capabilities.md b/docs/specs/object-capabilities.md new file mode 100644 index 0000000..3f31c8a --- /dev/null +++ b/docs/specs/object-capabilities.md @@ -0,0 +1,923 @@ +# Handle-Backed Object Capabilities + +Status: Proposed +Target release: `0.3.0` +Tracking issue: [#62](https://github.com/auths-dev/capsec/issues/62) + +## Summary + +Add first-class, handle-backed object capabilities for filesystem and network +access across `capsec-std` and `capsec-tokio`. + +The existing `Cap
` remains the authority to acquire resources of permission +class `P`. The new object-capability layer turns that broad authority into +unforgeable values representing specific filesystem namespaces, files, network +endpoint sets, listeners, and connected sockets. + +Filesystem operations are performed relative to open directory handles. +Network operations are performed through immutable pools of authorized socket +addresses. Neither API performs an authorization check followed by an ambient +path or address operation. + +The implementation uses `cap-std` and `cap-net-ext` as the OS-facing backend. +Capsec wraps those types and never exposes the backend handles or pools through +safe public APIs. + +## Goals + +- Bind filesystem authority to open directory and file handles. +- Bind network authority to immutable sets of concrete socket addresses. +- Prevent path traversal, symlink escape, check/open races, and DNS rebinding. +- Preserve compile-time distinctions between read, write, connect, and bind + permissions. +- Provide equivalent synchronous and Tokio APIs. +- Support delegation and attenuation without recovering an ambient `Cap
`. +- Cover every filesystem and network operation currently exposed by + `capsec-std` and `capsec-tokio`. +- Preserve existing path-plus-token APIs for compatibility while making the + object-capability APIs the recommended interface. +- Make direct ambient acquisition and backend escape visible to + `cargo capsec audit`. + +## Non-goals + +- This specification does not add OS sandboxing to code that directly calls + `std`, `tokio`, FFI, inline assembly, or raw syscalls. `cargo capsec audit` + remains responsible for detecting those bypasses. +- Environment variables and process spawning are not filesystem or network + objects and have no portable handle-relative equivalents. They retain their + existing scoped-token APIs. +- The API does not expose raw file descriptors, raw handles, `cap_std::fs::Dir`, + or `cap_std::net::Pool`. +- The API does not make revocation retroactive. Revoking the authority used to + acquire an object does not invalidate an already-issued OS handle. + +## Terminology + +- **Class capability**: `Cap
` or another `CapProvider
`. It authorizes + acquisition of objects in permission class `P`. +- **Object capability**: an unforgeable value holding an OS handle or an + immutable authority set. +- **Bootstrap**: the explicit conversion of ambient process authority into the + first object capability. +- **Attenuation**: deriving a capability with equal or narrower authority. +- **Relative path**: a path resolved from a directory capability, never from + the process working directory. + +## Security model + +The following invariants are mandatory: + +1. Safe code cannot construct an object capability without an existing + `CapProvider
` or another object capability with sufficient authority. +2. Safe code cannot extract a `Cap
` from an object capability.
+3. Safe code cannot extract the backend directory, pool, file descriptor,
+ socket, or raw OS handle.
+4. `DirCap ` continues
+ to control authority acquisition; acquired OS objects are transferable.
+16. No safe conversion broadens a permission. Narrowing conversions consume
+ the source capability unless the API explicitly duplicates the underlying
+ handle.
+
+The confinement guarantee begins at bootstrap. `open_ambient` and network-pool
+construction are the only APIs allowed to consult process-global namespaces.
+Applications must call them in trusted initialization code and delegate only
+the resulting object capabilities.
+
+## Developer UX
+
+### Filesystem
+
+```rust
+use capsec::prelude::*;
+use capsec::fs::DirCap;
+
+fn main() -> Result<(), CapSecError> {
+ let root = capsec::root();
+
+ // Trusted bootstrap: ambient path becomes an open directory authority.
+ let app = DirCap:: |
+| | |
+| +-------------------+-------------------+ |
+| v v v |
+| open_ambient() resolve endpoints bind addresses |
++-------------|-------------------|-------------------|----------------+
+ v v v
+ +-------------+ +----------------+ +----------------+
+ | DirCap | | ConnectPoolCap | | BindPoolCap |
+ | open handle | | immutable pool | | immutable pool |
+ +------+------+ +-------+--------+ +--------+-------+
+ | | |
+ relative operations | |
+ v v v
+ +-------------+ +-------------+ +-------------+
+ | FileCap | | TcpStream | | TcpListener |
+ | OS handle | | OS socket | | OS socket |
+ +-------------+ +-------------+ +-------------+
+ |
+ +---- open_dir("child") ----> narrower DirCap
+
+ capsec-objects
+ +---------------------------------------------------------------+
+ | private cap-std/cap-net-ext backend and permission invariants |
+ +-----------------------------+---------------------------------+
+ |
+ +--------------+--------------+
+ v v
+ capsec-std capsec-tokio
+ sync facade async facade
+```
+
+### Crate layout
+
+Add a workspace crate named `capsec-objects`.
+
+`capsec-objects` owns all backend types and security invariants. It depends on:
+
+- `capsec-core`
+- `cap-std = "4"`
+- `cap-net-ext = "4"`
+- optional `tokio = "1"` behind a `tokio` feature
+
+`capsec-std` depends on `capsec-objects` without async features.
+`capsec-tokio` depends on `capsec-objects` with the `tokio` feature.
+
+This crate boundary prevents duplicated security-sensitive path and address
+resolution logic. It also allows sync and async wrappers to construct their
+respective file and socket types without exposing backend handles publicly.
+
+The `capsec` facade re-exports the synchronous types from `capsec::fs` and
+`capsec::net`, and asynchronous types from `capsec::tokio::fs` and
+`capsec::tokio::net`.
+
+### Backend rules
+
+- `cap_std::ambient_authority()` is called only inside audited bootstrap and
+ network-pool construction functions in `capsec-objects`.
+- All filesystem operations after bootstrap call methods on `cap_std::fs::Dir`.
+- All network operations call methods on `cap_std::net::Pool` or capabilities
+ derived from `cap-net-ext`.
+- Backend types remain in private fields.
+- No public `AsFd`, `AsHandle`, `AsRawFd`, `AsRawHandle`, `IntoRawFd`,
+ `IntoRawHandle`, or `into_inner` implementation is provided.
+- Platform-specific conversion to Tokio types remains private and uses owned
+ handle conversions, not duplicated ambient lookups.
+
+## Typed authorization targets
+
+The current `CapProvider ` accepts `&str`, which loses non-UTF-8 path data and
+mixes filesystem, network, environment, and process targets. Version `0.3.0`
+replaces that argument with a typed target:
+
+```rust
+#[non_exhaustive]
+#[derive(Clone, Copy, Debug)]
+pub enum CapTarget<'a> {
+ Path(&'a std::path::Path),
+ SocketAddr(std::net::SocketAddr),
+ HostPort {
+ host: &'a str,
+ port: u16,
+ },
+ EnvKey(&'a std::ffi::OsStr),
+ Program(&'a std::ffi::OsStr),
+ Opaque(&'a str),
+}
+
+pub trait CapProvider
+}
+
+pub struct FileCap
+}
+
+pub type ReadDirCap = DirCap {
+ pub fn open_dir(&self, relative: impl AsRef ` does not implement `CapProvider `. Such an implementation would
+allow the object capability to authorize an unrelated ambient path through the
+legacy APIs.
+
+### File operations
+
+The complete synchronous method set is:
+
+| Method | Required permission | Result |
+|---|---|---|
+| `read` | `AllowsFsRead` | `Vec ` |
+
+`rename` and `copy` accept separate source and destination directory
+capabilities:
+
+```rust
+source.rename("old", &destination, "new")?;
+source.copy("input", &destination, "output")?;
+```
+
+Both sides are mediated by their respective open directory handles.
+
+### Open options
+
+Raw `std::fs::OpenOptions` and `cap_std::fs::OpenOptions` are not accepted.
+Expose permission-specific builders:
+
+```rust
+pub struct ReadOpenOptions { /* private */ }
+pub struct WriteOpenOptions { /* private */ }
+pub struct ReadWriteOpenOptions { /* private */ }
+```
+
+- `ReadOpenOptions` cannot enable write, append, truncate, or create.
+- `WriteOpenOptions` cannot enable read.
+- `ReadWriteOpenOptions` is accepted only by `DirCap {
+ pub fn into_async(self) -> AsyncDirCap ;
+}
+
+impl {
+ pub fn into_sync(self) -> DirCap ;
+}
+```
+
+No ambient namespace lookup occurs during conversion.
+
+## Network API
+
+### Immutable authority pools
+
+```rust
+pub struct NetPoolCap
+}
+
+pub type ConnectPoolCap = NetPoolCap ` and
+`AsyncFileCap `. Existing trait behavior remains unchanged.
+
+`DirScope` and `HostScope` remain supported for compatibility APIs and for
+controlling object-capability bootstrap. Documentation distinguishes them from
+handle-backed confinement.
+
+## Audit integration
+
+`cargo capsec audit` adds the following rules:
+
+- Flag direct calls to `cap_std::ambient_authority`.
+- Flag direct `cap_std::fs::Dir::open_ambient_dir` and ambient network-pool
+ insertion outside `capsec-objects`.
+- Flag direct construction or use of `cap_std`, `cap_primitives`, and
+ `cap-net-ext` authority types in application crates.
+- Continue flagging direct `std` and `tokio` filesystem/network operations.
+- Treat object-capability methods as authorized sinks tied to the receiver
+ object rather than requiring a nearby `CapProvider ` argument.
+- Include object-capability acquisition and delegation edges in shallow and
+ deep audit reports.
+
+The audit report distinguishes:
+
+- class-capability acquisition;
+- ambient bootstrap;
+- object attenuation;
+- object delegation;
+- actual I/O operations.
+
+## Testing
+
+### Compile-fail tests
+
+Tests must prove:
+
+- `DirCap ` does not implement `CapProvider `.
+- Backend and raw-handle fields are inaccessible.
+- Safe code cannot convert a narrow object capability into `FsAll` or `NetAll`.
+- Async permission boundaries match synchronous boundaries.
+
+### Filesystem runtime tests
+
+Run equivalent sync and Tokio tests for:
+
+- normal reads and writes;
+- nonexistent-file creation;
+- recursive directory creation;
+- source/destination copy across two capabilities;
+- source/destination rename across two capabilities;
+- absolute-path rejection;
+- every platform prefix form;
+- `..` traversal rejection;
+- symlink to a file outside the root;
+- symlink to a directory outside the root;
+- nested symlink chains;
+- dangling symlinks;
+- concurrent symlink replacement during open;
+- concurrent directory rename during open;
+- removal and recreation of a pathname after a file handle is acquired;
+- non-UTF-8 filenames on Unix;
+- permission attenuation and `try_split`;
+- directory iteration without ambient-path escape;
+- no access to parent or sibling directories;
+- capability transfer across threads/tasks.
+
+The concurrent race tests run enough iterations to exercise the check/open
+window that exists in the compatibility API and demonstrate that the
+handle-relative API never escapes.
+
+### Network runtime tests
+
+Run equivalent sync and Tokio tests for:
+
+- connect to an exact allowed address;
+- reject a different IP;
+- reject a different port;
+- CIDR and port-range boundaries;
+- bind only to allowed local addresses;
+- deny-all empty pool;
+- subset restriction;
+- reject attempted authority expansion;
+- hostname resolution pinned to its construction-time addresses;
+- no DNS lookup during delegated `connect`;
+- TCP listener acceptance;
+- UDP destination check on every `send_to`;
+- connected UDP cannot retarget;
+- async connect cancellation;
+- object transfer across threads/tasks.
+
+### Platform matrix
+
+Required CI targets:
+
+- Linux
+- macOS
+- Windows
+
+WASI builds cover the filesystem API supported by `cap-std`. Network APIs are
+compiled only where the backend and standard library expose the required
+socket facilities.
+
+### Audit tests
+
+Add fixtures proving:
+
+- direct backend ambient acquisition is reported;
+- object-capability I/O is not reported as an ambient bypass;
+- delegation appears in the authority graph;
+- legacy direct `std` and `tokio` bypasses remain detected.
+
+## Documentation
+
+Update:
+
+- root `README.md`;
+- `SECURITY.md`;
+- `crates/capsec-core/README.md`;
+- `crates/capsec-std/README.md`;
+- `crates/capsec-tokio/README.md`;
+- crate-level rustdoc for all affected crates;
+- examples for sync filesystem, Tokio filesystem, sync network, and Tokio
+ network;
+- the comparison with `cap-std` to state that capsec now uses it as the
+ handle-backed enforcement layer.
+
+Documentation must use “class capability,” “scoped capability,” and “object
+capability” consistently and must not describe name-based `DirScope` checks as
+equivalent to handle-backed confinement.
+
+## Implementation sequence
+
+1. Add typed `CapTarget` and migrate `CapProvider`, `Scope`, macros, wrappers,
+ and tests.
+2. Add `capsec-objects` with private backend wrappers and relative-path
+ validation.
+3. Implement synchronous directory and file capabilities with the complete
+ operation matrix.
+4. Implement Tokio directory and file capabilities with parity tests.
+5. Implement immutable network pools and synchronous TCP/UDP objects.
+6. Implement nonblocking Tokio TCP/UDP objects.
+7. Re-export the APIs through `capsec-std`, `capsec-tokio`, and `capsec`.
+8. Extend audit discovery, call classification, and reports.
+9. Update compile-fail, adversarial, cross-platform, documentation, and example
+ coverage.
+
+Each step lands with tests. No step weakens an existing capability invariant.
+
+## Acceptance criteria
+
+The implementation is complete when:
+
+- all security invariants in this specification have automated tests;
+- every current filesystem and network wrapper has an object-capability
+ equivalent in sync and Tokio APIs;
+- filesystem access after bootstrap is exclusively handle-relative;
+- network access after pool construction is exclusively pool-mediated;
+- no safe public API exposes or broadens backend authority;
+- typed targets eliminate lossy path conversion;
+- Linux, macOS, Windows, and applicable WASI checks pass;
+- compile-fail snapshots prove permission separation;
+- `cargo capsec audit` detects direct ambient/backend bypasses;
+- README and rustdoc examples use object capabilities as the recommended
+ interface;
+- the legacy API remains source-compatible except for the documented
+ `CapProvider`/`Scope` signature migration required by `0.3.0`.