Security

Local-first, single-user threat model.

Report vulnerabilities privately. Do not open a public issue for a security flaw.

Reporting

Mail security@lemma.ventures with what you found and how to reproduce it. We will acknowledge within a week. Please give us 90 days before publishing.

Do not send AI-generated bug reports you have not verified yourself.

Threat model

Captain Code is a single-user, local tool. It assumes:

  • The machine is yours and not shared with an untrusted user
  • The repositories you point it at are ones you are allowed to modify
  • The credentials on the machine are yours to use

Under those assumptions it holds no secrets of its own, opens no inbound port beyond loopback, and sends nothing anywhere except the prompts you asked a provider to answer.

Workers run with approvals disabled

By default Captain Code drives workers with the vendor CLI's approval prompts turned off (--dangerously-skip-permissions and equivalents), and captain init writes a permissive opencode ruleset. This is deliberate: a headless worker that stops to ask a question waits forever.

A worker can run any command your user account can run in the directory it was given. It is the same authority the vendor CLI has when you run it interactively and approve everything, without the pause.

If that is not acceptable:

  • Keep approvals on via CAPTAIN_CLAUDE_PERMISSIONS and equivalents
  • Use a sandbox mode for Codex (CAPTAIN_CODEX_CLI_SANDBOX)
  • Run in a container or VM with only the repository mounted
  • Do not run as root, and do not run on a machine holding credentials the work does not need

captain init also narrows what a worker may read, notably denying the read tool on .env files. That is a guard rail against an accidental read, not a security boundary: a shell command can still read any file the user can.

Workers are processes, not sandboxes

A worker is an ordinary subprocess of the brain, with the working directory set and the brain's entire environment inherited, including every credential in it. The only isolation is a git worktree: a worktree isolates Git changes, not processes, credentials, or network access.

The egress proxy is a closed set of provider origins, and CAPTAIN_EGRESS_ALLOW narrows it further. That bounds captain's own model traffic. It is not a network boundary for a worker: curl in a bash tool call does not pass through the proxy at all.

The action gate

Because there is nobody to ask, captain screens actions with a classifier instead of a prompt: three calibrated nouls on the decision leg over the command a worker is about to run (irreversible destruction, out of scope, exfiltration). It runs as a Claude Code PreToolUse hook and from the opencode plugin's tool boundary.

Its limits:

  • It defaults to shadow: it records and allows. Enforcement is opt-in.
  • A failed or slow call allows. It is an availability-preserving gate.
  • With no decision leg configured it does nothing at all.
  • It is a classifier. It will be wrong in both directions, and it is not a substitute for a container, a VM, or a machine without the credentials.

What we do protect

  • Shield (secrets masking) is on by default. Recognisable credential shapes in tool output and provider request bodies are replaced with stable placeholders before a model or remote API sees them; the sidebar shield line counts what was masked. See docs/SECRETS.md.
  • Credential values are never read by doctor (keys only)
  • Journals and digests are scrubbed of common token shapes before write
  • Cross-project context sharing is off by default
  • Digests and state are written 0600

Do not even post credentials in prompts, even if Captain Code provides a shield. The shield masks common key shapes at the tool boundary and on the wire; a bare passphrase, an unusual token, or a secret typed in prose still reaches the provider. Treat every prompt as leaving your machine.

Known limitations

  • Prompts reach third-party providers. Treat every leg as an external recipient.
  • Shield is pattern matching, not judgment: secrets without a recognisable shape pass through, and cursor-agent traffic is outside the proxy.
  • A malicious repository can influence a worker through its own files (AGENTS.md, fixtures).
  • Most brain routes on the loopback port have no authentication. Anything running as your user can drive chat/route. The versioned task API (/v1/task/*) is loopback-only by default; set CAPTAIN_TASK_TOKEN and send Authorization: Bearer … if you expose it beyond loopback. captain task mcp talks to that same surface.