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 --stdiowithMATAPAN_OBOLset — see docs/quickstart-claude.md. - ChatGPT (HTTPS tunnel): authenticated StreamableHTTP at
/mcpvia a user-managed tunnel — see docs/quickstart-chatgpt.md.
Tool surface
- Profiles (
mcp.tool_profilein config.json, defaultminimal): minimal is the core 12 tools;fulladds typedworkspace_file_globandworkspace_file_grepfor hosts that prefer typed search over shellrg/find(DevSpace parity). - Capability discovery:
matapan_capabilitiesreturns 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.mdare discovered and returned onworkspace_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
noneunless the workspace has a grant from a human (matapan workspace create --egress DOMAINor theworkspace_grant_egresstool, scopeworkspace.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 aspolicy_digest. - Secrets:
matapan secret setstores 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 asMATAPAN_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_urlswitches token validation to the Charon validate endpoint (5s, fail-closed, audiencematapanenforced 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: oauthmakes 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 clientchatgpt-mcp, metadata discovery under/.well-known/, and an owner approval gate: the authorize endpoint renders a form demanding the owner password (MATAPAN_OAUTH_OWNER_PASSWORDat 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-specificagent-<client_id>principal (AgentScopes: noworkspace.grant, noproposal.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-setupprints the exact connector instructions. - Audit retention:
ledger.retention_days(90) andledger.max_entries(1e6); the startup/daily sweeper compacts with an anchor entry soVerifystill 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.
| Variable | Meaning | Default |
|---|---|---|
MATAPAN_LISTEN_ADDR | HTTP bind address | 127.0.0.1:18777 |
MATAPAN_DB_PATH | SQLite control db | ~/.matapan/matapan.db |
MATAPAN_WORKSPACE_ROOT | workspace root dir | ~/.matapan/workspaces |
MATAPAN_DOCKER_IMAGE | digest-pinned run image | alpine:3.23 pinned |
MATAPAN_OBOL_KEY_PATH | obol HMAC key (0600) | ~/.matapan/obol.key |
MATAPAN_SECRET_KEY_PATH | secrets AES key (0600) | ~/.matapan/secret.key |
MATAPAN_ALLOW_LAN | true/false non-loopback bind | false |
MATAPAN_TOOL_PROFILE | minimal/full | minimal |
MATAPAN_AUTH_MODE | local/charon | local |
MATAPAN_CHARON_URL | Charon validate endpoint | — |
MATAPAN_GC_IDLE_HOURS | idle workspace expiry (int) | 72 |
MATAPAN_LEDGER_RETENTION_DAYS | audit retention (int) | 90 |
MATAPAN_LEDGER_MAX_ENTRIES | audit cap (int) | 1000000 |
MATAPAN_RUNTIME_ISOLATION | docker/gvisor | docker |
MATAPAN_OAUTH_CLIENT_ID | OAuth 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/execargv arrays; there is nosh -cpath in the codebase. - Workspace path defense. Every file tool call re-canonicalizes with
filepath.EvalSymlinksand 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: noneby 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.sockmount. Containers are namedmatapan-<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 tomatapan. - 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.