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

Function extractSafeCatHeredoc

src/utils/bash/ast.ts:1721–1775  ·  view source on GitHub ↗

* Check if a command_substitution node is exactly `$(cat <<'DELIM'...DELIM)` * and return the heredoc body if so. Any deviation (extra args to cat, * unquoted delimiter, additional commands) returns null. * * tree-sitter structure: * command_substitution * $( * redirected_statement

(subNode: Node)

Source from the content-addressed store, hash-verified

1719 * )
1720 */
1721function extractSafeCatHeredoc(subNode: Node): string | 'DANGEROUS' | null {
1722 // Expect exactly: $( + one redirected_statement + )
1723 let stmt: Node | null = null
1724 for (const child of subNode.children) {
1725 if (!child) continue
1726 if (child.type === '$(' || child.type === ')') continue
1727 if (child.type === 'redirected_statement' && stmt === null) {
1728 stmt = child
1729 } else {
1730 return null
1731 }
1732 }
1733 if (!stmt) return null
1734
1735 // redirected_statement must be: command(cat) + heredoc_redirect (quoted)
1736 let sawCat = false
1737 let body: string | null = null
1738 for (const child of stmt.children) {
1739 if (!child) continue
1740 if (child.type === 'command') {
1741 // Must be bare `cat` — no args, no env vars
1742 const cmdChildren = child.children.filter(c => c)
1743 if (cmdChildren.length !== 1) return null
1744 const nameNode = cmdChildren[0]
1745 if (nameNode?.type !== 'command_name' || nameNode.text !== 'cat') {
1746 return null
1747 }
1748 sawCat = true
1749 } else if (child.type === 'heredoc_redirect') {
1750 // Reuse the existing validator: quoted delimiter, body is pure text.
1751 // walkHeredocRedirect returns null on success, non-null on rejection.
1752 if (walkHeredocRedirect(child) !== null) return null
1753 for (const hc of child.children) {
1754 if (hc?.type === 'heredoc_body') body = hc.text
1755 }
1756 } else {
1757 return null
1758 }
1759 }
1760
1761 if (!sawCat || body === null) return null
1762 // SECURITY: the heredoc body becomes the outer command's argv value via
1763 // substitution, so a body like `/proc/self/environ` is semantically
1764 // `cat /proc/self/environ`. checkSemantics never sees the body (we drop it
1765 // at the walkString call site to avoid newline+# FPs). Returning `null`
1766 // here would fall through to collectCommandSubstitution in walkString,
1767 // which would extract the inner `cat` via walkHeredocRedirect (body text
1768 // not inspected there) — effectively bypassing this check. Return a
1769 // distinct sentinel so the caller can reject instead of falling through.
1770 if (PROC_ENVIRON_RE.test(body)) return 'DANGEROUS'
1771 // Same for jq system(): checkSemantics checks argv but never sees the
1772 // heredoc body. Check unconditionally (we don't know the outer command).
1773 if (/\bsystem\s*\(/.test(body)) return 'DANGEROUS'
1774 return body
1775}
1776
1777function walkVariableAssignment(
1778 node: Node,

Callers 1

walkStringFunction · 0.85

Calls 1

walkHeredocRedirectFunction · 0.85

Tested by

no test coverage detected