Skip to content

dstoc/cladding

Repository files navigation

Cladding lets you run an agent in a constrained container environment where network access is intentionally narrow:

  • The agent runs as a standalone --network none container named <name>-agent-instance.
  • Optional delegated sandboxes run as standalone --network none containers named <name>-nw-sandbox-instance and <name>-fs-sandbox-instance.
  • HTTP(S) egress is mediated by the <name>-proxy pod. Execution containers reach it through scoped Unix-domain socket mounts and local socat loopback bridges, and Squid uses separate listeners to distinguish agent traffic from network-sandbox traffic.
  • The agent can reach host.containers.internal only on ports listed in config-template/agent/host_ports.lst.
  • <name>-nw-sandbox-instance and <name>-fs-sandbox-instance each serve mcp-run on a mounted Unix socket and execute commands only when allowed by their Rego policy modules under .cladding/config/.
  • Proxy allow-lists come from:

In short: the agent cannot freely access the network; users can run sandbox commands from the host with cladding run-with-scissors, while commands running inside the agent can delegate to sandbox containers with run-in-nw-sandbox or run-in-fs-sandbox.

Getting Started

  • Install Podman

  • Install the cladding binary:

    podman run --rm -it \
      -v $HOME/.local/bin:/target/bin \
      rust:latest \
      cargo install --git https://github.com/dstoc/cladding cladding --root /target --force

    Alternative (install Rust locally first):

    # install Rust
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    # or on macOS with Homebrew
    brew install rust
    
    # from this repo
    cargo install --path . --bin cladding
    
    # or install directly from git
    cargo install --git https://github.com/dstoc/cladding cladding
  • Initialize local config:

    Config and mounts are stored in a .cladding directory.

    cladding init
    # or override generated name
    cladding init myproject
  • Edit files under .cladding/config/:

    Generated proxy scripts and Squid runtime config are refreshed under .cladding/runtime/scripts/ by cladding up; they are not user-editable config.

  • Build images and refresh host-mounted binaries (mcp-run, run-remote, and sandbox helper wrappers) in .cladding/tools/bin:

    cladding build
  • Start the environment:

    cladding up
  • Run commands in the agent container (workdir follows your host cwd relative to the directory containing .cladding):

    cladding run codex --yolo
    cladding run --env GEMINI_API_KEY gemini
  • Temporarily publish a TCP port from the agent container to the host while the project is running:

    cladding expose 3000
    cladding expose 3000 9000

    cladding expose runs in the foreground. Stop it with Ctrl-C.

  • Temporarily make one host-reachable TCP endpoint available on agent localhost while the project is running:

    cladding inject 11434
    cladding inject 5432 15432
    cladding inject db.internal:5432 15432

    cladding inject <host-endpoint> [containerport] runs in the foreground and stops with Ctrl-C. A bare port such as 11434 maps agent 127.0.0.1:11434 to host 127.0.0.1:11434. A second port changes only the agent-side listener, so cladding inject 5432 15432 maps agent 127.0.0.1:15432 to host 127.0.0.1:5432. An explicit host:port is resolved from the host and should be treated as a deliberate temporary exception for that one endpoint, not general host networking.

Configuring mounts

cladding.json supports a mounts list. Each entry has:

  • mount (required, absolute path in the container)
  • hostPath (optional, host bind mount; relative paths are resolved from .cladding/)
  • volume (optional, named volume; mutually exclusive with hostPath)
  • readOnly (optional, default false; ignored for volume mounts and forced true for empty mask mounts)
  • targets (optional; explicit list of agent, nw-sandbox, fs-sandbox)
  • ignore (optional, default false; when true, removes an existing default mount at the same mount path instead of replacing it)

If neither hostPath nor volume is set, a managed empty runtime volume is mounted read-only - this is intended for masking or hiding underlying files. Mounts apply to the components named in targets. When targets is omitted, the mount applies to the agent and to nw-sandbox when it is enabled. fs-sandbox only receives custom mounts that explicitly target fs-sandbox.

Example:

{
  "agent": {
    "image": "cladding-agent:local"
  },
  "nw_sandbox": {
    "enabled": true,
    "image": "cladding-sandbox:local"
  },
  "mounts": [
    { "mount": "/home/user/workspace/.cache/npm", "volume": "npm-cache" },
    { "mount": "/opt/data", "hostPath": "../data", "readOnly": true },
    { "mount": "/tmp/isolated" },
    { "mount": "/opt/nw-sandbox-only", "hostPath": "../nw-sandbox-data", "targets": ["nw-sandbox"] },
    { "mount": "/opt/fs-sandbox-only", "hostPath": "../fs-sandbox-data", "targets": ["fs-sandbox"] }
  ]
}

