Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 15 additions & 1 deletion src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -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');
});
Comment on lines +689 to +701

it('should mount workspace directory under /host', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
const agent = result.services.agent;
Expand Down
24 changes: 17 additions & 7 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Comment on lines +728 to +730
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
Expand Down
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Comment on lines +392 to +399
enableDind?: boolean;

/**
* URL patterns to allow for HTTPS traffic (requires sslBump: true)
*
Expand Down
Loading