Skip to main content

OpenShell Sandboxing Layers

Back to main doc

Supervisor as Container Entrypoint

Each agent pod uses the OpenShell supervisor as the container entrypoint:

  1. Supervisor starts (ENTRYPOINT)
  2. Connects to OpenShell Gateway via OPENSHELL_GATEWAY env var
  3. Reads OPA/Rego policy
  4. Applies Landlock (filesystem restrictions) + custom seccomp (syscall filtering)
  5. Drops all capabilities
  6. Execs the agent process as a restricted child

The agent inherits kernel-enforced isolation for its entire lifetime. Normal pod networking is preserved (no network namespace in PoC), so Istio mesh works unchanged.

Protection layers

LayerMechanismLocked?Reloadable?
FilesystemLinux Landlock LSM — kernel-level path allowlistAt sandbox creationNo
NetworkHTTP CONNECT proxy (forced via veth/netns) + OPA/RegoAt sandbox creationYes (hot-reload)
ProcessSeccomp BPF — syscall allowlistAt sandbox creationNo
InferenceCredential stripping + backend injection + model ID rewritingAt sandbox creationYes (hot-reload)

Credential isolation

OpenShell implements zero-secret credential isolation. Agent env vars contain placeholder tokens (openshell:resolve:env:API_KEY), not real secrets. The supervisor proxy resolves placeholders to real credentials at the HTTP layer via TLS termination before forwarding upstream.

For LLM calls, the supervisor's inference router strips agent-supplied auth headers entirely and injects backend API keys from the gateway's credential store.

Egress policy enforcement

AgentSupervisor?OPA Enforced?Egress
weather-agent-supervisedYesYesTier 2 (supervisor + port bridge)
weather-agent-supervisedYesYesRestricted to *.svc.cluster.local + LiteMaaS
adk-agent-supervisedYesYes (supervisor enforced)Tier 2
claude-sdk-agentNoNo (policy mounted but not enforced)Open

Non-supervised agents have OPA policy files mounted at /etc/openshell/policy.yaml as preparation for supervisor integration. The policies are NOT enforced until the supervisor binary is the container entrypoint.

Blocker for full enforcement: The supervisor creates a network namespace that blocks kubectl port-forward and K8s readiness probes. Solutions:

  1. Upstream: supervisor exposes agent port through the proxy
  2. Workaround: run tests from inside the cluster (test runner pod)
  3. Workaround: sidecar that bridges the netns port to the pod network

Security: Init Container Pattern (TODO)

The PoC uses privileged: true on the supervised agent container because the supervisor needs CAP_SYS_ADMIN + CAP_NET_ADMIN for network namespace creation.

Minimum capability set (from codebase research):

CapabilityRequired forCan be dropped?
CAP_NET_ADMINveth pairs, netns, IPs, routesNo
CAP_SYS_ADMINunshare(), setns(), Landlock ABINo
CAP_SYS_PTRACEOPA proxy process inspectionPossibly

Target (production): Use an init container for the supervisor:

initContainers:
- name: supervisor-init
image: ghcr.io/nvidia/openshell/supervisor:latest
securityContext:
privileged: true # Only init container is privileged
command: ["/usr/local/bin/openshell-sandbox", "--setup-only"]

containers:
- name: agent
image: agent:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL] # Agent has zero capabilities

Requires: Upstream OpenShell support for --setup-only mode.

OpenShell RFC 0001

OpenShell is being rearchitected via RFC 0001 into a composable, driver-based system with four pluggable subsystems:

SubsystemPurposeRossoctl Mapping
ComputeSandbox lifecycle (K8s, Podman, VM)Rossoctl as compute driver (Phase 2)
CredentialsSecret resolution (Vault, K8s Secrets)Delivers secrets to supervisor proxy
Control-plane identityUser/operator auth (mTLS, OIDC)Keycloak OIDC
Sandbox identityWorkload identity (SPIFFE)SPIRE

AuthBridge + Supervisor Integration (Phase 3)

Rossoctl's AuthBridge and the OpenShell supervisor provide complementary security layers. In the current PoC they are mutually exclusive — supervised agents disable AuthBridge injection via rossoctl.io/inject: disabled. Phase 3 resolves the architectural conflict and combines both.

What Each Layer Provides

ConcernAuthBridgeSupervisorCombined Value
Inbound authJWT validation (Keycloak JWKS)Who can talk to the agent
Egress filteringOPA/Rego policy (allow/deny by endpoint)What the agent can reach
Outbound authRFC 8693 token exchange (audience-scoped)How the agent authenticates to external APIs
Credential injectionGateway provider injection (API keys)Zero-trust secret delivery
Workload identitySPIFFE SVID via SPIRECryptographic agent identity
Filesystem isolationLandlock LSMKernel-enforced path allowlist
Syscall filteringSeccomp BPFDangerous syscalls blocked
Network isolationnetns + veth pairAgent in separate network namespace
ObservabilityOTel span injection on every requestDistributed tracing

Why They Don't Work Together Today

