From f661393b268f4d29602973e927e6a4ae9b8dcc23 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:02:52 +0000 Subject: [PATCH 1/2] Initial plan From 00015078da4ff6708e2d6268d1f215c93815915b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:14:33 +0000 Subject: [PATCH 2/2] fix: use Docker default allowlist seccomp profile Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> --- containers/agent/seccomp-profile.json | 58 --------------------------- package.json | 3 +- src/docker-manager.test.ts | 13 +++--- src/docker-manager.ts | 27 +++---------- 4 files changed, 14 insertions(+), 87 deletions(-) delete mode 100644 containers/agent/seccomp-profile.json diff --git a/containers/agent/seccomp-profile.json b/containers/agent/seccomp-profile.json deleted file mode 100644 index 0643e3e44..000000000 --- a/containers/agent/seccomp-profile.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "defaultAction": "SCMP_ACT_ALLOW", - "architectures": [ - "SCMP_ARCH_X86_64", - "SCMP_ARCH_X86", - "SCMP_ARCH_AARCH64" - ], - "syscalls": [ - { - "names": [ - "ptrace", - "process_vm_readv", - "process_vm_writev" - ], - "action": "SCMP_ACT_ERRNO", - "errnoRet": 1, - "comment": "Block process inspection/modification" - }, - { - "names": [ - "kexec_load", - "kexec_file_load", - "reboot", - "init_module", - "finit_module", - "delete_module", - "acct", - "swapon", - "swapoff", - "pivot_root", - "syslog", - "add_key", - "request_key", - "keyctl", - "uselib", - "personality", - "ustat", - "sysfs", - "vhangup", - "get_kernel_syms", - "query_module", - "create_module", - "nfsservctl" - ], - "action": "SCMP_ACT_ERRNO", - "errnoRet": 1 - }, - { - "names": [ - "umount", - "umount2" - ], - "action": "SCMP_ACT_ERRNO", - "errnoRet": 1, - "comment": "Block unmounting filesystems - mount is allowed for procfs but unmount is not needed" - } - ] -} diff --git a/package.json b/package.json index 1b80d7c2d..c660500ad 100644 --- a/package.json +++ b/package.json @@ -80,8 +80,7 @@ "pkg": { "scripts": "dist/**/*.js", "assets": [ - "node_modules/chalk/**/*", - "containers/agent/seccomp-profile.json" + "node_modules/chalk/**/*" ], "targets": [ "node18-linux-x64", diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index a431c66c0..f1b6acd09 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -986,8 +986,9 @@ describe('docker-manager', () => { 'MKNOD', ]); - // Verify seccomp profile is configured - expect(agent.security_opt).toContain('seccomp=/tmp/awf-test/seccomp-profile.json'); + // Verify Docker's built-in default seccomp profile is used (allowlist-based, SCMP_ACT_ERRNO default) + // No custom seccomp profile - Docker applies its default which blocks ~44 dangerous syscall families + expect(agent.security_opt).not.toContainEqual(expect.stringContaining('seccomp=')); // Verify no-new-privileges is enabled to prevent privilege escalation expect(agent.security_opt).toContain('no-new-privileges:true'); @@ -1851,11 +1852,11 @@ describe('docker-manager', () => { workDir: newWorkDir, }; - // writeConfigs may succeed if seccomp profile is found, or fail if not + // writeConfigs may succeed or fail depending on SSL/other config try { await writeConfigs(config); } catch { - // Expected to fail if seccomp profile not found, but directories should still be created + // Expected to fail in test environment, but directories should still be created } // Verify work directory was created @@ -1938,7 +1939,7 @@ describe('docker-manager', () => { // May fail after writing configs } - // Verify squid.conf was created (it's created before seccomp check) + // Verify squid.conf was created const squidConfPath = path.join(testDir, 'squid.conf'); if (fs.existsSync(squidConfPath)) { const content = fs.readFileSync(squidConfPath, 'utf-8'); @@ -1984,7 +1985,7 @@ describe('docker-manager', () => { try { await writeConfigs(config); } catch { - // May fail if seccomp profile not found + // May fail in test environment } // Verify directory was created with restricted permissions diff --git a/src/docker-manager.ts b/src/docker-manager.ts index aff4bad9a..691763660 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -876,13 +876,17 @@ export function generateDockerCompose( 'SYS_RAWIO', // Prevents raw I/O access 'MKNOD', // Prevents device node creation ], - // Apply seccomp profile and no-new-privileges to restrict dangerous syscalls and prevent privilege escalation + // Security hardening via Docker's built-in default seccomp profile (allowlist-based, SCMP_ACT_ERRNO default). + // Docker's default profile blocks ~44 dangerous syscall families while allowing standard container operations. + // Dangerous syscalls like mount/chroot are conditionally allowed based on capabilities (CAP_SYS_ADMIN, + // CAP_SYS_CHROOT), which are irrevocably dropped via capsh before user code runs. + // ptrace/process_vm_readv/process_vm_writev are blocked by cap_drop: SYS_PTRACE above. + // no-new-privileges prevents SUID/SGID privilege escalation. // AppArmor is set to unconfined to allow mounting procfs at /host/proc // (Docker's default AppArmor profile blocks mount). This is safe because SYS_ADMIN is // dropped via capsh before user code runs, so user code cannot mount anything. security_opt: [ 'no-new-privileges:true', - `seccomp=${config.workDir}/seccomp-profile.json`, 'apparmor:unconfined', ], // Resource limits to prevent DoS attacks (conservative defaults) @@ -1196,25 +1200,6 @@ export async function writeConfigs(config: WrapperConfig): Promise { logger.debug(`Using network config: ${networkConfig.subnet} (squid: ${networkConfig.squidIp}, agent: ${networkConfig.agentIp}, api-proxy: ${networkConfig.proxyIp})`); - // Copy seccomp profile to work directory for container security - const seccompSourcePath = path.join(__dirname, '..', 'containers', 'agent', 'seccomp-profile.json'); - const seccompDestPath = path.join(config.workDir, 'seccomp-profile.json'); - if (fs.existsSync(seccompSourcePath)) { - fs.copyFileSync(seccompSourcePath, seccompDestPath); - logger.debug(`Seccomp profile written to: ${seccompDestPath}`); - } else { - // If running from dist, try relative to dist - const altSeccompPath = path.join(__dirname, '..', '..', 'containers', 'agent', 'seccomp-profile.json'); - if (fs.existsSync(altSeccompPath)) { - fs.copyFileSync(altSeccompPath, seccompDestPath); - logger.debug(`Seccomp profile written to: ${seccompDestPath}`); - } else { - const message = `Seccomp profile not found at ${seccompSourcePath} or ${altSeccompPath}. Container security hardening requires the seccomp profile.`; - logger.error(message); - throw new Error(message); - } - } - // Generate SSL Bump certificates if enabled let sslConfig: SslConfig | undefined; if (config.sslBump) {