Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.crewship.ai/llms.txt

Use this file to discover all available pages before exploring further.

Container Isolation

Crewship uses multiple layers of isolation to prevent agents from accessing unauthorized resources, exfiltrating data, or interfering with other agents.

Isolation Layers

+-----------------------------------------------------------+
| Layer 5: Keeper (AI-powered credential gating)            |
+-----------------------------------------------------------+
| Layer 4: Network Policy (free/restricted domain filtering)|
+-----------------------------------------------------------+
| Layer 3: Sidecar Proxy (credential injection, scrubbing)  |
+-----------------------------------------------------------+
| Layer 2: UID Boundary (agent=1001, sidecar=1002)          |
+-----------------------------------------------------------+
| Layer 1: Container Runtime (Docker, gVisor, Kata, Sysbox) |
+-----------------------------------------------------------+

UID Security Boundary

Two fixed UIDs enforce a privilege boundary inside each container:
UIDProcessAccess
1001Agent process (Claude Code, etc.)Code execution, file I/O, HTTP via proxy
1002Sidecar proxyCredential store, IPC token, network control
The UID assignments (1001 for agent, 1002 for sidecar) are a security boundary. Do not change these values — they are referenced throughout the codebase for privilege separation.

What Agents Cannot Do

Because agents run as UID 1001 and the sidecar runs as UID 1002:
  • Agents cannot read the sidecar’s in-memory credential store
  • Agents cannot access the IPC token (used for crewshipd authentication)
  • Agents cannot modify network policy configuration
  • Agents cannot tamper with outbound request headers (the sidecar handles this)
  • Agents cannot set their own container ID in Keeper execute requests

Container Runtime Security

Crewship supports multiple container runtimes with different security profiles:
RuntimeSecurity LevelDescription
runcStandardDefault Docker runtime
runsc (gVisor)HighKernel-level syscall interception
kata-runtimeVery HighLightweight VM isolation
sysbox-runcHighEnhanced container isolation
Configure via:
container:
  default_runtime: "runsc"  # or "runc", "kata-runtime", "sysbox-runc"

Container Creation Security

Containers are created with these security measures (internal/provider/docker/docker.go):
  • Cap-drop ALL: All Linux capabilities are dropped
  • Cap-add NET_RAW: Re-added after dropping ALL (allows ICMP/ping for network diagnostics)
  • SecurityOpt no-new-privileges: Blocks execution of setuid binaries inside the container
  • Non-root execution: Agent processes run as UID 1001
  • Read-only filesystem: System directories are read-only (ReadonlyRootfs: true)
  • tmpfs /tmp (500M): Writable temp directory, not persistent across restarts
  • PID limit: 200: Prevents fork bomb attacks (PidsLimit: 200)
  • Memory limits: Configurable per-crew (default_memory_mb, default 512 MB)
  • CPU limits: Configurable per-crew (default_cpus, default 1.0)
  • Network isolation: Containers connect to a dedicated Docker bridge network
  • ExtraHosts: host.docker.internal:host-gateway enables container-to-host communication
  • Named volumes: crewship-home-{slug} and crewship-tools-{slug} for persistent home and tools directories

Network Policy

Each crew can have a network access policy:
All outbound connections are allowed. The sidecar still intercepts and injects credentials for known LLM providers, but does not block unknown domains.
# No explicit network policy = free mode
Unknown network modes default to restricted (fail closed). This prevents a typo in the configuration from accidentally allowing unrestricted access.

Domain Allowlist Implementation

The DomainAllowlist (internal/sidecar/allowlist.go) is a thread-safe set of allowed domain names:
  • Domains are stored lowercase for case-insensitive matching
  • Port numbers are stripped before comparison
  • IPv6 bracket notation is handled correctly
  • Domains can be added at runtime via allowlist.Add(domain)

Proxy Security

The sidecar proxy (internal/sidecar/proxy.go) enforces several security measures:

Request Body Limits

const maxRequestBodyBytes = 10 * 1024 * 1024 // 10 MB
All proxied requests have their body limited to 10 MB to prevent OOM attacks. LLM API requests are typically under 1 MB.

Hop-by-Hop Header Stripping

Per RFC 2616 Section 13.5.1, the proxy strips these headers:
HeaderRisk
ConnectionConnection control
Keep-AliveConnection control
Proxy-AuthenticateProxy auth
Proxy-AuthorizationData exfiltration vector
TETransfer encoding
TrailersTransfer encoding
Transfer-EncodingTransfer encoding
UpgradeProtocol upgrade
Proxy-Authorization is especially dangerous — an agent could use it to exfiltrate credentials via a controlled proxy.

HTTPS CONNECT Tunnels

For HTTPS requests, the sidecar:
  1. Checks the domain allowlist (in restricted mode)
  2. Establishes a TCP tunnel
  3. Does not inject credentials (the tunnel is opaque)
Credential injection only works for:
  • Plain HTTP proxy requests (agent sets HTTP_PROXY)
  • Reverse proxy mode (ANTHROPIC_BASE_URL=http://127.0.0.1:9119)

Provider Detection

The sidecar automatically detects which LLM provider a request is targeting:
func providerForHost(host string) ProviderType {
    switch host {
    case "api.anthropic.com":    return ProviderAnthropic
    case "api.openai.com":       return ProviderOpenAI
    case "generativelanguage.googleapis.com": return ProviderGoogle
    default:                     return ""  // no injection
    }
}

Credential Injection by Provider

ProviderAuth Method
Anthropic (API key)x-api-key: {key} + anthropic-version: 2023-06-01
Anthropic (OAuth)Authorization: Bearer {token} (detected by sk-ant-oat prefix)
OpenAIAuthorization: Bearer {key}
Google?key={key} query parameter

Keeper Execute Security

The /keeper/execute flow has the most stringent security because it runs shell commands with credentials:
  1. Sidecar validation:
    • Intent and command length limits (4096 chars)
    • Null byte rejection
    • Shell metacharacter blocking (;, |, `, >, &&, ||, $()
    • Content inside single quotes is exempt
    • Container ID always from IPC config (agent cannot override)
  2. crewshipd validation:
    • Same shell metacharacter checks (defense in depth)
    • Keeper LLM evaluates the command
  3. Execution:
    • Credential injected as environment variable for the command only
    • Output scrubbed of credential values before returning to agent

Output Scrubbing

The scrubber package (internal/scrubber/) removes credential values from agent output. This prevents:
  • Credentials appearing in chat messages
  • Credentials being logged to the progress stream
  • Credentials in Keeper execute output being returned to the agent

Container Auto-Detection

The Docker provider (internal/provider/docker/docker.go) auto-detects the container runtime by probing socket paths:
Socket PathRuntime
/var/run/docker.sockDocker
~/.colima/default/docker.sockColima
~/.orbstack/run/docker.sockOrbStack
~/.rd/docker.sockRancher Desktop
~/.docker/run/docker.sockDocker Desktop (macOS)
/run/user/{uid}/podman/podman.sockPodman (rootless)
/run/podman/podman.sockPodman (root)
/run/containerd/containerd.socknerdctl
Each socket gets a 1.5-second ping timeout to avoid blocking on unresponsive daemons.