matapan

Give scoped API access to Chatgpt or Claude to your repo via a docker container. isolated by default changes don't hit you repo unless you allow it

Documentation

Matapan

Matapan is a local-first transactional control plane for agent-produced code. Coding agents (ChatGPT, Claude, Codex) get disposable, container-isolated workspaces and return work only as immutable, evidence-bearing proposals, applied via compare-and-swap. Your active checkout is never in the agent's write path.

The loop:

import trusted source → disposable isolated workspace → scoped tools/runtime → capture edits + verification → seal immutable proposal against an expected base → review → compare-and-swap apply → revoke and destroy → redacted audit trail remains.

Matapan is a workspace transaction manager — not an IDE, not an agent, not a generic sandbox, not a Git GUI.

Status

0.1.0-beta.1 — all eight planned sprints are landed: security core, workspace lifecycle (leases + GC), hardened runtime (gVisor experimental), MCP tool surface, MVP proposal engine, policy profiles, secrets broker, egress proxy, audit retention, Charon adapter boundary, packaging, and adversarial hardening (fuzz + edge matrix). See CHANGELOG.md, docs/MVP-acceptance.md, and docs/docker-limitations.md.

Quickstart

From zero, on macOS or Linux (requires git, Go 1.25.12+ to build from source, and a local Docker daemon for workspace_run):

# Install (builds and installs to /usr/local/bin, or --user for ~/.local/bin)
scripts/install.sh

# Verify the environment
matapan doctor
# git                    ok   git version 2.50.1
# config                 ok   ~/.matapan/config.json
# database               ok   ~/.matapan/matapan.db (schema v7)
# obol key               ok   ~/.matapan/obol.key (perms 600)
# docker                 ok   daemon reachable
# runtime image          ok   present, digest matches
# runtime spawn          ok   hardened container ran, exit 0

# Pre-pull the digest-pinned runtime image
matapan runtime pull

# Create a workspace from a git repo (worktree at an exact commit)
matapan workspace create --repo ~/src/my-app --base main
# {"ID": "019f…", "State": "ready", "Branch": "matapan/019f…", …}

# Start the daemon, then connect an agent (see docs/quickstart-claude.md)
matapand &

The agent then drives workspace_file_edit / workspace_run / workspace_commit against that workspace ID. When the work is done:

# Seal the workspace into an immutable proposal (via the agent or MCP),
# then review and apply with compare-and-swap:
matapan proposal list
matapan proposal show <id>            # diff, digests, evidence, lineage
matapan proposal apply <id> --expected-base <commit>
# {"ProposalID": "019f…", "AppliedHead": "7f84…", "Strategy": "auto",
#  "Rollback": "git -C ~/src/my-app reset --hard <commit>"}

# Housekeeping
matapan workspace destroy <id>        # verified teardown
matapan gc --dry-run                  # what idle/expired GC would collect

Other source types: --source snapshot --path /dir (defended copy of a non-Git directory), --source fresh [--git-init] (empty scaffold). Useful flags: --profile restricted (policy profile), --lease 24h (GC expiry), --egress proxy.example.com (human egress grant).

Connecting agents

  • Claude Code / local agents (stdio): matapand --stdio with MATAPAN_OBOL set — see docs/quickstart-claude.md.
  • ChatGPT (HTTPS tunnel): authenticated StreamableHTTP at /mcp via a user-managed tunnel — see docs/quickstart-chatgpt.md.

