MCPcopy Index your code
hub / github.com/codeaashu/claude-code / collectCommands

Function collectCommands

src/utils/bash/ast.ts:482–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

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

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
getMethod · 0.65
deleteMethod · 0.65
pushMethod · 0.45

Tested by

no test coverage detected