Skip to content

feat(sandbox): extend --share-agent-dir to Cursor, Codex, Hermes, and OpenCode#105

Open
mvanhorn wants to merge 1 commit intoInfisical:mainfrom
mvanhorn:feat/share-agent-dir-multi-agent
Open

feat(sandbox): extend --share-agent-dir to Cursor, Codex, Hermes, and OpenCode#105
mvanhorn wants to merge 1 commit intoInfisical:mainfrom
mvanhorn:feat/share-agent-dir-multi-agent

Conversation

@mvanhorn
Copy link
Copy Markdown

Summary

--share-agent-dir already listed every local agent command in knownAgents, but the container plumbing still hardcoded .claude/.claude.json. This PR wires the flag to the table so the host bind source and container mount point are derived from the command after --. Running it with an unknown command now errors out (previously silently used .claude).

Demo

Command Host dir Container path
claude ~/.claude (+ ~/.claude.json sibling, macOS Keychain bridge) /home/claude/.claude
cursor / agent ~/.cursor /home/claude/.cursor
codex ~/.codex + ~/.agents /home/claude/.codex + /home/claude/.agents
hermes ~/.hermes /home/claude/.hermes
opencode ~/.opencode /home/claude/.opencode

Codex is the one agent where the skills dir (~/.agents, used by maybeInstallSkills for the Agent Vault skill) and the state dir (~/.codex, auth/config/history) differ. The knownAgent.stateDir field plus an effectiveStateDir() helper keep the two concepts separate, and the container run emits a second bind mount for ~/.agents so the Agent Vault skill stays visible inside the sandbox alongside Codex's login.

The bundled sandbox image only preinstalls @anthropic-ai/claude-code. Running a non-Claude agent on it would fail with "executable file not found" after docker run, so --share-agent-dir with a non-Claude command now rejects early unless --image is supplied (docs already covered this; this change moves the failure from docker's exec to a clear CLI error). The existing security invariants from #103 are preserved: Linux uid-0 refusal, HOST_UID/HOST_GID remap, reservedContainerDsts coverage (now extended to block user --mount over the active skills dir too).

Pattern follows #101 for extending the agent table.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / build

Test plan

  • Existing tests pass (make test) — full suite, 16 packages green
  • Added tests for new behavior:
    • cmd/run_container_test.go: TestAgentContainerInfo_KnownAgents (covers all 5 agents including the Codex skills-vs-state split), TestAgentContainerInfo_Unknown, TestKnownAgentBases, TestRequireCustomImageForNonClaudeShare (the bundled-image gate)
    • internal/sandbox/docker_test.go: TestBuildRunArgs_HostAgentDirCursorBindMount, TestBuildRunArgs_HostAgentDirBindMount (existing Claude regression, now using the new helpers), TestBuildRunArgs_HostAgentSkillsDirBindMount (the Codex skills dir second mount), TestBuildRunArgs_RejectUserMountActiveAgentDir, TestBuildRunArgs_RejectUserMountActiveSkillsDir
  • Manual: go build produces a working binary; ./agent-vault vault run --help | grep share-agent-dir shows the updated per-agent flag description (scene 1 of the demo above)

Security checklist

  • No secrets or credentials in code
  • No new unauthenticated endpoints
  • Input validation on new API surfaces — --share-agent-dir now rejects unknown agent commands; BuildRunArgs rejects user --mount that would override the active state or skills dir
  • Checked for OWASP top 10 — no network/authn/injection surface introduced; the only new input is the agent command name, matched against knownAgents, plus --image presence

This contribution was developed with AI assistance.

… OpenCode

The flag now picks the bind source and container mount point from the
command after `--` instead of hardcoding ~/.claude. Unknown commands
are rejected with a list of supported bases.

knownAgents has two concepts: baseDir is the skills install directory
(~/<baseDir>/skills/), stateDir is where the agent stores auth/login
state. For Claude/Cursor/Hermes/OpenCode these are the same directory.
For Codex they differ: skills at ~/.agents/skills/, state at ~/.codex/.
The new stateDir field and effectiveStateDir() helper keep the two
concepts separate so --share-agent-dir binds the real state dir.

When an agent's baseDir differs from its state dir (Codex today),
--share-agent-dir emits a second bind mount (~/.agents ->
/home/claude/.agents) so the Agent Vault skill installed by
maybeInstallSkills at ~/.agents/skills/agent-vault/SKILL.md stays
visible inside the sandbox alongside the state dir.

Running a non-Claude agent with --share-agent-dir requires --image: the
bundled sandbox image only preinstalls @anthropic-ai/claude-code, so
agent-vault now rejects that combination before launching docker run
with a clear error instead of letting docker fail with "executable file
not found" after the fact.

Adds ContainerAgentHome/ContainerAgentConfig helpers and extends
sandbox.Config with HostAgentConfig/HostAgentSkillsDir plus matching
ContainerAgentDir/ContainerConfig/ContainerAgentSkillsDir fields so
the existing Claude path (sibling .claude.json bind, macOS Keychain
bridge, reserved-dst protection) stays intact and non-Claude agents
get the same treatment without it. reservedContainerDsts now also
blocks user --mount over the active skills dir.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@dangtony98
Copy link
Copy Markdown
Contributor

Thanks for this contribution @mvanhorn. Will take a look at this PR and review shortly :)

