fix: mount project root read-only to prevent container escape (#392)
The main group's project root was mounted read-write, allowing the container agent to modify host application code (e.g. dist/container-runner.js) to inject arbitrary mounts on next restart — a full sandbox escape. Fix: mount the project root read-only. Writable paths the agent needs (group folder, IPC, .claude/) are already mounted separately. The agent-runner source is now copied into a per-group writable location so agents can still customize container-side behavior without affecting host code or other groups. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,10 @@ private_key, .secret
|
||||
- Container path validation (rejects `..` and absolute paths)
|
||||
- `nonMainReadOnly` option forces read-only for non-main groups
|
||||
|
||||
**Read-Only Project Root:**
|
||||
|
||||
The main group's project root is mounted read-only. Writable paths the agent needs (group folder, IPC, `.claude/`) are mounted separately. This prevents the agent from modifying host application code (`src/`, `dist/`, `package.json`, etc.) which would bypass the sandbox entirely on next restart.
|
||||
|
||||
### 3. Session Isolation
|
||||
|
||||
Each group has isolated Claude sessions at `data/sessions/{group}/.claude/`:
|
||||
@@ -82,7 +86,7 @@ const allowedVars = ['CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_API_KEY'];
|
||||
|
||||
| Capability | Main Group | Non-Main Group |
|
||||
|------------|------------|----------------|
|
||||
| Project root access | `/workspace/project` (rw) | None |
|
||||
| Project root access | `/workspace/project` (ro) | None |
|
||||
| Group folder | `/workspace/group` (rw) | `/workspace/group` (rw) |
|
||||
| Global memory | Implicit via project | `/workspace/global` (ro) |
|
||||
| Additional mounts | Configurable | Read-only unless allowed |
|
||||
|
||||
@@ -61,11 +61,11 @@ This is the **main channel**, which has elevated privileges.
|
||||
|
||||
## Container Mounts
|
||||
|
||||
Main has access to the entire project:
|
||||
Main has read-only access to the project and read-write access to its group folder:
|
||||
|
||||
| Container Path | Host Path | Access |
|
||||
|----------------|-----------|--------|
|
||||
| `/workspace/project` | Project root | read-write |
|
||||
| `/workspace/project` | Project root | read-only |
|
||||
| `/workspace/group` | `groups/main/` | read-write |
|
||||
|
||||
Key paths inside the container:
|
||||
|
||||
@@ -67,11 +67,15 @@ function buildVolumeMounts(
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
if (isMain) {
|
||||
// Main gets the entire project root mounted
|
||||
// Main gets the project root read-only. Writable paths the agent needs
|
||||
// (group folder, IPC, .claude/) are mounted separately below.
|
||||
// Read-only prevents the agent from modifying host application code
|
||||
// (src/, dist/, package.json, etc.) which would bypass the sandbox
|
||||
// entirely on next restart.
|
||||
mounts.push({
|
||||
hostPath: projectRoot,
|
||||
containerPath: '/workspace/project',
|
||||
readonly: false,
|
||||
readonly: true,
|
||||
});
|
||||
|
||||
// Main also gets its group folder as the working directory
|
||||
@@ -155,13 +159,18 @@ function buildVolumeMounts(
|
||||
readonly: false,
|
||||
});
|
||||
|
||||
// Mount agent-runner source from host — recompiled on container startup.
|
||||
// Bypasses sticky build cache for code changes.
|
||||
// Copy agent-runner source into a per-group writable location so agents
|
||||
// can customize it (add tools, change behavior) without affecting other
|
||||
// groups. Recompiled on container startup via entrypoint.sh.
|
||||
const agentRunnerSrc = path.join(projectRoot, 'container', 'agent-runner', 'src');
|
||||
const groupAgentRunnerDir = path.join(DATA_DIR, 'sessions', group.folder, 'agent-runner-src');
|
||||
if (!fs.existsSync(groupAgentRunnerDir) && fs.existsSync(agentRunnerSrc)) {
|
||||
fs.cpSync(agentRunnerSrc, groupAgentRunnerDir, { recursive: true });
|
||||
}
|
||||
mounts.push({
|
||||
hostPath: agentRunnerSrc,
|
||||
hostPath: groupAgentRunnerDir,
|
||||
containerPath: '/app/src',
|
||||
readonly: true,
|
||||
readonly: false,
|
||||
});
|
||||
|
||||
// Additional mounts validated against external allowlist (tamper-proof from containers)
|
||||
|
||||
Reference in New Issue
Block a user