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
9 changes: 6 additions & 3 deletions src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,8 @@ describe('docker-manager', () => {
tmpfs.forEach((mount: string) => {
expect(mount).toContain('noexec');
expect(mount).toContain('nosuid');
expect(mount).toContain('size=1m');
// Each mount must have a size limit (value varies: 1m for secrets, 65536k for /dev/shm)
expect(mount).toMatch(/size=\d+[mk]/);
});
});

Expand All @@ -1409,18 +1410,20 @@ describe('docker-manager', () => {
}
});

it('should include exactly 4 tmpfs mounts (mcp-logs + workDir, both normal and /host)', () => {
it('should include exactly 5 tmpfs mounts (mcp-logs + workDir both normal and /host, plus /host/dev/shm)', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
const agent = result.services.agent;
const tmpfs = agent.tmpfs as string[];

expect(tmpfs).toHaveLength(4);
expect(tmpfs).toHaveLength(5);
// Normal paths
expect(tmpfs.some((t: string) => t.includes('/tmp/gh-aw/mcp-logs:'))).toBe(true);
expect(tmpfs.some((t: string) => t.startsWith(`${mockConfig.workDir}:`))).toBe(true);
// /host-prefixed paths (chroot always on)
expect(tmpfs.some((t: string) => t.includes('/host/tmp/gh-aw/mcp-logs:'))).toBe(true);
expect(tmpfs.some((t: string) => t.startsWith(`/host${mockConfig.workDir}:`))).toBe(true);
// Writable /dev/shm for POSIX semaphores (chroot makes /host/dev read-only)
expect(tmpfs.some((t: string) => t.startsWith('/host/dev/shm:'))).toBe(true);
});
});

Expand Down
9 changes: 9 additions & 0 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,11 +833,20 @@ export function generateDockerCompose(
//
// Hide both normal and /host-prefixed paths since /tmp is mounted at both
// /tmp and /host/tmp in chroot mode (which is always on)
//
// /host/dev/shm: /dev is bind-mounted read-only (/dev:/host/dev:ro), which makes
// /dev/shm read-only after chroot /host. POSIX semaphores and shared memory
// (used by python/black's blackd server and other tools) require a writable /dev/shm.
// A tmpfs overlay at /host/dev/shm provides a writable, isolated in-memory filesystem.
// Security: Docker containers use their own IPC namespace (no --ipc=host), so shared
// memory is fully isolated from the host and other containers. Size is capped at 64MB
// (Docker's default). noexec and nosuid flags restrict abuse vectors.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The comment mentions "noexec and nosuid flags restrict abuse vectors" but doesn't mention the nodev flag that's also applied in line 849. Consider updating the comment to include nodev for completeness: "noexec, nosuid, and nodev flags restrict abuse vectors."

Suggested change
// (Docker's default). noexec and nosuid flags restrict abuse vectors.
// (Docker's default). noexec, nosuid, and nodev flags restrict abuse vectors.

Copilot uses AI. Check for mistakes.
tmpfs: [
'/tmp/gh-aw/mcp-logs:rw,noexec,nosuid,size=1m',
'/host/tmp/gh-aw/mcp-logs:rw,noexec,nosuid,size=1m',
`${config.workDir}:rw,noexec,nosuid,size=1m`,
`/host${config.workDir}:rw,noexec,nosuid,size=1m`,
'/host/dev/shm:rw,noexec,nosuid,nodev,size=65536k',
],
depends_on: {
'squid-proxy': {
Expand Down
Loading