Skip to content
Closed
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
20 changes: 20 additions & 0 deletions containers/agent/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,26 @@ if [ "${AWF_CHROOT_ENABLED}" = "true" ]; then
exit 1
fi

# SECURITY: Mask sensitive /proc entries to prevent kernel info disclosure (#223)
# /proc/kallsyms exposes kernel symbol addresses (aids ASLR bypass / exploit development)
# /proc/modules lists loaded kernel modules (aids kernel exploit targeting)
mount --bind /dev/null /host/proc/kallsyms 2>/dev/null || true
mount --bind /dev/null /host/proc/modules 2>/dev/null || true
echo "[entrypoint] Masked /proc/kallsyms and /proc/modules"

# Set up additional /dev entries in chroot (#223)
# Since /dev is selectively mounted (only null, zero, random, urandom, tty),
# we need to create pts, shm, and standard symlinks for proper operation
mkdir -p /host/dev/pts /host/dev/shm
mount -t devpts devpts /host/dev/pts 2>/dev/null || true
mount -t tmpfs tmpfs /host/dev/shm -o mode=1777 2>/dev/null || true
# Standard /dev symlinks needed by many programs
ln -sf /proc/self/fd /host/dev/fd 2>/dev/null || true
ln -sf /proc/self/fd/0 /host/dev/stdin 2>/dev/null || true
ln -sf /proc/self/fd/1 /host/dev/stdout 2>/dev/null || true
ln -sf /proc/self/fd/2 /host/dev/stderr 2>/dev/null || true
echo "[entrypoint] Set up /dev/pts, /dev/shm, and standard symlinks in chroot"
Comment on lines +182 to +188
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Chroot mode mounts devpts at /host/dev/pts, but /host/dev/ptmx isn’t created/mounted here. Many programs expect /dev/ptmx (often a symlink to pts/ptmx) for pseudo-terminal allocation; without it, PTY-using commands can fail inside the chroot. Consider creating /host/dev/ptmx -> pts/ptmx alongside these standard /dev symlinks, or allowlist-bind-mount /dev/ptmx into /host/dev/ptmx.

Suggested change
mount -t tmpfs tmpfs /host/dev/shm -o mode=1777 2>/dev/null || true
# Standard /dev symlinks needed by many programs
ln -sf /proc/self/fd /host/dev/fd 2>/dev/null || true
ln -sf /proc/self/fd/0 /host/dev/stdin 2>/dev/null || true
ln -sf /proc/self/fd/1 /host/dev/stdout 2>/dev/null || true
ln -sf /proc/self/fd/2 /host/dev/stderr 2>/dev/null || true
echo "[entrypoint] Set up /dev/pts, /dev/shm, and standard symlinks in chroot"
# Provide /dev/ptmx inside the chroot as a symlink to pts/ptmx for PTY allocation
ln -sf pts/ptmx /host/dev/ptmx 2>/dev/null || true
mount -t tmpfs tmpfs /host/dev/shm -o mode=1777 2>/dev/null || true
# Standard /dev symlinks needed by many programs
ln -sf /proc/self/fd /host/dev/fd 2>/dev/null || true
ln -sf /proc/self/fd/0 /host/dev/stdin 2>/dev/null || true
ln -sf /proc/self/fd/1 /host/dev/stdout 2>/dev/null || true
ln -sf /proc/self/fd/2 /host/dev/stderr 2>/dev/null || true
echo "[entrypoint] Set up /dev/pts, /dev/ptmx, /dev/shm, and standard symlinks in chroot"

Copilot uses AI. Check for mistakes.

Comment on lines +173 to +189
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The /host/proc masking mounts ignore failures (|| true) but then always log "Masked /proc/kallsyms and /proc/modules". If either bind-mount fails, the container will still expose these proc entries while logs claim they’re masked. Please check each mount’s exit status and either fail closed or at least emit a clear WARN/ERROR when masking cannot be applied (and consider remounting the bind mount read-only if you want ro semantics).

