MCPcopy Create free account
hub / github.com/claude-code-best/claude-code / collectCommands

Function collectCommands

src/utils/bash/ast.ts:483–955  ·  view source on GitHub ↗

* Recursively collect leaf `command` nodes from a structural wrapper node. * Returns an error result on any disallowed node type, or null on success.

(
  node: Node,
  commands: SimpleCommand[],
  varScope: Map<string, string>,
)

Source from the content-addressed store, hash-verified

481 * Returns an error result on any disallowed node type, or null on success.
482 */
483function collectCommands(
484 node: Node,
485 commands: SimpleCommand[],
486 varScope: Map<string, string>,
487): ParseForSecurityResult | null {
488 if (node.type === 'command') {
489 // Pass `commands` as the innerCommands accumulator — any $() extracted
490 // during walkCommand gets appended alongside the outer command.
491 const result = walkCommand(node, [], commands, varScope)
492 if (result.kind !== 'simple') return result
493 commands.push(...result.commands)
494 return null
495 }
496
497 if (node.type === 'redirected_statement') {
498 return walkRedirectedStatement(node, commands, varScope)
499 }
500
501 if (node.type === 'comment') {
502 return null
503 }
504
505 if (STRUCTURAL_TYPES.has(node.type)) {
506 // SECURITY: `||`, `|`, `|&`, `&` must NOT carry varScope linearly. In bash:
507 // `||` RHS runs conditionally → vars set there MAY not be set
508 // `|`/`|&` stages run in subshells → vars set there are NEVER visible after
509 // `&` LHS runs in a background subshell → same as above
510 // Flag-omission attack: `true || FLAG=--dry-run && cmd $FLAG` — bash skips
511 // the `||` RHS (FLAG unset → $FLAG empty), runs `cmd` WITHOUT --dry-run.
512 // With linear scope, our argv has ['cmd','--dry-run'] → looks SAFE → bypass.
513 //
514 // Fix: snapshot incoming scope at entry. After these separators, reset to
515 // the snapshot — vars set in clauses between separators don't leak. `scope`
516 // for clauses BETWEEN `&&`/`;` chains shares state (common `VAR=x && cmd
517 // $VAR`). `scope` crosses `||`/`|`/`&` as the pre-structure snapshot only.
518 //
519 // `&&` and `;` DO carry scope: `VAR=x && cmd $VAR` is sequential, VAR is set.
520 //
521 // NOTE: `scope` and `varScope` diverge after the first `||`/`|`/`&`. The
522 // caller's varScope is only mutated for the `&&`/`;` prefix — this is
523 // conservative (vars set in `A && B | C && D` leak A+B into caller, not
524 // C+D) but safe.
525 //
526 // Efficiency: snapshot is only needed if we hit `||`/`|`/`|&`/`&`. For
527 // the dominant case (`ls`, `git status` — no such separators), skip the
528 // Map alloc via a cheap pre-scan. For `pipeline`, node.type already tells
529 // us stages are subshells — copy once at entry, no snapshot needed (each
530 // reset uses the entry copy pattern via varScope, which is untouched).
531 const isPipeline = node.type === 'pipeline'
532 let needsSnapshot = false
533 if (!isPipeline) {
534 for (const c of node.children) {
535 if (c && (c.type === '||' || c.type === '&')) {
536 needsSnapshot = true
537 break
538 }
539 }
540 }

Callers 3

walkProgramFunction · 0.85
walkRedirectedStatementFunction · 0.85

Calls 14

walkCommandFunction · 0.85
walkRedirectedStatementFunction · 0.85
walkArgumentFunction · 0.85
walkVariableAssignmentFunction · 0.85
applyVarToScopeFunction · 0.85
tooComplexFunction · 0.85
containsAnyPlaceholderFunction · 0.85
walkTestExprFunction · 0.85
setMethod · 0.80
getMethod · 0.65
deleteMethod · 0.65

Tested by

no test coverage detected