dangtony98 added a commit that referenced this pull request Apr 27, 2026
## Summary

Hard rename of `--sandbox=process|container` to
`--isolation=host|container` for `vault run`, plus the matching env var
(`AGENT_VAULT_SANDBOX` → `AGENT_VAULT_ISOLATION`) and internal package
(`internal/sandbox` → `internal/isolation`).

- **Why now**: future modes won't fit cleanly under "sandbox." Pre-1.0
is the lowest-cost moment to rename before docs/users ossify.
- **Why `host` over `process`**: today's "process" mode has no actual
process isolation — the only boundary is the agent honoring
`HTTPS_PROXY`. Naming it `host` makes the "no isolation" tradeoff
visible and nudges users toward `container` when they want a real
boundary. Also more accurate when future modes (Lima, devcontainers)
land — those run in a VM/container *from the host's perspective*.
- **No aliasing.** `--sandbox` and `AGENT_VAULT_SANDBOX` are removed,
not deprecated. Pre-1.0, the cost of a deprecation cycle outweighs the
small user impact.
- **Default behavior unchanged.** Same code paths, same security
properties, same default.

### Surfaces touched
- **Go**: `cmd/sandbox_flag.go` → `cmd/isolation_flag.go`
(`SandboxMode/Process/Container` → `IsolationMode/Host/Container`);
`internal/sandbox/` → `internal/isolation/` with embed var, image-repo
string (`agent-vault/sandbox` → `agent-vault/isolation`), host CA dir
(`~/.agent-vault/sandbox/` → `~/.agent-vault/isolation/`), and network
label (`agent-vault-sandbox` → `agent-vault-isolation`) all renamed for
consistency. Test files renamed: `internal/{mitm,ca}/sandbox_*_test.go`
→ `isolation_*_test.go`.
- **Agent skills** (versioned together per `CLAUDE.md`):
`cmd/skill_cli.md`, `cmd/skill_http.md`.
- **Docs**: `README.md`, `.env.example`, `docs/reference/cli.mdx`,
`docs/self-hosting/environment-variables.mdx`. Renamed
`docs/guides/container-sandbox.mdx` →
`docs/guides/container-isolation.mdx`; updated `docs/docs.json` nav.
`CLAUDE.md` "Two isolation modes" bullet rewritten.
`.github/dependabot.yml` docker directory path.

### One-time user impact
- Existing users will rebuild the docker image once on next `vault run
--isolation=container` use (image is hash-tagged so the old
`agent-vault/sandbox:*` orphans).
- Any pre-existing `~/.agent-vault/sandbox/` dir from prior runs becomes
orphaned (tiny PEM files; safe to `rm -rf`).
- Any orphan `agent-vault-sandbox`-labeled networks from
previously-crashed runs won't be auto-pruned.

All acceptable for a pre-1.0 hard rename; the plan accepted these
explicitly.

## Test plan

- [x] `go build ./...` — clean
- [x] `go test ./...` — all green (asset hash pin updated for the
Dockerfile comment edit)
- [x] `vault run --help` shows `--isolation` (default `host`);
`--sandbox` absent
- [x] `vault run --sandbox=process` rejected with `unknown flag:
--sandbox`
- [x] Repo-wide grep for
`AGENT_VAULT_SANDBOX|--sandbox|SandboxMode|SandboxProcess|SandboxContainer|internal/sandbox|/guides/container-sandbox`
returns zero hits
- [ ] Reviewer: check Mintlify renders
`docs/guides/container-isolation.mdx` and the nav entry
- [ ] Reviewer: rebase #105 (`feat(sandbox): extend --share-agent-dir`)
against this — its title and any flag references need updating

## Out of scope

- Adding Lima or devcontainer modes (the whole point of this rename is
to prep for those — they land in separate PRs).
- AWS SES / SocketLabs "sandbox mode" wording in `docs/guides/smtp.mdx`
(unrelated).
- Generic "sandboxed agents" SDK terminology in `README.md` and
`docs/quickstart/custom-agent.mdx` (describes external runtimes like
Daytona/E2B/Firecracker, not Agent Vault's flag).
- "vault run is a convenience wrapper, not a sandbox" sentence in
`docs/agents/overview.mdx` and `docs/guides/connect-coding-agent.mdx`
(still reads accurately for the default `host` mode).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants