fix(ai): confine benchmark requests
Disable proxy discovery and redirect following so validated Ollama origins cannot escape the approved network boundary. Run the standard-library safety suite in CI.
This commit is contained in:
@@ -37,6 +37,11 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: job-tracker-ui/package-lock.json
|
cache-dependency-path: job-tracker-ui/package-lock.json
|
||||||
|
|
||||||
|
- name: Test repository safety scripts
|
||||||
|
# Standard-library only and plan-only: this validates the synthetic benchmark harness
|
||||||
|
# without contacting Ollama, pulling a model, or requiring package installation.
|
||||||
|
run: python3 scripts/test-ollama-evaluation.py
|
||||||
|
|
||||||
- name: Build backend
|
- name: Build backend
|
||||||
run: dotnet build JobTrackerApi/JobTrackerApi.csproj --configuration Release
|
run: dotnet build JobTrackerApi/JobTrackerApi.csproj --configuration Release
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,22 @@ TASK_INSTRUCTIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NoRedirectHandler(urllib.request.HTTPRedirectHandler):
|
||||||
|
"""Keep a validated Ollama origin from redirecting a request elsewhere."""
|
||||||
|
|
||||||
|
def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ANN001
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def build_http_opener() -> urllib.request.OpenerDirector:
|
||||||
|
# Do not inherit HTTP(S)_PROXY from the operator shell and never follow redirects. The
|
||||||
|
# explicitly validated loopback/private origin is the only network boundary this tool may use.
|
||||||
|
return urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirectHandler())
|
||||||
|
|
||||||
|
|
||||||
|
HTTP_OPENER = build_http_opener()
|
||||||
|
|
||||||
|
|
||||||
def sha256_text(value: str) -> str:
|
def sha256_text(value: str) -> str:
|
||||||
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
@@ -115,7 +131,7 @@ def request_json(base_url: str, path: str, payload: dict[str, Any] | None = None
|
|||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
method="GET" if data is None else "POST",
|
method="GET" if data is None else "POST",
|
||||||
)
|
)
|
||||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
with HTTP_OPENER.open(request, timeout=timeout) as response:
|
||||||
return json.loads(response.read().decode("utf-8"))
|
return json.loads(response.read().decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
@@ -130,7 +146,7 @@ def stream_generate(base_url: str, payload: dict[str, Any], timeout: float) -> t
|
|||||||
first_token_at: float | None = None
|
first_token_at: float | None = None
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
final: dict[str, Any] = {}
|
final: dict[str, Any] = {}
|
||||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
with HTTP_OPENER.open(request, timeout=timeout) as response:
|
||||||
for raw_line in response:
|
for raw_line in response:
|
||||||
if not raw_line.strip():
|
if not raw_line.strip():
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import importlib.util
|
|||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
SCRIPT = Path(__file__).with_name("run-ollama-evaluation.py")
|
SCRIPT = Path(__file__).with_name("run-ollama-evaluation.py")
|
||||||
@@ -54,6 +56,13 @@ class OllamaEvaluationTests(unittest.TestCase):
|
|||||||
MODULE.validate_base_url("http://ollama:11434", True)
|
MODULE.validate_base_url("http://ollama:11434", True)
|
||||||
self.assertEqual("http://192.168.1.20:11434", MODULE.validate_base_url("http://192.168.1.20:11434", True))
|
self.assertEqual("http://192.168.1.20:11434", MODULE.validate_base_url("http://192.168.1.20:11434", True))
|
||||||
|
|
||||||
|
def test_http_opener_disables_proxies_and_redirects(self):
|
||||||
|
with mock.patch.object(urllib.request, "getproxies", side_effect=AssertionError("proxy discovery must stay disabled")):
|
||||||
|
opener = MODULE.build_http_opener()
|
||||||
|
self.assertFalse(any(isinstance(handler, urllib.request.ProxyHandler) for handler in opener.handlers))
|
||||||
|
self.assertTrue(any(isinstance(handler, MODULE.NoRedirectHandler) for handler in MODULE.HTTP_OPENER.handlers))
|
||||||
|
self.assertIsNone(MODULE.NoRedirectHandler().redirect_request(None, None, 302, "Found", {}, "http://example.invalid"))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user