Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions solvent/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ def get_job(job_id: str):
metrics = agent.t.get_metrics(job_id)
return {"job": dict(row), "metrics": metrics}

def _is_local_request(request: Request | None) -> bool:
if request is None:
return False
client_host = getattr(request.client, "host", "") if request.client else ""
return client_host in ("127.0.0.1", "::1", "localhost", "testclient")

@app.get("/api/receipt/{job_id}")
def get_receipt(job_id: str, token: str = "", request: Request = None):
"""Return a plaintext job receipt.
Expand All @@ -246,12 +252,7 @@ def get_receipt(job_id: str, token: str = "", request: Request = None):
raise HTTPException(404, "job not found")

# Auth: localhost OR valid delivery token
is_local = False
if request is not None:
client_host = getattr(request.client, "host", "") if request.client else ""
is_local = client_host in ("127.0.0.1", "::1", "localhost", "testclient")

if not is_local:
if not _is_local_request(request):
if not verify_delivery_token(job_id, token):
raise HTTPException(403, "invalid or expired delivery token")

Expand Down Expand Up @@ -309,7 +310,9 @@ def get_brief(job_id: str, token: str = ""):
raise HTTPException(404, "brief not found")

@app.get("/api/briefs")
def list_briefs():
def list_briefs(request: Request = None):
if not _is_local_request(request):
raise HTTPException(403, "brief listing is only available locally")
reports_dir = reports_dir_fn()
if not reports_dir.is_dir():
return []
Expand All @@ -320,7 +323,9 @@ def list_briefs():
return sorted(list(stems))

@app.get("/api/briefs/{job_id}")
def get_brief_api(job_id: str):
def get_brief_api(job_id: str, token: str = "", request: Request = None):
if not _is_local_request(request) and not verify_delivery_token(job_id, token):
raise HTTPException(403, "invalid or expired delivery token")
reports_dir = reports_dir_fn().resolve()

path_html = (reports_dir / f"{job_id}.html").resolve()
Expand Down
25 changes: 25 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ def test_brief_serving_requires_exact_job_id_match(self):
self.assertEqual(response.status_code, 200)
self.assertIn("Private brief", response.text)

def test_api_brief_routes_require_localhost_or_delivery_token(self):
try:
from fastapi.testclient import TestClient
except ImportError:
self.skipTest("FastAPI test client is not installed")

client = TestClient(create_app())

listed = client.get("/api/briefs")
self.assertEqual(listed.status_code, 200)
self.assertIn("victim-job", listed.json())

response = client.get("/api/briefs/victim-job")
self.assertEqual(response.status_code, 200)
self.assertIn("Private brief", response.text)

remote_client = TestClient(create_app(), client=("203.0.113.10", 12345))
self.assertEqual(remote_client.get("/api/briefs").status_code, 403)
self.assertEqual(remote_client.get("/api/briefs/victim-job").status_code, 403)

token = make_delivery_token("victim-job")
response = remote_client.get(f"/api/briefs/victim-job?token={token}")
self.assertEqual(response.status_code, 200)
self.assertIn("Private brief", response.text)

def test_interactive_dashboard_routes(self):
try:
from fastapi.testclient import TestClient
Expand Down