diff --git a/scripts/ci/noema_review_gate.py b/scripts/ci/noema_review_gate.py index 1e4661b7..a022744b 100644 --- a/scripts/ci/noema_review_gate.py +++ b/scripts/ci/noema_review_gate.py @@ -295,6 +295,9 @@ def call_llm(repo: str, number: int, pr: dict[str, Any], diff: str, truncated: b prompt, ], } + if not (api_url.startswith("http://") or api_url.startswith("https://")): + raise ValueError(f"URL must start with http:// or https://, got: {api_url}") + request = urllib.request.Request( api_url, data=json.dumps(payload).encode("utf-8"), @@ -304,7 +307,7 @@ def call_llm(repo: str, number: int, pr: dict[str, Any], diff: str, truncated: b }, method="POST", ) - with urllib.request.urlopen(request, timeout=120) as response: + with urllib.request.urlopen(request, timeout=120) as response: # nosec B310 raw = response.read().decode("utf-8") data = json.loads(raw) content = (((data.get("choices") or [{}])[0].get("message") or {}).get("content") or "").strip() diff --git a/tests/test_noema_review_gate.py b/tests/test_noema_review_gate.py index 0b333ab3..3a366666 100644 --- a/tests/test_noema_review_gate.py +++ b/tests/test_noema_review_gate.py @@ -305,3 +305,11 @@ def test_parse_args_and_main(monkeypatch): with pytest.raises(SystemExit, match="--pr-number must be positive"): noema.main(["--repo", "owner/repo", "--pr-number", "0"]) + +def test_call_llm_rejects_unsafe_url_schemes(monkeypatch): + pr = make_pr() + monkeypatch.setenv("NOEMA_LLM_API_URL", "file:///etc/passwd") + monkeypatch.setenv("NOEMA_LLM_API_KEY", "secret") + + with pytest.raises(ValueError, match="URL must start with http:// or https://"): + noema.call_llm("owner/repo", 1, pr, "diff", False)