* If the command contains a heredoc (<<), extract the command prefix before it. * Returns the first word(s) before the heredoc operator as a stable prefix, * or null if the command doesn't contain a heredoc. * * Examples: * 'git commit -m "$(cat <<\'EOF\'\n...\nEOF\n)"' → 'git commit' * 'c
(command: string)
| 305 | * 'echo hello' → null (no heredoc) |
| 306 | */ |
| 307 | function extractPrefixBeforeHeredoc(command: string): string | null { |
| 308 | if (!command.includes('<<')) return null |
| 309 | |
| 310 | const idx = command.indexOf('<<') |
| 311 | if (idx <= 0) return null |
| 312 | |
| 313 | const before = command.substring(0, idx).trim() |
| 314 | if (!before) return null |
| 315 | |
| 316 | const prefix = getSimpleCommandPrefix(before) |
| 317 | if (prefix) return prefix |
| 318 | |
| 319 | // Fallback: skip safe env var assignments and take up to 2 tokens. |
| 320 | // This preserves flag tokens (e.g., "python3 -c" stays "python3 -c", |
| 321 | // not just "python3") and skips safe env var prefixes like "NODE_ENV=test". |
| 322 | // If a non-safe env var is encountered, return null to avoid generating |
| 323 | // prefix rules that can never match (same rationale as getSimpleCommandPrefix). |
| 324 | const tokens = before.split(/\s+/).filter(Boolean) |
| 325 | let i = 0 |
| 326 | while (i < tokens.length && ENV_VAR_ASSIGN_RE.test(tokens[i]!)) { |
| 327 | const varName = tokens[i]!.split('=')[0]! |
| 328 | const isAntOnlySafe = |
| 329 | process.env.USER_TYPE === 'ant' && ANT_ONLY_SAFE_ENV_VARS.has(varName) |
| 330 | if (!SAFE_ENV_VARS.has(varName) && !isAntOnlySafe) { |
| 331 | return null |
| 332 | } |
| 333 | i++ |
| 334 | } |
| 335 | if (i >= tokens.length) return null |
| 336 | return tokens.slice(i, i + 2).join(' ') || null |
| 337 | } |
| 338 | |
| 339 | function suggestionForPrefix(prefix: string): PermissionUpdate[] { |
| 340 | return sharedSuggestionForPrefix(BashTool.name, prefix) |
no test coverage detected