Skip to content

sec(server): add per-sandbox authorization to gRPC RPCs #40

@pimlock

Description

@pimlock

Problem

All sandbox pods and CLI tools share a single mTLS client certificate (CN=navigator-client). The server authenticates that the client holds a valid cert but has no mechanism to verify which sandbox is calling. Sandbox identity is determined entirely by the sandbox_id field in the protobuf request message, which is not verified against the TLS connection.

Impact

A compromised sandbox pod could:

  • Read another sandbox's policy via GetSandboxPolicy(sandbox_id="other-sandbox")
  • Read another sandbox's provider credentials via GetSandboxProviderEnvironment(sandbox_id="other-sandbox")
  • (After issue fix: use github backend for sccache in mise.toml #78) Spoof policy load status via ReportPolicyStatus(sandbox_id="other-sandbox")

Current Mitigations

  • mTLS prevents unauthenticated access (the channel is encrypted and authenticated)
  • Kubernetes pod isolation limits lateral movement
  • Sandbox IDs are UUIDs (not guessable, but enumerable via ListSandboxes)

Root Cause

  1. crates/navigator-bootstrap/src/pki.rs:35-98 — PKI generates a single shared client cert
  2. crates/navigator-server/src/sandbox/mod.rs:681-710 — Same navigator-client-tls secret mounted in all pods
  3. crates/navigator-server/src/multiplex.rs:44-56 — No tonic interceptor extracts client cert identity
  4. crates/navigator-server/src/grpc.rs:517-580 — Handlers call request.into_inner() discarding connection metadata

Proposed Fix Options

Option A: Per-sandbox client certificates

  • Extend crates/navigator-bootstrap/src/pki.rs to generate unique client certs per sandbox with sandbox_id in the SAN
  • Create per-sandbox Kubernetes secrets
  • Server extracts SAN from peer cert in a tonic interceptor and validates against sandbox_id in request

Option B: Tonic interceptor with Kubernetes identity

  • Add a tonic interceptor/middleware that extracts peer cert info from the TLS connection
  • Inject verified identity into Request::extensions() for handler consumption
  • Handlers compare extracted identity against sandbox_id in request

Option C: Service mesh or network policy

  • Use Kubernetes NetworkPolicy to restrict sandbox-to-server traffic to only its own sandbox_id
  • Less code change but relies on infrastructure-level controls

Affected RPCs

Related

Discovered during security review of issue #78 (live policy updates).


Originally by @johntmyers on 2026-02-25T08:58:12.857-08:00

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:gatewayGateway server and control-plane work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions