MCPcopy Create free account
hub / github.com/codeaashu/claude-code / validateBraceExpansion

Function validateBraceExpansion

src/tools/BashTool/bashSecurity.ts:1751–1892  ·  view source on GitHub ↗

* Detects unquoted brace expansion syntax that Bash expands but shell-quote/tree-sitter * treat as literal strings. This parsing discrepancy allows permission bypass: * git ls-remote {--upload-pack="touch /tmp/test",test} * Parser sees one literal arg, but Bash expands to: --upload-pack="touch

(context: ValidationContext)

Source from the content-addressed store, hash-verified

1749 * Backslash-escaped braces (\{, \}) also suppress expansion.
1750 */
1751function validateBraceExpansion(context: ValidationContext): PermissionResult {
1752 // Use pre-strip content to avoid false negatives from stripSafeRedirections
1753 // creating backslash adjacencies (e.g., `\>/dev/null{a,b}` → `\{a,b}` after
1754 // stripping, making isEscapedAtPosition think the brace is escaped).
1755 const content = context.fullyUnquotedPreStrip
1756
1757 // SECURITY: Check for MISMATCHED brace counts in fullyUnquoted content.
1758 // A mismatch indicates that quoted braces (e.g., `'{'` or `"{"`) were
1759 // stripped by extractQuotedContent, leaving unbalanced braces in the content
1760 // we analyze. Our depth-matching algorithm below assumes balanced braces —
1761 // with a mismatch, it closes at the WRONG position, missing commas that
1762 // bash's algorithm WOULD find.
1763 //
1764 // Exploit: `git diff {@'{'0},--output=/tmp/pwned}`
1765 // - Original: 2 `{`, 2 `}` (quoted `'{'` counts as content, not operator)
1766 // - fullyUnquoted: `git diff {@0},--output=/tmp/pwned}` — 1 `{`, 2 `}`!
1767 // - Our depth-matcher: closes at first `}` (after `0`), inner=`@0`, no `,`
1768 // - Bash (on original): quoted `{` is content; first unquoted `}` has no
1769 // `,` yet → bash treats as literal content, keeps scanning → finds `,`
1770 // → final `}` closes → expands to `@{0} --output=/tmp/pwned`
1771 // - git writes diff to /tmp/pwned. ARBITRARY FILE WRITE, ZERO PERMISSIONS.
1772 //
1773 // We count ONLY unescaped braces (backslash-escaped braces are literal in
1774 // bash). If counts mismatch AND at least one unescaped `{` exists, block —
1775 // our depth-matching cannot be trusted on this content.
1776 let unescapedOpenBraces = 0
1777 let unescapedCloseBraces = 0
1778 for (let i = 0; i < content.length; i++) {
1779 if (content[i] === '{' && !isEscapedAtPosition(content, i)) {
1780 unescapedOpenBraces++
1781 } else if (content[i] === '}' && !isEscapedAtPosition(content, i)) {
1782 unescapedCloseBraces++
1783 }
1784 }
1785 // Only block when CLOSE count EXCEEDS open count — this is the specific
1786 // attack signature. More `}` than `{` means a quoted `{` was stripped
1787 // (bash saw it as content, we see extra `}` unaccounted for). The inverse
1788 // (more `{` than `}`) is usually legitimate unclosed/escaped braces like
1789 // `{foo` or `{a,b\}` where bash doesn't expand anyway.
1790 if (unescapedOpenBraces > 0 && unescapedCloseBraces > unescapedOpenBraces) {
1791 logEvent('tengu_bash_security_check_triggered', {
1792 checkId: BASH_SECURITY_CHECK_IDS.BRACE_EXPANSION,
1793 subId: 2,
1794 })
1795 return {
1796 behavior: 'ask',
1797 message:
1798 'Command has excess closing braces after quote stripping, indicating possible brace expansion obfuscation',
1799 }
1800 }
1801
1802 // SECURITY: Additionally, check the ORIGINAL command (before quote stripping)
1803 // for `'{'` or `"{"` INSIDE an unquoted brace context — this is the specific
1804 // attack primitive. A quoted brace inside an outer unquoted `{...}` is
1805 // essentially always an obfuscation attempt; legitimate commands don't nest
1806 // quoted braces inside brace expansion (awk/find patterns are fully quoted,
1807 // like `awk '{print $1}'` where the OUTER brace is inside quotes too).
1808 //

Callers

nothing calls this directly

Calls 2

isEscapedAtPositionFunction · 0.85
logEventFunction · 0.85

Tested by

no test coverage detected