harden ollama metadata egress and cpp path handling (F3, F5)

F3: OLLAMA_BASE_URL pointing at a link-local or cloud-metadata address
(169.254.x, metadata.google.*, 0.0.0.0) now fails closed instead of only
warning, since no real Ollama host lives there and it is a classic SSRF
target. General LAN hosts still warn-and-allow.

F5: the Fortran C-preprocessor call now passes an absolute path. cpp has no
'--' end-of-options terminator, so an attacker-named corpus file like
'-I/etc/x.F90' could otherwise be parsed as a cpp option; an absolute path
always begins with '/' and cannot be.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-06-02 22:23:15 +01:00
co-authored by Claude Opus 4.8
parent c50ffc2285
commit 46a1d4cabb
4 changed files with 72 additions and 5 deletions
+4 -1
View File
@@ -5248,8 +5248,11 @@ def _cpp_preprocess(path: Path) -> bytes:
if not shutil.which("cpp"):
return path.read_bytes()
try:
# Pass an absolute path so a corpus file named like "-I/etc/x.F90" cannot
# be parsed by cpp as an option (cpp does not accept a "--" end-of-options
# terminator). An absolute path always begins with "/".
result = subprocess.run(
["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null", str(path)],
["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null", str(path.resolve())],
capture_output=True,
timeout=30,
)
+13 -3
View File
@@ -1251,11 +1251,13 @@ def estimate_cost(backend: str, input_tokens: int, output_tokens: int) -> float:
def _validate_ollama_base_url(url: str) -> None:
"""Warn (do not raise) if OLLAMA_BASE_URL looks unsafe.
"""Warn if OLLAMA_BASE_URL looks unsafe; hard-block link-local/metadata (F3).
Sending an entire corpus to a non-loopback http:// endpoint silently leaks
proprietary code; we surface a visible stderr warning instead of failing
closed (some users genuinely run Ollama on a LAN host they trust).
proprietary code, but some users genuinely run Ollama on a LAN host they
trust, so a general non-loopback target only warns. A link-local or cloud
metadata address (169.254.x, metadata.google.*) is never a legitimate Ollama
host and is a classic SSRF target, so we fail closed with a ValueError there.
"""
try:
from urllib.parse import urlparse
@@ -1274,6 +1276,14 @@ def _validate_ollama_base_url(url: str) -> None:
)
return
host = (parsed.hostname or "").lower()
if (
host.startswith("169.254.") # link-local, includes the 169.254.169.254 metadata IP
or host in ("metadata.google.internal", "metadata.google.com", "0.0.0.0", "::", "[::]") # nosec B104 - blocklist, not a bind
):
raise ValueError(
f"OLLAMA_BASE_URL points at a link-local/metadata address ({host!r}); refusing to "
"send the corpus there. Set it to a real Ollama host."
)
is_loopback = host in ("localhost", "127.0.0.1", "::1") or host.startswith("127.")
if not is_loopback:
scheme_note = " (UNENCRYPTED)" if parsed.scheme == "http" else ""
+32
View File
@@ -0,0 +1,32 @@
"""The Fortran C-preprocessor path is hardened against argument injection (F5).
A corpus file is attacker-named; cpp does not accept a "--" end-of-options
terminator, so _cpp_preprocess passes an absolute path which can never be parsed
as a cpp option.
"""
from graphify import extract
def test_cpp_preprocess_passes_absolute_path(tmp_path, monkeypatch):
f = tmp_path / "weird.F90"
f.write_text("program x\nend program x\n")
captured = {}
def fake_run(argv, **kwargs):
captured["argv"] = argv
class _Result:
returncode = 0
stdout = b"preprocessed"
return _Result()
monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/cpp")
monkeypatch.setattr("subprocess.run", fake_run)
out = extract._cpp_preprocess(f)
assert out == b"preprocessed"
last_arg = captured["argv"][-1]
assert last_arg.startswith("/"), f"path arg must be absolute, got {last_arg!r}"
assert not last_arg.startswith("-"), "path arg must never look like an option"
+23 -1
View File
@@ -1,7 +1,29 @@
"""Tests for the Ollama backend additions in graphify/llm.py."""
from __future__ import annotations
from graphify.llm import detect_backend, BACKENDS
import pytest
from graphify.llm import detect_backend, BACKENDS, _validate_ollama_base_url
@pytest.mark.parametrize("url", [
"http://169.254.169.254/v1",
"http://169.254.1.5:11434/v1",
"http://metadata.google.internal/v1",
"http://0.0.0.0:11434/v1",
])
def test_ollama_blocks_link_local_and_metadata(url):
"""Link-local / cloud-metadata Ollama targets fail closed (F3)."""
with pytest.raises(ValueError):
_validate_ollama_base_url(url)
def test_ollama_loopback_and_lan_do_not_raise(capsys):
"""Loopback is silent; a general LAN host warns but is allowed (F3)."""
_validate_ollama_base_url("http://localhost:11434/v1")
assert capsys.readouterr().err == ""
_validate_ollama_base_url("http://192.168.1.50:11434/v1") # LAN: warn, not raise
assert "non-loopback" in capsys.readouterr().err
def test_ollama_in_backends():