From d846d161f876cb0a84a8e81d3959f07f0f4b8514 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:12:30 +0000 Subject: [PATCH 1/2] fix(ci): validate URL schemes in noema_review_gate.py to prevent SSRF Add validation to ensure `api_url` starts with `http://` or `https://` before passing it to `urllib.request.urlopen`. Suppress Bandit B310 warning now that the input is safely validated. --- scripts/ci/noema_review_gate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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() From 08eb4c35300a8e562e21785315f3109c2c69a2e9 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:21:18 +0000 Subject: [PATCH 2/2] fix(ci): validate URL schemes in noema_review_gate.py to prevent SSRF Add validation to ensure `api_url` starts with `http://` or `https://` before passing it to `urllib.request.urlopen`. Suppress Bandit B310 warning now that the input is safely validated. Also added test coverage for the scheme validation. --- tests/test_noema_review_gate.py | 8 ++++++++ 1 file changed, 8 insertions(+) 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)