This commit is contained in:
Colin McDonnell
2026-01-08 13:58:08 -08:00
parent 6260b23de7
commit fe7ce4af11
11 changed files with 321 additions and 163 deletions
+23 -28
View File
@@ -11,49 +11,37 @@ export const BashParams = type({
});
// patterns for sensitive env vars: suffixes (_KEY, _SECRET, _TOKEN) plus AI provider prefixes
const SENSITIVE_PATTERNS = [
/_KEY$/i,
/_SECRET$/i,
/_TOKEN$/i,
/PASSWORD/i,
/CREDENTIAL/i,
/AUTH/i,
/^ANTHROPIC/i,
/^OPENAI/i,
/^GEMINI/i,
/^GOOGLE/i,
/^CLAUDE/i,
/^CODEX/i,
/^CURSOR/i,
/^AWS/i,
/^GITHUB_TOKEN$/i,
/^GH_TOKEN$/i,
];
const SENSITIVE_PATTERNS = [/_KEY$/i, /_SECRET$/i, /_TOKEN$/i, /_PASSWORD$/i, /_CREDENTIAL$/i];
function isSensitive(key: string): boolean {
return SENSITIVE_PATTERNS.some((p) => p.test(key));
}
/** filter env vars, removing sensitive values */
function filterEnv(): Record<string, string> {
/** filter env vars, removing sensitive values (only for public repos) */
function filterEnv(isPublicRepo: boolean): Record<string, string> {
const filtered: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (value !== undefined && !isSensitive(key)) filtered[key] = value;
if (value === undefined) continue;
// only filter sensitive vars for public repos
if (isPublicRepo && isSensitive(key)) continue;
filtered[key] = value;
}
return filtered;
}
/**
* spawn command with filtered env. in CI, also use PID namespace isolation
* to prevent child from reading /proc/$PPID/environ
* to prevent child from reading /proc/$PPID/environ (only for public repos)
*/
function spawnSandboxed(
command: string,
options: { env: Record<string, string>; cwd: string }
options: { env: Record<string, string>; cwd: string; isPublicRepo: boolean }
): ChildProcess {
const stdio: ["ignore", "pipe", "pipe"] = ["ignore", "pipe", "pipe"];
const spawnOpts = { env: options.env, cwd: options.cwd, stdio, detached: true };
return process.env.CI === "true" // requires linux runner or this will fail.
// only use PID namespace isolation for public repos in CI
const useNamespaceIsolation = process.env.CI === "true" && options.isPublicRepo;
return useNamespaceIsolation
? spawn("unshare", ["--pid", "--fork", "--mount-proc", "bash", "-c", command], spawnOpts)
: spawn("bash", ["-c", command], spawnOpts);
}
@@ -74,21 +62,28 @@ async function killProcessGroup(proc: ChildProcess): Promise<void> {
}
}
export function BashTool(_ctx: ToolContext) {
export function BashTool(ctx: ToolContext) {
const isPublicRepo = !ctx.repo.private;
return tool({
name: "bash",
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
description: `Execute shell commands securely.${isPublicRepo ? " Environment is filtered to remove API keys and secrets." : ""}
Use this tool to:
- Run shell commands (ls, cat, grep, find, etc.)
- Execute build tools (npm, pnpm, cargo, make, etc.)
- Run tests and linters
- Perform git operations`,
- Perform git operations
${isPublicRepo ? "\nThe command runs in a bash shell with a filtered environment that excludes sensitive variables like API keys and tokens." : ""}`,
parameters: BashParams,
execute: execute(async (params) => {
const timeout = Math.min(params.timeout ?? 120000, 600000);
const cwd = params.working_directory ?? process.cwd();
const proc = spawnSandboxed(params.command, { env: filterEnv(), cwd });
const proc = spawnSandboxed(params.command, {
env: filterEnv(isPublicRepo),
cwd,
isPublicRepo,
});
let stdout = "",
stderr = "",