CSC supports two execution modes with distinct security properties. The mode determines which enforcement mechanisms are active and what claims can be made about execution integrity.
Rule: security claims are mode-specific. A receipt produced in local mode does not carry the same trust guarantees as one produced in hardened mode. Operators must not treat them as equivalent.
Purpose: Development, testing, demos, and learning.
csc run contract.json policy.yaml
csc run contract.json policy.yaml --mode local- Policy evaluation (allow/deny/needs_approval)
- Contract schema validation
- Resource limit checking (command count, argv size, justification length)
- Pre-launch path validation (cwd resolution, symlink rejection, scope prefix checks) — this is contract-level validation before execution, not runtime containment
- Capped output capture with deterministic hashing
- Structured receipts for every outcome (success, failed, blocked)
- Optional receipt signing (
--sign --signing-key PATH --key-id ID)
- Process isolation (no namespace separation)
- Runtime filesystem containment — a subprocess can access anything the runner process can, regardless of declared scopes
- Network restriction
- Privilege drop or
no_new_privs - Mandatory receipt signing
Local mode trusts the host environment. Policy evaluation and path checks are pre-launch validation — they reject obviously wrong contracts but cannot prevent a running subprocess from exceeding declared scope. Local mode is not a sandbox.
Development workflows, policy authoring, CI dry runs, integration testing on non-Linux platforms.
Purpose: Bounded production execution with kernel-enforced isolation.
csc run contract.json policy.yaml \
--mode hardened \
--sign --signing-key /keys/key.pem --key-id prod-01- Linux only (rejects other platforms at startup)
bwrap,setpriv, andprlimitmust be onPATH- Receipt signing is mandatory in hardened mode (
--sign --signing-key --key-id) - For the tested containerized deployment shape, the outer container should be started with
--network=noneas host-level defense in depth
Hardened mode requires a compatible Linux runtime. Not all Linux environments support the namespace operations that bubblewrap requires.
- User namespaces must be available to the runtime
- Some Ubuntu/AppArmor-restricted environments may block
bwrap --unshare-netwith errors such asRTM_NEWADDR: Operation not permitted - Restrictive seccomp profiles may prevent namespace creation
- Container runtimes must allow the namespace features bubblewrap uses; not all Docker/Podman configurations do so by default
If bwrap --unshare-net is blocked by the host or runtime, that environment is not currently supported for hardened mode.
The shipped Docker image and CI workflow document the tested configuration. Deployments outside that configuration require operator verification.
Everything in local mode, plus:
- Namespace isolation via bubblewrap:
- Mount namespace: explicit filesystem view (only declared paths visible)
- Network namespace:
--unshare-net(no external connectivity) - PID namespace:
--unshare-pid(process isolation) - New session:
--new-session - Parent-linked lifecycle:
--die-with-parent
- Privilege controls via setpriv:
--no-new-privsalways applied (prevents privilege escalation)- Optional UID/GID drop with
--clear-groups(requires root container)
- Resource limits via prlimit:
- CPU time, address space, process count, file size
- Mandatory receipt signing (Ed25519)
- Advisory command blocking (shells, interpreters, wrappers denied by default)
- Writable paths come only from declared write scopes
- cwd is read-only unless under an approved writable root
- System paths (
/usr,/lib,/bin,/etc) are read-only /tmpis a private tmpfs (not shared with host)/procand/devare sandbox-local- Paths not explicitly bound are invisible inside the sandbox
- Primary enforcement:
bwrap --unshare-net(kernel namespace) - Secondary check:
verify_network_disabled()confirms no non-loopback interfaces on the host (expects--network=nonecontainer) - No DNS, no egress, no ingress inside the sandbox
- Full allowlist/egress engine deferred to Stage 3
- Syscall filtering (seccomp — deferred to post-pilot)
- Arbitrary UID/GID switching in default non-root container image
- Protection against compromised container runtime
- Protection against host root compromise
- Guarantee that command-name blocking is a security boundary (it is advisory)
Hardened mode uses kernel-enforced boundaries. Even if a subprocess attempts to escape declared scope, the namespace isolation restricts what it can see and do. The security boundary is bubblewrap + setpriv + prlimit, not Python-side checks.
CI/CD pipelines, automated agent execution, any context where execution integrity matters and the environment is Linux-based.
| Property | Local | Hardened |
|---|---|---|
| Platform | Any | Linux only |
| Namespace isolation | No | Yes (mount, net, pid) |
| Filesystem boundary | Pre-launch validation only | Kernel-enforced (bwrap) |
| Network | Unrestricted | Disabled (namespace) |
no_new_privs |
No | Always |
| Resource limits | Schema/runner checks | prlimit enforced |
| Receipt signing | Optional | Mandatory |
| Command blocking | Advisory | Advisory |
| Receipts | Produced for all runtime outcomes | Signed for all runtime outcomes |
| Approval artifacts | Supported | Supported |
Note on receipts: In both modes, CLI/configuration errors (e.g. missing signing key, invalid mode string, unparseable contract file) may fail before receipt generation. The "produced/signed for all outcomes" claim applies to runtime outcomes after successful CLI setup — not to pre-parse or pre-config failures.
The pilot claim is bounded: Linux, filesystem-bounded local/CI execution, no network. Hardened mode does not currently support:
- Windows or macOS
- Network allowlisting (full egress control is Stage 3)
- Secrets management (deferred)
- Syscall filtering (seccomp deferred to post-pilot)
- Multi-tenant isolation (single-runner, single-contract model)
check_command_allowed() blocks known shells, interpreters, and wrappers by basename. This is a product-policy layer, not a security enforcement mechanism. A renamed binary bypasses it. The real containment is the sandbox namespace.
bubblewrap requires the host/container runtime to support the namespace features it uses. Some Ubuntu/AppArmor-restricted environments, restrictive seccomp profiles, or disabled user namespaces can prevent bwrap from functioning. If bwrap --unshare-net fails, that environment is not supported for hardened mode. The hardened pilot is tested against the shipped container image and CI workflow only.
The InMemoryApprovalStore tracks consumed single-execution approvals in memory. Consumed approvals are lost on process restart. For durable replay prevention across restarts, a persistent backend is needed (deferred).
A signed receipt proves integrity and signer identity. It does not by itself prove that the sandbox was correctly configured or that isolation was enforced. Receipt trust and runtime trust are separate concepts.
CSC hardened mode is safe enough for bounded production use in Linux-based, filesystem-bounded local/CI execution workflows without network access, under the documented trust assumptions and deployment constraints.
This claim applies only to the tested container image and CI workflow configuration. It does not extend to arbitrary Linux hosts, other operating systems, or deployment configurations not covered by the integration test suite.