Default mounts for the agent and nw-sandbox (as if expressed via mounts):

{
  "mounts": [
    { "mount": "/opt/config", "hostPath": "config", "readOnly": true },
    { "mount": "/opt/tools", "hostPath": "tools", "readOnly": true },
    { "mount": "/home/user", "hostPath": "home" },
    { "mount": "/home/user/workspace", "hostPath": ".." },
    { "mount": "/home/user/workspace/.cladding" }
  ]
}

The fs-sandbox default mount set is narrower: /opt/config and /opt/tools are mounted read-only, plus an internal run socket under /run/cladding/run/fs-sandbox. It does not receive /home/user, /home/user/workspace, or the .cladding workspace mask unless custom mounts explicitly target fs-sandbox.

Default mounts may be overridden by adding an entry with the same mount value, or removed by adding an entry with the same mount and "ignore": true.

Architecture + Network Controls

Current runtime shape:

  • <name>-proxy is the only Podman pod. It contains <name>-proxy-instance and <name>-proxy-bridge.
  • <name>-agent-instance, <name>-nw-sandbox-instance, and <name>-fs-sandbox-instance are standalone execution containers with --network none.
  • Execution containers communicate through scoped Unix-domain socket mounts under .cladding/runtime/sockets.
  • use_runsc, when enabled, applies only to the standalone execution containers.
flowchart TB
  subgraph C["standalone: <name>-agent-instance"]
    CA[agent process]
    CAP[socat 127.0.0.1:3128]
  end

  subgraph H[volumes]
    WS[.cladding/..]
    HOME[.cladding/home]
  end


  subgraph S["standalone: <name>-nw-sandbox-instance"]
    SAP[socat 127.0.0.1:3128]
    SA[mcp-run on /run/cladding/run/nw-sandbox/run.sock]
  end

  subgraph F["standalone: <name>-fs-sandbox-instance"]
    FA[mcp-run on /run/cladding/run/fs-sandbox/run.sock]
  end

  subgraph P["pod: <name>-proxy"]
    PB[proxy bridge sidecar]
    PX1[Squid listener 127.0.0.1:3128]
    PX2[Squid listener 127.0.0.1:3129]
  end

  WS --> CA
  WS --> SA
  WS --> FA
  HOME --> CA
  HOME --> SA
  HOME --> FA

  CA -- run-remote over UDS --> SA
  CA -- run-remote over UDS --> FA
  CA -- HTTP(S) proxy env --> CAP
  CAP -- proxy/agent/proxy.sock --> PB
  SAP -- proxy/nw-sandbox/proxy.sock --> PB
  PB --> PX1
  PB --> PX2
  SA -- HTTP(S) proxy env --> SAP
  PX1 -- listener identity, allowlisted domains only --> NET[(Internet)]
  PX2 -- listener identity, allowlisted domains only --> NET
Loading

The filesystem sandbox has no proxy socket mount and no proxy environment by default. It can run allowed commands through mcp-run, but it is not given HTTP(S) egress.

Useful Commands

cladding init [name]  # initialize .cladding and config
cladding check        # verify required paths/images
cladding ps           # list running cladding projects
cladding run [--env KEY[=VALUE] ...] [cmd] # run a command in the agent container
cladding run-with-scissors [--target nw-sandbox|fs-sandbox] [--env KEY[=VALUE] ...] [cmd] # run a command in an enabled sandbox container
cladding expose <containerport> [hostport] # block while forwarding localhost hostport to agent containerport
cladding inject <host-endpoint> [containerport] # block while forwarding agent localhost containerport to a host-reachable endpoint
cladding reload-proxy # reconfigure squid after domain-list edits
cladding logs [agent|proxy|nw-sandbox|fs-sandbox] [podman logs args...] # view container logs
cladding down         # stop managed containers and the proxy pod
cladding destroy      # force-remove running containers
cladding up           # starts the containers
cladding logs proxy -f       # follow proxy logs
cladding logs nw-sandbox -f  # follow network sandbox (mcp-run) logs
cladding logs fs-sandbox -f  # follow filesystem sandbox (mcp-run) logs

Inside the agent container, use run-in-nw-sandbox -- <cmd> [args...] or run-in-fs-sandbox -- <cmd> [args...] to ask an enabled sandbox to run an allowlisted command. These wrappers call run-remote over the component-specific Unix socket injected into the agent environment.

About

Constrained container environment for running agents

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages