security: sanitize env vars from agent Bash subprocesses (#171)

Use a PreToolUse SDK hook to prepend `unset ANTHROPIC_API_KEY
CLAUDE_CODE_OAUTH_TOKEN` to every Bash command Kit runs, preventing
secret leakage via env/printenv/echo/$PROC. Secrets are now passed
via stdin JSON instead of mounted env files, closing all known
exfiltration vectors.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Cole
2026-02-13 12:33:39 -08:00
committed by GitHub
parent c30bd62417
commit 1a07869329
3 changed files with 73 additions and 33 deletions

View File

@@ -16,7 +16,7 @@
import fs from 'fs';
import path from 'path';
import { query, HookCallback, PreCompactHookInput } from '@anthropic-ai/claude-agent-sdk';
import { query, HookCallback, PreCompactHookInput, PreToolUseHookInput } from '@anthropic-ai/claude-agent-sdk';
import { fileURLToPath } from 'url';
interface ContainerInput {
@@ -26,6 +26,7 @@ interface ContainerInput {
chatJid: string;
isMain: boolean;
isScheduledTask?: boolean;
secrets?: Record<string, string>;
}
interface ContainerOutput {
@@ -183,6 +184,30 @@ function createPreCompactHook(): HookCallback {
};
}
// Secrets to strip from Bash tool subprocess environments.
// These are needed by claude-code for API auth but should never
// be visible to commands Kit runs.
const SECRET_ENV_VARS = ['ANTHROPIC_API_KEY', 'CLAUDE_CODE_OAUTH_TOKEN'];
function createSanitizeBashHook(): HookCallback {
return async (input, _toolUseId, _context) => {
const preInput = input as PreToolUseHookInput;
const command = (preInput.tool_input as { command?: string })?.command;
if (!command) return {};
const unsetPrefix = `unset ${SECRET_ENV_VARS.join(' ')} 2>/dev/null; `;
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
updatedInput: {
...(preInput.tool_input as Record<string, unknown>),
command: unsetPrefix + command,
},
},
};
};
}
function sanitizeFilename(summary: string): string {
return summary
.toLowerCase()
@@ -422,7 +447,8 @@ async function runQuery(
},
},
hooks: {
PreCompact: [{ hooks: [createPreCompactHook()] }]
PreCompact: [{ hooks: [createPreCompactHook()] }],
PreToolUse: [{ matcher: 'Bash', hooks: [createSanitizeBashHook()] }],
},
}
})) {
@@ -477,6 +503,12 @@ async function main(): Promise<void> {
process.exit(1);
}
// Set secrets as env vars for the SDK (needed for API auth).
// The PreToolUse hook strips these from every Bash subprocess.
for (const [key, value] of Object.entries(containerInput.secrets || {})) {
process.env[key] = value;
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mcpServerPath = path.join(__dirname, 'ipc-mcp-stdio.js');