MCPcopy Create free account
hub / github.com/freecodexyz/free-code / validateMidWordHash

Function validateMidWordHash

src/tools/BashTool/bashSecurity.ts:1919–1962  ·  view source on GitHub ↗
(context: ValidationContext)

Source from the content-addressed store, hash-verified

1917}
1918
1919function validateMidWordHash(context: ValidationContext): PermissionResult {
1920 const { unquotedKeepQuoteChars } = context
1921 // Match # preceded by a non-whitespace character (mid-word hash).
1922 // shell-quote treats mid-word # as comment-start but bash treats it as a
1923 // literal character, creating a parser differential.
1924 //
1925 // Uses unquotedKeepQuoteChars (which preserves quote delimiters but strips
1926 // quoted content) to catch quote-adjacent # like 'x'# — fullyUnquotedPreStrip
1927 // would strip both quotes and content, turning 'x'# into just # (word-start).
1928 //
1929 // SECURITY: Also check the CONTINUATION-JOINED version. The context is built
1930 // from the original command (pre-continuation-join). For `foo\<NL>#bar`,
1931 // pre-join the `#` is preceded by `\n` (whitespace → `/\S#/` doesn't match),
1932 // but post-join it's preceded by `o` (non-whitespace → matches). shell-quote
1933 // operates on the post-join text (line continuations are joined in
1934 // splitCommand), so the parser differential manifests on the joined text.
1935 // While not directly exploitable (the `#...` fragment still prompts as its
1936 // own subcommand), this is a defense-in-depth gap — shell-quote would drop
1937 // post-`#` content from path extraction.
1938 //
1939 // Exclude ${# which is bash string-length syntax (e.g., ${#var}).
1940 // Note: the lookbehind must be placed immediately before # (not before \S)
1941 // so that it checks the correct 2-char window.
1942 const joined = unquotedKeepQuoteChars.replace(/\\+\n/g, match => {
1943 const backslashCount = match.length - 1
1944 return backslashCount % 2 === 1 ? '\\'.repeat(backslashCount - 1) : match
1945 })
1946 if (
1947 // eslint-disable-next-line custom-rules/no-lookbehind-regex -- .test() with atom search: fast when # absent
1948 /\S(?<!\$\{)#/.test(unquotedKeepQuoteChars) ||
1949 // eslint-disable-next-line custom-rules/no-lookbehind-regex -- same as above
1950 /\S(?<!\$\{)#/.test(joined)
1951 ) {
1952 logEvent('tengu_bash_security_check_triggered', {
1953 checkId: BASH_SECURITY_CHECK_IDS.MID_WORD_HASH,
1954 })
1955 return {
1956 behavior: 'ask',
1957 message:
1958 'Command contains mid-word # which is parsed differently by shell-quote vs bash',
1959 }
1960 }
1961 return { behavior: 'passthrough', message: 'No mid-word hash' }
1962}
1963
1964/**
1965 * Detects when a `#` comment contains quote characters that would desync

Callers

nothing calls this directly

Calls 1

logEventFunction · 0.85

Tested by

no test coverage detected