Merge pull request #224 from ajmallesh/security/tighten-docker-env-isolation

Hardening local defaults
This commit is contained in:
Arjun Malleswaran
2026-03-07 11:56:35 -08:00
committed by GitHub
4 changed files with 42 additions and 14 deletions
+4
View File
@@ -677,6 +677,10 @@ Shannon is designed for legitimate security auditing purposes only.
Windows Defender may flag files in `xben-benchmark-results/` or `deliverables/` as malware. These are false positives caused by exploit code in the reports. Add an exclusion for the Shannon directory in Windows Defender, or use Docker/WSL2. Windows Defender may flag files in `xben-benchmark-results/` or `deliverables/` as malware. These are false positives caused by exploit code in the reports. Add an exclusion for the Shannon directory in Windows Defender, or use Docker/WSL2.
#### **7. Security Considerations**
Shannon Lite is designed for scanning repositories and applications you own or have explicit permission to test. Do not point it at untrusted or adversarial codebases. Like any AI-powered tool that reads source code, Shannon Lite is susceptible to prompt injection from content in the scanned repository.
## 📜 License ## 📜 License
+3 -4
View File
@@ -3,8 +3,8 @@ services:
image: temporalio/temporal:latest image: temporalio/temporal:latest
command: ["server", "start-dev", "--db-filename", "/home/temporal/temporal.db", "--ip", "0.0.0.0"] command: ["server", "start-dev", "--db-filename", "/home/temporal/temporal.db", "--ip", "0.0.0.0"]
ports: ports:
- "7233:7233" # gRPC - "127.0.0.1:7233:7233" # gRPC
- "8233:8233" # Web UI (built-in) - "127.0.0.1:8233:8233" # Web UI (built-in)
volumes: volumes:
- temporal-data:/home/temporal - temporal-data:/home/temporal
healthcheck: healthcheck:
@@ -47,7 +47,6 @@ services:
- ./repos:/repos - ./repos:/repos
- ${BENCHMARKS_BASE:-.}:/benchmarks - ${BENCHMARKS_BASE:-.}:/benchmarks
shm_size: 2gb shm_size: 2gb
ipc: host
security_opt: security_opt:
- seccomp:unconfined - seccomp:unconfined
@@ -63,7 +62,7 @@ services:
envsubst < /config/router-config.json > /root/.claude-code-router/config.json && envsubst < /config/router-config.json > /root/.claude-code-router/config.json &&
ccr start" ccr start"
ports: ports:
- "3456:3456" - "127.0.0.1:3456:3456"
volumes: volumes:
- ./configs/router-config.json:/config/router-config.json:ro - ./configs/router-config.json:/config/router-config.json:ro
environment: environment:
+24 -8
View File
@@ -82,7 +82,7 @@ function buildMcpServers(
const isDocker = process.env.SHANNON_DOCKER === 'true'; const isDocker = process.env.SHANNON_DOCKER === 'true';
const mcpArgs: string[] = [ const mcpArgs: string[] = [
'@playwright/mcp@latest', '@playwright/mcp@0.0.68',
'--isolated', '--isolated',
'--user-data-dir', userDataDir, '--user-data-dir', userDataDir,
]; ];
@@ -92,13 +92,29 @@ function buildMcpServers(
mcpArgs.push('--browser', 'chromium'); mcpArgs.push('--browser', 'chromium');
} }
const envVars: Record<string, string> = Object.fromEntries( // NOTE: Explicit allowlist — the Playwright MCP subprocess must not inherit
Object.entries({ // secrets (API keys, AWS tokens) from the parent process.
...process.env, const MCP_ENV_ALLOWLIST = [
PLAYWRIGHT_HEADLESS: 'true', 'PATH', 'HOME', 'NODE_PATH', 'DISPLAY',
...(isDocker && { PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' }), 'PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH',
}).filter((entry): entry is [string, string] => entry[1] !== undefined) ] as const;
);
const envVars: Record<string, string> = {
PLAYWRIGHT_HEADLESS: 'true',
...(isDocker && { PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' }),
};
for (const key of MCP_ENV_ALLOWLIST) {
if (process.env[key]) {
envVars[key] = process.env[key]!;
}
}
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('XDG_') && value !== undefined) {
envVars[key] = value;
}
}
mcpServers[playwrightMcpName] = { mcpServers[playwrightMcpName] = {
type: 'stdio' as const, type: 'stdio' as const,
+11 -2
View File
@@ -102,10 +102,19 @@ async function buildLoginInstructions(authentication: Authentication, logger: Ac
// Pure function: Process @include() directives // Pure function: Process @include() directives
async function processIncludes(content: string, baseDir: string): Promise<string> { async function processIncludes(content: string, baseDir: string): Promise<string> {
const includeRegex = /@include\(([^)]+)\)/g; const includeRegex = /@include\(([^)]+)\)/g;
// Use a Promise.all to handle all includes concurrently const resolvedBase = path.resolve(baseDir);
const replacements: IncludeReplacement[] = await Promise.all( const replacements: IncludeReplacement[] = await Promise.all(
Array.from(content.matchAll(includeRegex)).map(async (match) => { Array.from(content.matchAll(includeRegex)).map(async (match) => {
const includePath = path.join(baseDir, match[1]!); const includePath = path.resolve(baseDir, match[1]!);
if (!includePath.startsWith(resolvedBase + path.sep) && includePath !== resolvedBase) {
throw new PentestError(
`Path traversal detected in @include(): ${match[1]}`,
'prompt',
false,
{ includePath, baseDir: resolvedBase }
);
}
const sharedContent = await fs.readFile(includePath, 'utf8'); const sharedContent = await fs.readFile(includePath, 'utf8');
return { return {
placeholder: match[0], placeholder: match[0],