This issue also appears on line 180 of the same file.

Suggested change
mount --bind /dev/null /host/proc/kallsyms 2>/dev/null || true
mount --bind /dev/null /host/proc/modules 2>/dev/null || true
echo "[entrypoint] Masked /proc/kallsyms and /proc/modules"
# Set up additional /dev entries in chroot (#223)
# Since /dev is selectively mounted (only null, zero, random, urandom, tty),
# we need to create pts, shm, and standard symlinks for proper operation
mkdir -p /host/dev/pts /host/dev/shm
mount -t devpts devpts /host/dev/pts 2>/dev/null || true
mount -t tmpfs tmpfs /host/dev/shm -o mode=1777 2>/dev/null || true
# Standard /dev symlinks needed by many programs
ln -sf /proc/self/fd /host/dev/fd 2>/dev/null || true
ln -sf /proc/self/fd/0 /host/dev/stdin 2>/dev/null || true
ln -sf /proc/self/fd/1 /host/dev/stdout 2>/dev/null || true
ln -sf /proc/self/fd/2 /host/dev/stderr 2>/dev/null || true
echo "[entrypoint] Set up /dev/pts, /dev/shm, and standard symlinks in chroot"
masked_kallsyms=0
masked_modules=0
if mount --bind /dev/null /host/proc/kallsyms 2>/dev/null; then
# Optionally remount read-only to enforce ro semantics
if ! mount -o remount,ro,bind /host/proc/kallsyms 2>/dev/null; then
echo "[entrypoint][WARN] Masked /proc/kallsyms but failed to remount read-only; continuing with writable bind mount"
fi
masked_kallsyms=1
else
echo "[entrypoint][WARN] Failed to mask /proc/kallsyms; kernel symbol information may be exposed in chroot"
fi
if mount --bind /dev/null /host/proc/modules 2>/dev/null; then
# Optionally remount read-only to enforce ro semantics
if ! mount -o remount,ro,bind /host/proc/modules 2>/dev/null; then
echo "[entrypoint][WARN] Masked /proc/modules but failed to remount read-only; continuing with writable bind mount"
fi
masked_modules=1
else
echo "[entrypoint][WARN] Failed to mask /proc/modules; kernel module list may be exposed in chroot"
fi
if [ "$masked_kallsyms" -eq 1 ] && [ "$masked_modules" -eq 1 ]; then
echo "[entrypoint] Masked /proc/kallsyms and /proc/modules"
else
echo "[entrypoint][WARN] One or more sensitive /proc entries could not be masked (kallsyms=$masked_kallsyms, modules=$masked_modules)"
fi
# Set up additional /dev entries in chroot (#223)
# Since /dev is selectively mounted (only null, zero, random, urandom, tty),
# we need to create pts, shm, and standard symlinks for proper operation
mkdir -p /host/dev/pts /host/dev/shm
devpts_ok=0
shm_ok=0
if mount -t devpts devpts /host/dev/pts 2>/dev/null; then
devpts_ok=1
else
echo "[entrypoint][WARN] Failed to mount devpts at /host/dev/pts; pseudo-terminal support in chroot may be limited"
fi
if mount -t tmpfs tmpfs /host/dev/shm -o mode=1777 2>/dev/null; then
shm_ok=1
else
echo "[entrypoint][WARN] Failed to mount tmpfs at /host/dev/shm; shared memory in chroot may be unavailable"
fi
# Standard /dev symlinks needed by many programs
ln -sf /proc/self/fd /host/dev/fd 2>/dev/null || true
ln -sf /proc/self/fd/0 /host/dev/stdin 2>/dev/null || true
ln -sf /proc/self/fd/1 /host/dev/stdout 2>/dev/null || true
ln -sf /proc/self/fd/2 /host/dev/stderr 2>/dev/null || true
if [ "$devpts_ok" -eq 1 ] && [ "$shm_ok" -eq 1 ]; then
echo "[entrypoint] Set up /dev/pts, /dev/shm, and standard symlinks in chroot"
else
echo "[entrypoint][WARN] /dev/pts or /dev/shm could not be fully set up in chroot (devpts_ok=$devpts_ok, shm_ok=$shm_ok)"
fi