AuthBridge's proxy-init installs iptables rules in the pod's default network namespace. The supervisor then moves the agent process into a separate network namespace (10.200.0.2). This breaks both directions:

Inbound: Caller → iptables (default netns) → Envoy → localhost:8080
↑ agent is NOT here
agent is at 10.200.0.2:8080
in supervisor's netns

Outbound: Agent (10.200.0.2) → OPA proxy (10.200.0.1:3128)
→ exits to default netns → iptables → Envoy (token exchange)
→ upstream
↑ outbound COULD work (double-proxied) but credential
injection conflicts: supervisor strips auth headers,
Envoy injects different tokens

Current Deployment: AuthBridge Disabled for Supervised Agents

Supervised agents use rossoctl.io/inject: disabled to prevent the webhook from injecting AuthBridge sidecars. This is correct for Phase 1 because:

  • The supervisor provides its own egress control (OPA proxy)
  • The supervisor provides its own credential injection (gateway providers)
  • AuthBridge's iptables rules break in the supervisor's netns
AgentAuthBridgeSupervisorWhy
weather-agent-supervisedInjectedNoStandard Rossoctl agent (Tier 3)
adk-agent-supervisedInjectedNoStandard Rossoctl agent (Tier 3)
claude-sdk-agentInjectedNoStandard Rossoctl agent (Tier 3)
weather-supervisedDisabledYesSupervisor's netns breaks AuthBridge
openshell sandboxesNot presentYesGateway-managed, no webhook

Phase 3 Resolution Paths

Option A — Supervisor --setup-only mode (no netns): Use the supervisor only for Landlock + seccomp (filesystem + syscall isolation). Skip netns creation. Delegate egress control to AuthBridge's Envoy with an OPA ext_authz filter. This avoids the netns conflict entirely.

Pod (single netns):
proxy-init (iptables) → Envoy (AuthBridge)
↳ inbound: JWT validation
↳ outbound: OPA ext_authz (egress filter) + token exchange
supervisor --setup-only (Landlock + seccomp only)
agent (restricted filesystem + syscalls, standard networking)

Option B — Socat bridge + Envoy upstream rewrite: Keep the supervisor's netns. Add a socat bridge sidecar (Pod:8080 → 10.200.0.2:8080). Configure AuthBridge's Envoy to forward validated inbound traffic to the bridge instead of localhost. Outbound: OPA proxy forwards to Envoy for token exchange.

Inbound: Caller → iptables → Envoy (JWT) → socat bridge → 10.200.0.2:8080
Outbound: Agent → OPA proxy → Envoy (token exchange) → upstream

Option C — AuthBridge as supervisor plugin (RFC 0001): OpenShell's rearchitecture (RFC 0001) introduces pluggable subsystems. The "Credentials" subsystem could delegate to Keycloak token exchange (AuthBridge). The "Sandbox identity" subsystem could use SPIRE/SPIFFE. This makes AuthBridge a native part of the supervisor rather than a separate sidecar.

Use Cases Enabled by AuthBridge + Supervisor

Use CaseAuthBridge ProvidesSupervisor Provides
MCP tool authOAuth2 token exchange for GitHub, Jira, Slack APIsEgress policy limits which MCP endpoints are reachable
Multi-tenant isolationJWT audience validation per teamFilesystem + network isolation per sandbox
Audit trailOTel spans on every request with identityOPA decision logs (allow/deny)
Zero-trust LLM accessAudience-scoped token for LLM providerCredential stripping + model ID rewriting
Agent-to-agent authSPIFFE SVID mutual authenticationNetwork policy enforcement

Phase 3 Test Matrix

These tests become possible when AuthBridge + supervisor are combined:

TestAgentsWhat It Validates
authbridge_jwt_validates_inboundTier 2Inbound request rejected without valid JWT
authbridge_token_exchange_outboundTier 2Outbound MCP call gets audience-scoped token
supervisor_egress_plus_tokenTier 2OPA allows endpoint + AuthBridge adds token
spiffe_identity_assignedTier 2SPIRE issues SVID for supervised agent
combined_audit_trailTier 2OTel span + OPA decision log for same request
mcp_tool_with_egress_policyTier 2Agent calls MCP tool, OPA allows, token exchanged

LLM Compatibility Matrix

Agent / CLILiteMaaS (llama-scout, deepseek)Anthropic APIOpenAI API
Claude CLI (base image)No — validates model nameYes (native)No
Claude SDK agent (custom)Yes — OpenAI-compatible formatYes (native SDK)Yes
ADK agent (Google ADK)Yes — via LiteLLM wrapperN/AYes
OpenCode (base image)Yes — OpenAI-compatibleN/AYes
Codex (base image)Partial — may need real OpenAI keyN/AYes
Copilot (base image)No — proprietary GitHub APIN/AN/A

Key limitation: Claude CLI requires a real Anthropic API key. Our custom Claude SDK agent works with LiteMaaS because it uses httpx with the OpenAI chat/completions format, bypassing Claude CLI's model validation.