diff --git a/src/cli.ts b/src/cli.ts index f105ff89c..490f0f954 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1016,6 +1016,13 @@ program ' Example: 3000,8080 or 3000-3010,8000-8090' ) + .option( + '--enable-dind', + 'Enable Docker-in-Docker by exposing host Docker socket.\n' + + ' WARNING: allows firewall bypass via docker run', + false + ) + // -- API Proxy -- .option( '--enable-api-proxy', @@ -1343,6 +1350,7 @@ program enableHostAccess: options.enableHostAccess, allowHostPorts: options.allowHostPorts, sslBump: options.sslBump, + enableDind: options.enableDind, allowedUrls, enableApiProxy: options.enableApiProxy, openaiApiKey: process.env.OPENAI_API_KEY, diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 54e7e3743..062387221 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -676,7 +676,7 @@ describe('docker-manager', () => { expect(volumes.some((v: string) => v.includes('agent-logs'))).toBe(true); }); - it('should hide Docker socket', () => { + it('should hide Docker socket by default', () => { const result = generateDockerCompose(mockConfig, mockNetworkConfig); const agent = result.services.agent; const volumes = agent.volumes as string[]; @@ -686,6 +686,20 @@ describe('docker-manager', () => { expect(volumes).toContain('/dev/null:/host/run/docker.sock:ro'); }); + it('should expose Docker socket when enableDind is true', () => { + const dindConfig = { ...mockConfig, enableDind: true }; + const result = generateDockerCompose(dindConfig, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + + // Docker socket should be mounted read-write, not hidden + expect(volumes).toContain('/var/run/docker.sock:/host/var/run/docker.sock:rw'); + expect(volumes).toContain('/run/docker.sock:/host/run/docker.sock:rw'); + // Should NOT have /dev/null mounts + expect(volumes).not.toContain('/dev/null:/host/var/run/docker.sock:ro'); + expect(volumes).not.toContain('/dev/null:/host/run/docker.sock:ro'); + }); + it('should mount workspace directory under /host', () => { const result = generateDockerCompose(mockConfig, mockNetworkConfig); const agent = result.services.agent; diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 665023b78..65c1afe8d 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -720,13 +720,23 @@ export function generateDockerCompose( fs.writeFileSync(chrootHostsPath, hostsContent, { mode: 0o644 }); agentVolumes.push(`${chrootHostsPath}:/host/etc/hosts:ro`); - // SECURITY: Hide Docker socket to prevent firewall bypass via 'docker run' - // An attacker could otherwise spawn a new container without network restrictions - agentVolumes.push('/dev/null:/host/var/run/docker.sock:ro'); - // Also hide /run/docker.sock (symlink on some systems) - agentVolumes.push('/dev/null:/host/run/docker.sock:ro'); - - logger.debug('Selective mounts configured: system paths (ro), home (rw), Docker socket hidden'); + // SECURITY: Docker socket access control + if (config.enableDind) { + logger.warn('Docker-in-Docker enabled: agent can run docker commands (firewall bypass possible)'); + // Mount the real Docker socket into the chroot + const dockerSocketPath = '/var/run/docker.sock'; + agentVolumes.push(`${dockerSocketPath}:/host${dockerSocketPath}:rw`); + // Also expose the /run/docker.sock symlink if it exists + agentVolumes.push('/run/docker.sock:/host/run/docker.sock:rw'); + logger.debug('Selective mounts configured: system paths (ro), home (rw), Docker socket exposed'); + } else { + // Hide Docker socket to prevent firewall bypass via 'docker run' + // An attacker could otherwise spawn a new container without network restrictions + agentVolumes.push('/dev/null:/host/var/run/docker.sock:ro'); + // Also hide /run/docker.sock (symlink on some systems) + agentVolumes.push('/dev/null:/host/run/docker.sock:ro'); + logger.debug('Selective mounts configured: system paths (ro), home (rw), Docker socket hidden'); + } // Add SSL CA certificate mount if SSL Bump is enabled // This allows the agent container to trust the dynamically-generated CA diff --git a/src/types.ts b/src/types.ts index 34c4827c7..ecca38826 100644 --- a/src/types.ts +++ b/src/types.ts @@ -386,6 +386,19 @@ export interface WrapperConfig { */ sslBump?: boolean; + /** + * Enable Docker-in-Docker by exposing the host Docker socket + * + * When true, the host's Docker socket (/var/run/docker.sock) is mounted + * into the agent container, allowing the agent to run Docker commands. + * + * WARNING: This allows the agent to bypass firewall restrictions by + * spawning new containers without network restrictions. + * + * @default false + */ + enableDind?: boolean; + /** * URL patterns to allow for HTTPS traffic (requires sslBump: true) *