Tool surface

  • Profiles (mcp.tool_profile in config.json, default minimal): minimal is the core 12 tools; full adds typed workspace_file_glob and workspace_file_grep for hosts that prefer typed search over shell rg/find (DevSpace parity).
  • Capability discovery: matapan_capabilities returns the server version, profile, tools + annotations, hard clamps, and feature flags so any agent can self-configure.
  • Typed errors: tool failures return a structured {"error": {"code", "message"}} field with stable codes (unauthorized, not_found, path_escape, workspace_locked, stale_base, spec_refused, state_conflict, invalid_argument, egress_denied, unsupported, unavailable, internal).
  • Instructions: root AGENTS.md/CLAUDE.md are discovered and returned on workspace_status, and their digests are recorded in every proposal seal. Instruction content is data for the agent, never control input — it cannot change policy, scopes, or limits.
  • Egress grants: network stays none unless the workspace has a grant from a human (matapan workspace create --egress DOMAIN or the workspace_grant_egress tool, scope workspace.grant — agent principals don't get it). Granted runs go through the matapan egress proxy (allowlisting CONNECT/HTTP proxy): HTTP(S) to granted domains works, everything else is denied and ledgered. Enforcement honesty: on native Linux Docker the container attaches to an internal network with no direct route out (full enforcement); on Docker Desktop the container runs on the default bridge with the proxy as its only configured path — HTTP(S) is filtered, but raw-IP egress is a documented gap until the sidecar proxy lands.

Policy profiles, secrets, and identity

  • Policy profiles (default, restricted, open; matapan policy list/show): image allowlists, egress policy, argv[0] command denylists, resource ceilings. Assigned per workspace at create (--profile); the profile digest is sealed into every proposal as policy_digest.
  • Secrets: matapan secret set stores values AES-256-GCM-encrypted (key at 0600, never plaintext at rest; value read from stdin or --env, never argv). Humans grant secrets to workspaces (workspace_grant_secret / matapan secret grant); runs inject only granted secrets as MATAPAN_SECRET_<NAME> env vars; grants are revoked automatically at proposal seal and workspace destroy. Values are registered with the ledger redactor at injection time.
  • Charon mode: auth.mode: charon + auth.charon_url switches token validation to the Charon validate endpoint (5s, fail-closed, audience matapan enforced either way). review.prevent_self_approval (default true in charon mode) blocks a proposal's creator from applying or rejecting it — independent review.
  • OAuth mode: auth.mode: oauth makes the instance a full OAuth 2.0 authorization server for connectors that require OAuth (ChatGPT, Claude). HS256 JWT access tokens (24h, same HMAC key file as obols), public PKCE client chatgpt-mcp, metadata discovery under /.well-known/, and an owner approval gate: the authorize endpoint renders a form demanding the owner password (MATAPAN_OAUTH_OWNER_PASSWORD at daemon start — hashed in memory, never stored; startup refuses oauth mode without it). No open enrollment: only the configured client and allowlisted redirect URIs are accepted. Tokens map to a connector-specific agent-<client_id> principal (AgentScopes: no workspace.grant, no proposal.apply) — the connector cannot grant egress or secrets and cannot apply proposals; apply stays human. The approval page renders the exact effective scopes. Obols keep working alongside JWTs. matapan config oauth-setup prints the exact connector instructions.
  • Audit retention: ledger.retention_days (90) and ledger.max_entries (1e6); the startup/daily sweeper compacts with an anchor entry so Verify still validates the retained tail.

Runtime configuration

The runtime section of config.json sets per-run defaults (timeout_sec, memory_mb, pids_limit, nano_cpus, max_output_bytes). Per-call values may lower these but never raise them past the hard clamps, which are constants in internal/runtime (not config): timeout ≤ 10 min, memory ≤ 4 GiB, captured/streamed output ≤ 4 MiB. Images are digest-pinned (docker_image) and verified after every pull; provenance lives in the runtime_images table (matapan runtime images).

Configuration precedence and env overrides

Precedence, always: env > file > defaults. Every MATAPAN_* variable is applied after the config file loads, before validation; invalid values fail closed with an error naming the variable.

VariableMeaningDefault
MATAPAN_LISTEN_ADDRHTTP bind address127.0.0.1:18777
MATAPAN_DB_PATHSQLite control db~/.matapan/matapan.db
MATAPAN_WORKSPACE_ROOTworkspace root dir~/.matapan/workspaces
MATAPAN_DOCKER_IMAGEdigest-pinned run imagealpine:3.23 pinned
MATAPAN_OBOL_KEY_PATHobol HMAC key (0600)~/.matapan/obol.key
MATAPAN_SECRET_KEY_PATHsecrets AES key (0600)~/.matapan/secret.key
MATAPAN_ALLOW_LANtrue/false non-loopback bindfalse
MATAPAN_TOOL_PROFILEminimal/fullminimal
MATAPAN_AUTH_MODElocal/charonlocal
MATAPAN_CHARON_URLCharon validate endpoint
MATAPAN_GC_IDLE_HOURSidle workspace expiry (int)72
MATAPAN_LEDGER_RETENTION_DAYSaudit retention (int)90
MATAPAN_LEDGER_MAX_ENTRIESaudit cap (int)1000000
MATAPAN_RUNTIME_ISOLATIONdocker/gvisordocker
MATAPAN_OAUTH_CLIENT_IDOAuth public client_id (oauth mode)chatgpt-mcp

Container

scripts/docker-build.sh matapan:local   # builds + version-stamps the image

docker run -d --name matapand \
  -p 127.0.0.1:18777:18777 \
  -v /etc/matapan/config.json:/etc/matapan/config.json:ro \
  -v ~/.matapan:/data \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$HOME/src:$HOME/src" \
  matapan:local

Path parity is required: the workspace root and any source repos must be mounted at the same absolute path inside and outside the container — matapand bind-mounts workspace dirs into run containers and runs git worktree ops against host paths. See docs/operations.md for the full container deployment guide (including the per-model compose layout) and the docker.sock trust note. The image runs as root (documented in the Dockerfile): the Docker socket's group varies by host; agent workloads still run under the full hardened profile.

Security summary

  • No shell anywhere. All subprocesses are os/exec argv arrays; there is no sh -c path in the codebase.
  • Workspace path defense. Every file tool call re-canonicalizes with filepath.EvalSymlinks and rejects .. escapes and symlink escapes from the workspace root, with a TOCTOU re-check on open.
  • Hardened container runtime. Non-root user, CapDrop: ALL, no-new-privileges, default seccomp, memory/CPU/PID limits, read-only rootfs, NetworkMode: none by default (explicit egress grant required), digest-pinned images with post-pull digest verification, explicit env allowlist (no ambient host env), and a hard refusal of privileged mode, host PID/IPC, and any /var/run/docker.sock mount. Containers are named matapan-<workspace>-<seq> and labeled so destroy and reconciliation can find them; cleanup is verified, not trusted. Command evidence includes exit code, OOM-kill flag, verified image digest, termination reason, and bounded (truncation-marked) output.
  • Immutable proposals + CAS apply. A proposal seals the diff, base/head commits, and a content digest. Apply verifies the target branch head equals both the proposal base and your expected_base, merges in a temporary worktree first, and is idempotent on replay. Stale base returns a typed error carrying the current head.
  • Audit ledger. Append-only, hash-chained, redacted before persistence — secret values are never stored (ledger.Redact).
  • Obol auth. HMAC-SHA256 bearer tokens (obol_<id>_<secret_hex>), revocable, audience hardcoded to matapan.
  • Snapshot import defense. Non-Git sources are copied, never referenced: source root canonicalized, escaping symlinks skipped and recorded, FIFOs/devices rejected, byte/file caps, TOCTOU-defended source opens.
  • Lifecycle safety. Per-workspace locking (in-process mutex + flock, crash-safe), destroy allowed from any state with post-delete verification, startup reconciliation for stuck/orphaned workspaces, and Matapan-labeled containers killed on destroy.

Honest isolation language: this is hardened container isolation, not "safe execution". gVisor/Kata/microVM profiles are the higher-assurance path (gVisor is experimental as of Sprint 8). Full honest detail: docs/docker-limitations.md.

Layout

cmd/matapand        daemon (MCP over stdio or authenticated HTTP)
cmd/matapan         CLI (workspace/proposal/policy/secret/gc/runtime/doctor/config)
internal/config     daemon config + obol signing key load/generate
internal/obol       HMAC-SHA256 token service (audience "matapan")
internal/auth       identity adapter (local obols / Charon validate endpoint)
internal/policy     principals, scopes, workspace grants, named policy profiles
internal/ledger     hash-chained append-only audit + retention sweeps
internal/idempotency key-based dedup
internal/store      SQLite schema, migrations, typed store
internal/workspace  lifecycle, worktrees, snapshots, locking, leases + GC,
                    canonical path defense
internal/reconcile  startup crash recovery + orphan reconciliation
internal/runtime    hardened Docker exec, verified images, egress proxy wiring
internal/egressproxy allowlisting CONNECT/HTTP egress proxy
internal/secrets    AES-256-GCM secret registry + grant lifecycle
internal/proposal   seal, CAS apply strategies, conflict handling, revise
internal/testparse  JUnit/TAP evidence parsing (matapan-observed)
internal/mcpserver  workspace-scoped MCP tools (mcp-go v0.56.0)
internal/httpserver daemon HTTP (/api/health, /mcp)
scripts/            install.sh + release.sh packaging
docs/               threat model, ADRs, quickstarts, operations, acceptance

License

MIT — Copyright (c) 2026 OpenLethe. See LICENSE.