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

Function resolveSimpleExpansion

src/utils/bash/ast.ts:1937–2008  ·  view source on GitHub ↗

* Resolve a `simple_expansion` ($VAR) node. Returns VAR_PLACEHOLDER if * resolvable, too-complex otherwise. * * @param insideString true when $VAR is inside a `string` node ("...$VAR...") * rather than a bare/concatenation argument. SAFE_ENV_VARS and unknown-value * tracked vars are only al

(
  node: Node,
  varScope: Map<string, string>,
  insideString: boolean,
)

Source from the content-addressed store, hash-verified

1935 * value IS known.
1936 */
1937function resolveSimpleExpansion(
1938 node: Node,
1939 varScope: Map<string, string>,
1940 insideString: boolean,
1941): string | ParseForSecurityResult {
1942 let varName: string | null = null
1943 let isSpecial = false
1944 for (const c of node.children) {
1945 if (c?.type === 'variable_name') {
1946 varName = c.text
1947 break
1948 }
1949 if (c?.type === 'special_variable_name') {
1950 varName = c.text
1951 isSpecial = true
1952 break
1953 }
1954 }
1955 if (varName === null) return tooComplex(node)
1956 // Tracked vars: check stored value. Literal strings (VAR=/tmp) are
1957 // returned DIRECTLY so downstream path validation sees the real path.
1958 // Non-literal values (containing any placeholder — loop vars, $() output,
1959 // read vars, composites like `VAR="prefix$(cmd)"`) are ONLY safe inside
1960 // strings; as bare args they'd hide the runtime path/flag from validation.
1961 //
1962 // SECURITY: Returning the actual trackedValue (not a placeholder) is the
1963 // critical fix. `VAR=/etc && rm $VAR` → argv ['rm', '/etc'] → validatePath
1964 // correctly rejects. Previously returned a placeholder → validatePath saw
1965 // '__LOOP_STATIC__', resolved as cwd-relative → PASSED → bypass.
1966 const trackedValue = varScope.get(varName)
1967 if (trackedValue !== undefined) {
1968 if (containsAnyPlaceholder(trackedValue)) {
1969 // Non-literal: bare → reject, inside string → VAR_PLACEHOLDER
1970 // (walkString's solo-placeholder gate rejects `"$VAR"` alone).
1971 if (!insideString) return tooComplex(node)
1972 return VAR_PLACEHOLDER
1973 }
1974 // Pure literal (e.g. '/tmp', 'foo') — return it directly. Downstream
1975 // path validation / checkSemantics operate on the REAL value.
1976 //
1977 // SECURITY: For BARE args (not inside a string), bash word-splits on
1978 // $IFS and glob-expands the result. `VAR="-rf /" && rm $VAR` → bash
1979 // runs `rm -rf /` (two args); `VAR="/etc/*" && cat $VAR` → expands to
1980 // all files. Reject values containing IFS/glob chars unless in "...".
1981 //
1982 // SECURITY: Empty value as bare arg. Bash word-splitting on "" produces
1983 // ZERO fields — the expansion disappears. `V="" && $V eval x` → bash
1984 // runs `eval x` (our argv would be ["","eval","x"] with name="" —
1985 // every EVAL_LIKE/ZSH/keyword check misses). `V="" && ls $V /etc` →
1986 // bash runs `ls /etc`, our argv has a phantom "" shifting positions.
1987 // Inside "...": `"$V"` → bash produces one empty-string arg → our ""
1988 // is correct, keep allowing.
1989 if (!insideString) {
1990 if (trackedValue === '') return tooComplex(node)
1991 if (BARE_VAR_UNSAFE_RE.test(trackedValue)) return tooComplex(node)
1992 }
1993 return trackedValue
1994 }

Callers 4

walkCommandFunction · 0.85
walkArgumentFunction · 0.85
walkStringFunction · 0.85
walkVariableAssignmentFunction · 0.85

Calls 4

tooComplexFunction · 0.85
containsAnyPlaceholderFunction · 0.85
getMethod · 0.65
hasMethod · 0.45

Tested by

no test coverage detected