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
Empty file modified examples/basic-curl.sh
100644 → 100755
Empty file.
23 changes: 23 additions & 0 deletions src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,29 @@ describe('docker-manager', () => {
expect(fs.existsSync(path.join(testDir, 'squid-logs'))).toBe(true);
});

it('should create /tmp/gh-aw/mcp-logs directory with world-writable permissions', async () => {
const config: WrapperConfig = {
allowedDomains: ['github.com'],
agentCommand: 'echo test',
logLevel: 'info',
keepContainers: false,
workDir: testDir,
};

try {
await writeConfigs(config);
} catch {
// May fail, but directory should still be created
}

// Verify /tmp/gh-aw/mcp-logs directory was created
expect(fs.existsSync('/tmp/gh-aw/mcp-logs')).toBe(true);
const stats = fs.statSync('/tmp/gh-aw/mcp-logs');
expect(stats.isDirectory()).toBe(true);
// Verify permissions are 0o777 (rwxrwxrwx) to allow non-root users to create subdirectories
expect((stats.mode & 0o777).toString(8)).toBe('777');
});
Comment on lines +1498 to +1519
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.

This test writes to a global host path (/tmp/gh-aw/mcp-logs) and assumes it can enforce 0777 permissions, which can be flaky if the directory already exists from prior runs (possibly owned by root) or tests run in parallel. Consider mocking fs.mkdirSync/chmodSync to assert the intended calls, or adding a test-only hook/config to redirect the MCP logs dir to a per-test temp directory.

Copilot uses AI. Check for mistakes.

it('should write squid.conf file', async () => {
const config: WrapperConfig = {
allowedDomains: ['github.com', 'example.com'],
Expand Down
30 changes: 30 additions & 0 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,17 @@ export function generateDockerCompose(
dns_search: [], // Disable DNS search domains to prevent embedded DNS fallback
volumes: agentVolumes,
environment,
// Hide /tmp/gh-aw/mcp-logs directory using tmpfs (empty in-memory filesystem)
// This prevents the agent from accessing MCP server logs while still allowing
// the host to write logs to /tmp/gh-aw/mcp-logs/ (e.g., /tmp/gh-aw/mcp-logs/safeoutputs/)
// For normal mode: hide /tmp/gh-aw/mcp-logs
// For chroot mode: hide both /tmp/gh-aw/mcp-logs and /host/tmp/gh-aw/mcp-logs
tmpfs: config.enableChroot
Comment on lines +721 to +726
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 PR description says this is hidden via /dev/null overlay mounts, but the implementation here uses Docker Compose tmpfs mounts. Please update the PR description (and any related docs) to match the actual behavior, or switch the implementation back to /dev/null if that’s the intended design.

This issue also appears in the following locations of the same file:

  • line 726
  • line 728

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

? [
'/tmp/gh-aw/mcp-logs:rw,noexec,nosuid,size=1m',
'/host/tmp/gh-aw/mcp-logs:rw,noexec,nosuid,size=1m',
]
: ['/tmp/gh-aw/mcp-logs:rw,noexec,nosuid,size=1m'],
depends_on: {
'squid-proxy': {
condition: 'service_healthy',
Expand Down Expand Up @@ -861,9 +872,28 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
const squidLogsDir = config.proxyLogsDir || path.join(config.workDir, 'squid-logs');
if (!fs.existsSync(squidLogsDir)) {
fs.mkdirSync(squidLogsDir, { recursive: true, mode: 0o777 });
// Explicitly set permissions to 0o777 (not affected by umask)
fs.chmodSync(squidLogsDir, 0o777);
}
logger.debug(`Squid logs directory created at: ${squidLogsDir}`);

// Create /tmp/gh-aw/mcp-logs directory
// This directory exists on the HOST for MCP gateway to write logs
// Inside the AWF container, it's hidden via tmpfs mount (see generateDockerCompose)
// Uses mode 0o777 to allow GitHub Actions workflows and MCP gateway to create subdirectories
// even when AWF runs as root (e.g., sudo awf --enable-chroot)
const mcpLogsDir = '/tmp/gh-aw/mcp-logs';
if (!fs.existsSync(mcpLogsDir)) {
fs.mkdirSync(mcpLogsDir, { recursive: true, mode: 0o777 });
// Explicitly set permissions to 0o777 (not affected by umask)
fs.chmodSync(mcpLogsDir, 0o777);
logger.debug(`MCP logs directory created at: ${mcpLogsDir}`);
} else {
// Fix permissions if directory already exists (e.g., created by a previous run)
fs.chmodSync(mcpLogsDir, 0o777);
logger.debug(`MCP logs directory permissions fixed at: ${mcpLogsDir}`);
}

// Use fixed network configuration (network is created by host-iptables.ts)
const networkConfig = {
subnet: '172.30.0.0/24',
Expand Down
56 changes: 56 additions & 0 deletions tests/integration/credential-hiding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,60 @@ describe('Credential Hiding Security', () => {
expect(output).not.toContain('auth');
}, 120000);
});

describe('MCP Logs Directory Hiding', () => {
test('Test 13: /tmp/gh-aw/mcp-logs/ is hidden in normal mode', async () => {
// Try to access the mcp-logs directory
const result = await runner.runWithSudo(
'ls -la /tmp/gh-aw/mcp-logs/ 2>&1 | grep -v "^\\[" | head -1',
{
allowDomains: ['github.com'],
logLevel: 'debug',
timeout: 60000,
}
);

// With tmpfs mounted over the directory, ls should succeed but show empty directory
// The directory appears to exist (as an empty tmpfs) but contains no files
const allOutput = `${result.stdout}\n${result.stderr}`;
// Verify either:
// 1. Directory listing shows it's effectively empty (total size indicates empty tmpfs)
// 2. Or old /dev/null behavior ("Not a directory")
expect(allOutput).toMatch(/total|Not a directory|cannot access/i);
}, 120000);

test('Test 14: /tmp/gh-aw/mcp-logs/ is hidden in chroot mode', async () => {
// Try to access the mcp-logs directory at /host path
const result = await runner.runWithSudo(
'ls -la /host/tmp/gh-aw/mcp-logs/ 2>&1 | grep -v "^\\[" | head -1',
{
allowDomains: ['github.com'],
logLevel: 'debug',
timeout: 60000,
enableChroot: true,
}
);

// With tmpfs mounted over the directory at /host path, ls should succeed but show empty
const allOutput = `${result.stdout}\n${result.stderr}`;
expect(allOutput).toMatch(/total|Not a directory|cannot access/i);
}, 120000);

test('Test 15: MCP logs files cannot be read in normal mode', async () => {
// Try to read a typical MCP log file path
const result = await runner.runWithSudo(
'cat /tmp/gh-aw/mcp-logs/safeoutputs/log.txt 2>&1 | grep -v "^\\[" | head -1',
{
allowDomains: ['github.com'],
logLevel: 'debug',
timeout: 60000,
}
);

// Should fail with "No such file or directory" (tmpfs is empty)
// This confirms the tmpfs mount is preventing file access to host files
const allOutput = `${result.stdout}\n${result.stderr}`;
expect(allOutput).toMatch(/No such file or directory|Not a directory|cannot access/i);
}, 120000);
});
});
Loading