Download src/tools/BashTool/shouldUseSandbox.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 5.19 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/tools/BashTool/shouldUseSandbox.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/tools/BashTool/shouldUseSandbox.ts
-
curl -L -o shouldUseSandbox.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/tools/BashTool/shouldUseSandbox.ts
5.19 kB
| import { 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 | |
| } | |