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:
gavrielc
2026-02-22 20:57:57 +02:00
committed by GitHub
parent ef00320018
commit 5fb10645cd
3 changed files with 22 additions and 9 deletions

View File

@@ -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)