Copilot uses AI. Check for mistakes.
# Copy one-shot-token library to host filesystem for LD_PRELOAD in chroot
# This prevents tokens from being read multiple times by malicious code
# Note: /tmp is always writable in chroot mode (mounted from host /tmp as rw)
Expand Down
63 changes: 60 additions & 3 deletions src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,11 @@ describe('docker-manager', () => {

// Should include blanket /:/host:rw mount
expect(volumes).toContain('/:/host:rw');
// Should NOT include /dev/null credential hiding
expect(volumes.some((v: string) => v.startsWith('/dev/null'))).toBe(false);
// Should NOT include /dev/null credential hiding (but /proc masking is still present)
expect(volumes.some((v: string) => v.includes('/dev/null') && v.includes('.docker/config.json'))).toBe(false);
// /proc masking should still be present even with full filesystem access
expect(volumes).toContain('/dev/null:/proc/kallsyms:ro');
expect(volumes).toContain('/dev/null:/proc/modules:ro');
});

it('should use blanket mount when allowFullFilesystemAccess is true in chroot mode', () => {
Expand Down Expand Up @@ -597,7 +600,15 @@ describe('docker-manager', () => {
expect(volumes).not.toContain('/proc:/host/proc:ro');
expect(volumes).not.toContain('/proc/self:/host/proc/self:ro');
expect(volumes).toContain('/sys:/host/sys:ro');
expect(volumes).toContain('/dev:/host/dev:ro');

// SECURITY: /dev is NOT blanket-mounted to prevent host block device access (#223)
// Only specific safe device nodes are exposed
expect(volumes).not.toContain('/dev:/host/dev:ro');
expect(volumes).toContain('/dev/null:/host/dev/null:rw');
expect(volumes).toContain('/dev/zero:/host/dev/zero:rw');
expect(volumes).toContain('/dev/random:/host/dev/random:ro');
expect(volumes).toContain('/dev/urandom:/host/dev/urandom:ro');
expect(volumes).toContain('/dev/tty:/host/dev/tty:rw');

// Should include /etc subdirectories (read-only)
expect(volumes).toContain('/etc/ssl:/host/etc/ssl:ro');
Expand Down Expand Up @@ -1093,6 +1104,52 @@ describe('docker-manager', () => {
expect(agent.cpu_shares).toBe(1024);
});

it('should mask /proc/kallsyms and /proc/modules for security (#223)', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
const agent = result.services.agent;
const volumes = agent.volumes as string[];

// SECURITY: Prevent kernel info disclosure
// /proc/kallsyms aids ASLR bypass, /proc/modules aids kernel exploit targeting
expect(volumes).toContain('/dev/null:/proc/kallsyms:ro');
expect(volumes).toContain('/dev/null:/proc/modules:ro');
});

it('should mask /proc/kallsyms and /proc/modules in chroot mode (#223)', () => {
const configWithChroot = {
...mockConfig,
enableChroot: true
};
const result = generateDockerCompose(configWithChroot, mockNetworkConfig);
const agent = result.services.agent;
const volumes = agent.volumes as string[];

// Container-level /proc masking should apply in chroot mode too (defense in depth)
// Additionally, entrypoint.sh masks /host/proc/kallsyms and /host/proc/modules
expect(volumes).toContain('/dev/null:/proc/kallsyms:ro');
expect(volumes).toContain('/dev/null:/proc/modules:ro');
});

it('should use selective /dev mounts in chroot mode instead of blanket mount (#223)', () => {
const configWithChroot = {
...mockConfig,
enableChroot: true
};
const result = generateDockerCompose(configWithChroot, mockNetworkConfig);
const agent = result.services.agent;
const volumes = agent.volumes as string[];

// Should NOT include blanket /dev mount (exposes host block devices)
expect(volumes).not.toContain('/dev:/host/dev:ro');

// Should include only safe device nodes
expect(volumes).toContain('/dev/null:/host/dev/null:rw');
expect(volumes).toContain('/dev/zero:/host/dev/zero:rw');
expect(volumes).toContain('/dev/random:/host/dev/random:ro');
expect(volumes).toContain('/dev/urandom:/host/dev/urandom:ro');
expect(volumes).toContain('/dev/tty:/host/dev/tty:rw');
});

it('should disable TTY by default to prevent ANSI escape sequences', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
const agent = result.services.agent;
Expand Down
25 changes: 23 additions & 2 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,14 @@ export function generateDockerCompose(
`${config.workDir}/agent-logs:${effectiveHome}/.copilot/logs:rw`,
];

// SECURITY: Mask sensitive /proc entries to prevent kernel info disclosure (#223)
// /proc/kallsyms exposes kernel symbol addresses (aids ASLR bypass and exploit development)
// /proc/modules lists loaded kernel modules (aids kernel exploit targeting)
agentVolumes.push(
'/dev/null:/proc/kallsyms:ro',
'/dev/null:/proc/modules:ro',
);

// Add chroot-related volume mounts when --enable-chroot is specified
// These mounts enable chroot /host to work properly for running host binaries
if (config.enableChroot) {
Expand All @@ -460,16 +468,29 @@ export function generateDockerCompose(
// /opt/hostedtoolcache contains Python, Node, Ruby, Go, Java, etc.
agentVolumes.push('/opt:/host/opt:ro');

// Special filesystem mounts for chroot (needed for devices and runtime introspection)
// Special filesystem mounts for chroot (needed for runtime introspection)
// NOTE: /proc is NOT bind-mounted here. Instead, a fresh container-scoped procfs is
// mounted at /host/proc in entrypoint.sh via 'mount -t proc'. This provides:
// - Dynamic /proc/self/exe (required by .NET CLR and other runtimes)
// - /proc/cpuinfo, /proc/meminfo (required by JVM, .NET GC)
// - Container-scoped only (does not expose host process info)
// The mount requires SYS_ADMIN capability, which is dropped before user code runs.
// Additionally, /proc/kallsyms and /proc/modules are masked in entrypoint.sh.
agentVolumes.push(
'/sys:/host/sys:ro', // Read-only sysfs
'/dev:/host/dev:ro', // Read-only device nodes (needed by some runtimes)
);

// SECURITY: Selective /dev mounting to prevent host block device access (#223)
// A blanket /dev:/host/dev:ro mount exposes ALL host device nodes including
// /dev/sda*, /dev/nvme* etc. which aids disk forensics and potential data exfiltration.
// Instead, mount only the specific device nodes that runtimes need.
// Additional /dev entries (pts, shm, symlinks) are set up in entrypoint.sh.
agentVolumes.push(
'/dev/null:/host/dev/null:rw',
'/dev/zero:/host/dev/zero:rw',
'/dev/random:/host/dev/random:ro',
'/dev/urandom:/host/dev/urandom:ro',
'/dev/tty:/host/dev/tty:rw',
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Selective /dev mounts in chroot mode omit /dev/ptmx. Since entrypoint.sh mounts devpts at /host/dev/pts, you typically also need /host/dev/ptmx (device or symlink to pts/ptmx) for PTY allocation. Without it, commands that allocate pseudo-terminals may fail inside the chroot. Add /dev/ptmx to the allowlist or ensure entrypoint creates the expected symlink.

Suggested change
'/dev/tty:/host/dev/tty:rw',
'/dev/tty:/host/dev/tty:rw',
'/dev/ptmx:/host/dev/ptmx:rw',

Copilot uses AI. Check for mistakes.
);

// User home directory for project files and Rust/Cargo (read-write)
Expand Down
Loading