( command: string, )
| 2255 | * unavailable. The primary gate is parseForSecurity (ast.ts). |
| 2256 | */ |
| 2257 | export function bashCommandIsSafe_DEPRECATED( |
| 2258 | command: string, |
| 2259 | ): PermissionResult { |
| 2260 | // SECURITY: Block control characters before any other processing. Null bytes |
| 2261 | // and other non-printable chars are silently dropped by bash but confuse our |
| 2262 | // validators, allowing metacharacters adjacent to them to slip through. |
| 2263 | if (CONTROL_CHAR_RE.test(command)) { |
| 2264 | logEvent('tengu_bash_security_check_triggered', { |
| 2265 | checkId: BASH_SECURITY_CHECK_IDS.CONTROL_CHARACTERS, |
| 2266 | }) |
| 2267 | return { |
| 2268 | behavior: 'ask', |
| 2269 | message: |
| 2270 | 'Command contains non-printable control characters that could be used to bypass security checks', |
| 2271 | isBashSecurityCheckForMisparsing: true, |
| 2272 | } |
| 2273 | } |
| 2274 | |
| 2275 | // SECURITY: Detect '\' patterns that exploit shell-quote's incorrect handling |
| 2276 | // of backslashes inside single quotes. Must run before shell-quote parsing. |
| 2277 | if (hasShellQuoteSingleQuoteBug(command)) { |
| 2278 | return { |
| 2279 | behavior: 'ask', |
| 2280 | message: |
| 2281 | 'Command contains single-quoted backslash pattern that could bypass security checks', |
| 2282 | isBashSecurityCheckForMisparsing: true, |
| 2283 | } |
| 2284 | } |
| 2285 | |
| 2286 | // SECURITY: Strip heredoc bodies before running security validators. |
| 2287 | // Only strip bodies for quoted/escaped delimiters (<<'EOF', <<\EOF) where |
| 2288 | // the body is literal text — $(), backticks, and ${} are NOT expanded. |
| 2289 | // Unquoted heredocs (<<EOF) undergo full shell expansion, so their bodies |
| 2290 | // may contain executable command substitutions that validators must see. |
| 2291 | // When extractHeredocs bails out (can't parse safely), the raw command |
| 2292 | // goes through all validators — which is the safe direction. |
| 2293 | const { processedCommand } = extractHeredocs(command, { quotedOnly: true }) |
| 2294 | |
| 2295 | const baseCommand = command.split(' ')[0] || '' |
| 2296 | const { withDoubleQuotes, fullyUnquoted, unquotedKeepQuoteChars } = |
| 2297 | extractQuotedContent(processedCommand, baseCommand === 'jq') |
| 2298 | |
| 2299 | const context: ValidationContext = { |
| 2300 | originalCommand: command, |
| 2301 | baseCommand, |
| 2302 | unquotedContent: withDoubleQuotes, |
| 2303 | fullyUnquotedContent: stripSafeRedirections(fullyUnquoted), |
| 2304 | fullyUnquotedPreStrip: fullyUnquoted, |
| 2305 | unquotedKeepQuoteChars, |
| 2306 | } |
| 2307 | |
| 2308 | const earlyValidators = [ |
| 2309 | validateEmpty, |
| 2310 | validateIncompleteCommands, |
| 2311 | validateSafeCommandSubstitution, |
| 2312 | validateGitCommit, |
| 2313 | ] |
| 2314 |
no test coverage detected