Skip to content

security: per-sandbox secret binding for sandbox→gateway RPCs #1354

@zredlined

Description

@zredlined

Context

Surfaced during review of PR #1323 (agent feedback loop /wait endpoint).

The sandbox supervisor authenticates to the gateway with
`x-sandbox-secret`, validated against the server-wide
`ssh_handshake_secret` config value. The secret is a single string for
the whole gateway — not per-sandbox.

Several gRPC methods accept this auth scheme:

  • `SubmitPolicyAnalysis` (SANDBOX_SECRET_METHODS, existing) — request
    carries a `sandbox_name` field; handler persists chunks under that
    name without verifying the caller "is" that sandbox.
  • `GetDraftPolicy` (DUAL_AUTH_METHODS, added in feat(policy): agent-driven policy management — the agent half #1323 so policy.local
    /wait works under OIDC) — request carries a `name` field; handler
    looks up the sandbox by name without verifying the caller's identity.
  • Other entries in SANDBOX_SECRET_METHODS follow the same pattern.

Exposure

Any process that holds the shared sandbox secret can act under any
sandbox name. Concrete impact:

  • Read: enumerate another sandbox's draft policy chunks
    (including rationale/proposed_rule, which can leak the agent's intent
    and the developer's reviewer guidance).
  • Write: submit policy proposals to another sandbox's draft set, with
    the receiving developer seeing them in their TUI inbox as if from
    their own agent.

This is symmetric across the read and write paths — both already
exist; #1323 extends the read surface as a no-net-new-attack-class
expansion. Worth fixing in one motion rather than two.

Proposed direction

Bind the sandbox secret to a sandbox identity at handshake time so the
gateway can enforce "caller name matches request name" on every
sandbox-secret-authenticated method.

Two plausible shapes:

  1. Per-sandbox secret. Gateway issues each sandbox a unique secret
    during handshake; secret carries (or is keyed on) the sandbox name.
    Validation extracts the name from the secret rather than the
    request.
  2. Signed sandbox identity claim. Single shared secret, but a
    short-lived signed token issued at handshake carries the sandbox
    name. Sandbox includes the token alongside the secret; gateway
    verifies the signature and compares to the request name.

(1) is simpler but requires per-sandbox secret storage and rotation.
(2) preserves the current single-secret deploy model but adds a
signing step.

Out of scope for this issue

  • Bearer-token / OIDC authz (already enforced via scopes).
  • Network policy on the sandbox→gateway channel.

Acceptance

  • All SANDBOX_SECRET_METHODS and DUAL_AUTH_METHODS handlers that name
    a sandbox in the request body verify the caller is that sandbox.
  • Cross-sandbox call is denied with a clear error.
  • Migration path documented for existing deployments (existing
    sandboxes can't speak the new scheme until they restart against an
    upgraded gateway).
  • Regression tests cover at least: same-sandbox call succeeds,
    cross-sandbox call fails with permission_denied.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions