cageforge-backend-api 0.3.0

Capability and execution contracts for Cageforge sandbox backends
Documentation

⚠️ Independent project

Cageforge is not affiliated with, sponsored by, or endorsed by OpenAI. This crate adapts sandbox boundary ideas from open-source OpenAI Codex into an independent library API and contains no copied Codex source.

This crate is a supporting component of the cageforge crate, a cross-platform Rust sandbox for AI agents and untrusted code.

cageforge-backend-api

cageforge-backend-api is the contract layer between Cageforge's portable execution values and a native Linux, macOS, or Windows backend. It defines the typed handoff from a composed command and sandbox policy to the operating- system integration that will enforce and launch them.

The crate turns a backend's declared enforcement capabilities into a common preflight contract. BackendRequest::prepare_for accepts a request built from CommandRequest and EffectiveSandbox, verifies every required capability, and returns a prepared request or a typed error that identifies the failed requirement.

The handoff model

Sandbox and SandboxChild define shared preparation, launch, streams, and child lifecycle operations. Each native crate implements these traits directly; applications can use them through this crate or through the cageforge facade. The concrete child and error types remain available.

DynSandbox supports a runtime-selected implementation behind Box<dyn DynSandbox> or a shared Arc<dyn DynSandbox>. Its launch method prepares and spawns on the same backend instance. It returns an owned child with the common SandboxChild interface. SandboxExecutionError preserves the failing operation and original native error; child destruction follows the same native cleanup path as a concrete child.

CommandRequest + EffectiveSandbox
                 │
                 ▼
        cageforge-backend-api
       capabilities + preflight
                 │
                 ▼
     native backend lowering/launch

BackendRequest accepts CommandRequest and EffectiveSandbox, so policy composition and the PolicyCeiling intersection are completed before the backend handoff. The command's EnvironmentSpec is checked against the requested environment retained by the composed result, keeping command and policy construction aligned.

Call request.prepare_for(&backend, &base_context) to run the common capability and working-directory checks. base_context must include the runtime current directory: it is checked even when the command does not set an explicit cwd, so a child cannot silently inherit an unchecked directory from the launching process. The backend trait only supplies its capabilities; it cannot override this preflight with a broader set. The base context is narrowed to the effective workspace ceiling, and an effective working directory is rejected when the filesystem policy denies it. After preparation, use PreparedBackendRequest::command_spec for the executable and argv values, PreparedBackendRequest::working_directory for the resolved cwd, PreparedBackendRequest::path_context to inspect the already narrowed context, and PreparedBackendRequest::apply_environment with a backend-selected EnvironmentInput. The filesystem decision helpers use that same bound context and cannot be given a context from another request. A symbolic selector is never evaluated without it. Use filesystem_lowering and network_lowering to obtain every immutable constraint layer needed by native lowering. Every returned layer is mandatory: the backend must enforce their conjunction and must not lower only the requested or only the ceiling side. Use authorize_connection to receive a decision that already combines the requested and ceiling sides. Every prepared accessor takes &backend and checks that it is the same instance used by prepare_for.

The returned PreparedBackendRequest<'_, B> is bound to the concrete backend type B whose capabilities were checked. A native lowering method should accept PreparedBackendRequest<'_, Self> so a handoff prepared for one backend implementation cannot be passed to another backend implementation by accident. It also carries a runtime BackendIdentity; every prepared accessor must be called with the same backend instance that was passed to prepare_for, or it returns BackendContractError::BackendIdentityMismatch. This prevents two instances of one backend type with different enforcement state from sharing a prepared handoff. The identity is a caller-managed token, not proof that operating-system enforcement exists. Prepared accessors also compare the capability snapshot captured during preflight and return BackendContractError::BackendCapabilitiesMismatch if the backend changes capabilities afterwards. The backend remains responsible for advertising only capabilities it can actually enforce.

Capability names describe portable enforcement requirements, not a shared operating-system mechanism. A Linux backend may advertise conventional temporary-scope or pathname local-IPC enforcement while a Windows backend may omit those capabilities and return the same typed unsupported-capability error before lowering. Native path, object, and process mechanisms stay in each backend.

Create BackendIdentity explicitly with BackendIdentity::new(). It has no Default implementation because each new identity represents a distinct backend boundary; cloning preserves an existing identity.

When a request needs an unsupported capability, preparation returns BackendContractError::UnsupportedCapability. The error is matchable by its BackendCapability variant and its display text names the required enforcement, for example: filesystem missing-path behavior (error or skip).

Capability checks include implicit requirements: a workspace-relative glob needs FilesystemScopes so it is evaluated against the narrowed workspace context, and every deny glob needs FilesystemGlobScanDepth because an absent explicit depth means unbounded scanning. Concrete scopes also require the matching selector capability: absolute, workspace, system-root, minimal, temporary-directory, or the platform's conventional temporary scope. A backend that cannot enforce any required behavior is rejected before lowering.

use cageforge_backend_api::{
    BackendCapabilities, BackendRequest, SandboxBackend,
};

struct ExampleBackend {
    capabilities: BackendCapabilities,
    identity: cageforge_backend_api::BackendIdentity,
}

impl ExampleBackend {
    fn new(capabilities: BackendCapabilities) -> Self {
        Self {
            capabilities,
            identity: cageforge_backend_api::BackendIdentity::new(),
        }
    }
}

impl SandboxBackend for ExampleBackend {
    fn identity(&self) -> &cageforge_backend_api::BackendIdentity {
        &self.identity
    }

    fn capabilities(&self) -> BackendCapabilities {
        self.capabilities.clone()
    }
}

// A real backend constructs its capabilities from the enforcement mechanisms
// it can prove safe, then runs the common preflight before lowering the
// request to native process and filesystem APIs.

Responsibilities

The crate owns:

  • BackendCapability and BackendCapabilities;
  • BackendRequest and the opaque PreparedBackendRequest;
  • the synchronous SandboxBackend capability contract and common preflight;
  • Sandbox, DynSandbox, and SandboxChild execution contracts;
  • common unsupported-capability and preparation errors.

The native backend owns:

  • translating the effective contract to Landlock, bubblewrap, Seatbelt, Windows ACL/token, or another OS mechanism;
  • symlink, junction, reparse-point, mount, and TOCTOU-safe enforcement;
  • DNS resolution and exact SocketAddr connection authorization;
  • platform-specific core environment selection; and
  • process launch, stdio, timeout, cancellation, and lifecycle handling.

cageforge provides the ergonomic facade and target-specific backend selection that connect this contract to a concrete execution flow.

Workspace role

Crate Role in the relationship
cageforge-command Supplies validated command and environment intent.
cageforge-policy-compose Supplies the narrowed EffectiveSandbox.
cageforge-policy Supplies portable policy values and decision types used during lowering.
cageforge-path Supplies the shared lexical path semantics used by policy and native integrations.
cageforge-config Produces validated TOML-backed command and policy values for the handoff.
cageforge Re-exports this contract and exposes the common Sandbox launch shape.
cageforge-linux, cageforge-macos, cageforge-windows Implement OS enforcement and process launch after preflight.

The complete API is documented on docs.rs.