Skip to content

feat: inject read-only files into sandbox at creation time without custom images #1268

@maruiz93

Description

@maruiz93

Problem Statement

There is no mechanism to place tamper-proof configuration files inside a sandbox at creation time without building a custom container image.

The filesystem policy's read_only enforcement via Landlock is production-ready, but file delivery into read-only paths requires either baking files into an image or manually configuring driver-level bind mounts. The --upload mechanism cannot help here — it runs after Landlock is enforced as the sandbox user, so it can only write to read_write paths.

This blocks use cases where operators need to inject configuration, persona files, policy overrides, or credential bundles that the agent must read but never modify — without maintaining per-configuration image builds.

Proposed Design

Add a files field to SandboxSpec (or SandboxTemplate) that accepts a list of (path, content, mode) tuples:

message SandboxFile {
  string path = 1;      // Absolute destination path inside sandbox
  bytes content = 2;     // File content
  uint32 mode = 3;       // Unix file mode (e.g., 0o444), optional default
}

message SandboxSpec {
  // ... existing fields ...
  repeated SandboxFile files = 11;
}

Supervisor-side behavior:

  1. During prepare_filesystem() (runs as root, before Landlock), iterate over files entries
  2. Create parent directories, write file content, set ownership to root and mode to read-only
  3. Any paths covered by files entries are implicitly added to the Landlock read_only ruleset (or validated against an explicit read_only policy entry)
  4. Landlock is applied — agent process cannot modify these files

CLI surface:

openshell sandbox create \
    --file ./local/config.yaml:/etc/myapp/config.yaml \
    --file ./local/SOUL.md:/sandbox/.agent/SOUL.md:0444 \
    --policy ./policy.yaml \
    -- python /app/main.py

Key design constraints:

  • Files are written by the supervisor as root before privilege drop and Landlock enforcement — same trust model as the existing TLS CA file injection (write_ca_files())
  • Content travels through the existing gRPC channel (gateway → supervisor), not via host filesystem access — compatible with cloud/remote deployments unlike --mount
  • Size limits should be enforced (e.g., 1 MiB per file, 10 MiB total) to prevent abuse of the gRPC channel
  • Paths must pass the same validation as filesystem_policy entries (absolute, no .., max length)

Alternatives Considered

  • Custom images: Works but creates an image-per-configuration maintenance burden. Not practical when configs change frequently or vary per sandbox instance.
  • --mount / bind mounts (feat: Support host directory mounts (hostPath/PVC) via CLI for long-running sandboxes #500): Declined by maintainers due to security concerns with bidirectional host access and incompatibility with cloud deployments. File injection through gRPC avoids both issues — content flows through the gateway, not the host filesystem.
  • --upload to read-only paths: Not possible. Upload runs as the sandbox user after Landlock enforcement. Cannot write to read-only directories.
  • Environment variables for config: Unsuitable. The agent process can read and modify its own environment, env vars are visible in /proc/self/environ, and they don't support structured config files or binary content.

Agent Investigation

Codebase paths examined:

  • crates/openshell-sandbox/src/policy.rsFilesystemPolicy struct with read_only/read_write vecs. Enforcement infrastructure is complete.
  • crates/openshell-sandbox/src/sandbox/linux/landlock.rs — Landlock enforcement. prepare() opens PathFds as root; enforce() calls restrict_self() after privilege drop. File injection would slot between prepare and enforce.
  • crates/openshell-sandbox/src/lib.rsprepare_filesystem() (line ~2016) creates/chowns read-write dirs. write_ca_files() writes TLS certs as root before Landlock. This is the existing pattern for pre-Landlock file injection.
  • crates/openshell-sandbox/src/ssh.rs — Upload via tar-over-SSH. Runs in a child that drops privileges and enforces Landlock before exec (pre-exec hook, line ~1135). Cannot write to read-only paths.
  • proto/openshell.protoCreateSandboxRequest / SandboxSpec. No file content field exists. SandboxTemplate has environment and volume_claim_templates but no file injection.
  • crates/openshell-cli/src/run.rs — CLI sandbox creation. --upload parsed as LOCAL[:REMOTE], executes after sandbox reaches Ready state.

Related issues: #500 (host directory mounts — closed), #785 (volume mounts — open)

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions