Skip to content

feat(sandbox): support policy discovery and restrictive defaults on sandbox containers #82

@drew

Description

@drew

Summary

Enable sandbox containers to operate without a pre-configured policy by supporting three policy resolution modes:

  1. Policy provided at create time — sandbox loads it from the gateway (current behavior, unchanged)
  2. Policy null at create time, found on disk — sandbox reads from /etc/navigator/policy.yaml, syncs it to the gateway, reads it back
  3. Policy null at create time, no disk policy — sandbox uses a hardcoded restrictive default policy, syncs it to the gateway

This allows policies to be shipped directly on container images (baked into /etc/navigator/policy.yaml) or to fall back to a safe restrictive default when no policy is configured at all.

Motivation

Currently, spec.policy is required at sandbox creation time. The CLI always provides one (falling back to the dev default from dev-sandbox-policy.yaml). This works but:

  • Prevents container images from shipping their own policies
  • Requires the CLI to always know the right policy upfront
  • Has no safe fallback if policy is somehow missing

Implementation Plan

Task 1: Add restrictive default policy to navigator-policy

File: crates/navigator-policy/src/lib.rs

Add restrictive_default_policy() that returns a SandboxPolicy with:

  • Filesystem: read-only system paths (/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log), read-write (/sandbox, /tmp, /dev/null), include_workdir: true
  • Landlock: best_effort
  • Process: run_as_user: sandbox, run_as_group: sandbox
  • No network policies (all network blocked)
  • No inference

Task 2: Make spec.policy optional in gateway create_sandbox

File: crates/navigator-server/src/grpc.rs

  • Remove the spec.policy.is_none() rejection
  • In get_sandbox_policy(), when spec.policy is None and no policy history exists, return policy: None, version: 0

Task 3: Modify UpdateSandboxPolicy to handle no-baseline case

File: crates/navigator-server/src/grpc.rs

When spec.policy is None:

  • Skip static field validation and network mode validation
  • Backfill spec.policy on the stored sandbox with the incoming policy
  • Proceed with normal version assignment and persistence

Task 4: Pass sandbox name to container via env var

Files:

  • crates/navigator-server/src/sandbox/mod.rs — Add NEMOCLAW_SANDBOX_NAME to apply_required_env()
  • crates/navigator-sandbox/src/main.rs — Add --sandbox-name / NEMOCLAW_SANDBOX_NAME CLI arg

Task 5: Add policy sync capability to sandbox gRPC client

File: crates/navigator-sandbox/src/grpc_client.rs

Add sync_policy() method that calls UpdateSandboxPolicy with the sandbox name and locally-resolved policy.

Task 6: Modify sandbox load_policy() for disk discovery + restrictive default fallback

Files:

  • crates/navigator-sandbox/src/lib.rs — Modify load_policy() gRPC branch
  • crates/navigator-sandbox/Cargo.toml — Add navigator-policy dependency

New gRPC mode flow:

1. Call GetSandboxPolicy
2. If policy returned → use it (unchanged)
3. If policy is None (version 0):
   a. Try to read /etc/navigator/policy.yaml
   b. If found → parse it, sync to gateway via UpdateSandboxPolicy
   c. If not found → use restrictive_default_policy(), sync to gateway
   d. Re-fetch from gateway (read-back to get version/hash)
   e. Proceed with the fetched policy

Task 7: Tests

navigator-policy tests:

  • restrictive_default_policy() returns valid policy with no network policies
  • restrictive_default_policy() has expected filesystem paths
  • parse_sandbox_policy() round-trips correctly

navigator-sandbox tests:

  • Policy disk discovery reads from /etc/navigator/policy.yaml when present
  • Falls back to restrictive default when file not found
  • Restrictive default produces NetworkMode::Block

navigator-server tests:

  • create_sandbox accepts None policy
  • get_sandbox_policy returns None/version 0 when no policy configured
  • update_sandbox_policy works when spec.policy is None (sets baseline)
  • update_sandbox_policy with established baseline still validates static fields

Design Decisions

  • Disk path: /etc/navigator/policy.yaml (standard Linux config location)
  • Sync flow: Both disk-found and restrictive default get synced to gateway, then re-read for consistency
  • Sandbox name: Passed via NEMOCLAW_SANDBOX_NAME env var (needed for UpdateSandboxPolicy RPC)
  • UpdateSandboxPolicy modification: Skip validation when no baseline exists, backfill spec.policy

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions