mirror of
https://github.com/usestrix/strix.git
synced 2026-08-21 18:52:47 +02:00
Session-gate the /api/runs history list
This commit is contained in:
@@ -222,10 +222,14 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
||||
self.end_headers()
|
||||
|
||||
def _handle_api(self, path: str, query: dict[str, list[str]]) -> None:
|
||||
# The launched run is always viewable with no verification. Only the
|
||||
# cross-run history list (/api/runs) is gated.
|
||||
# The launched run is always viewable with no verification. The
|
||||
# cross-run history list (/api/runs) unlocks its entries only for a
|
||||
# caller that holds this process's session capability *and* is email
|
||||
# verified, so merely reaching an exposed --host port never leaks the
|
||||
# run list (the payload still advertises the count as a teaser).
|
||||
if path == "/api/runs":
|
||||
payload = build_runs_payload(state.base_dir, verified=auth.is_verified())
|
||||
unlocked = self._has_session() and auth.is_verified()
|
||||
payload = build_runs_payload(state.base_dir, verified=unlocked)
|
||||
self._send_json(HTTPStatus.OK, payload)
|
||||
return
|
||||
if path == "/api/capabilities":
|
||||
|
||||
@@ -405,6 +405,39 @@ def test_historical_run_data_requires_verification(
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
def test_runs_list_requires_session_and_verification(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
launched = _make_run(tmp_path, "launched", status="completed", end_time="2026-01-01T00:00:00Z")
|
||||
_make_run(tmp_path, "other", status="completed", end_time="2026-01-01T00:00:00Z")
|
||||
_bundle(tmp_path, monkeypatch)
|
||||
|
||||
monkeypatch.setattr("strix.viewer.auth.is_verified", lambda: True)
|
||||
|
||||
def _runs(cookie: str | None) -> dict[str, object]:
|
||||
headers = {"Cookie": cookie} if cookie else {}
|
||||
req = urllib.request.Request(f"{url}/api/runs", headers=headers) # noqa: S310
|
||||
with urllib.request.urlopen(req) as resp: # noqa: S310 - localhost test server
|
||||
return dict(json.loads(resp.read()))
|
||||
|
||||
httpd, url, token = serve(launched, open_browser=False)
|
||||
try:
|
||||
# A cookie-less caller (even with the machine verified) only sees the
|
||||
# teaser count, never the run entries.
|
||||
payload = _runs(None)
|
||||
assert payload["locked"] is True
|
||||
assert payload["count"] == 2
|
||||
assert payload["runs"] == []
|
||||
|
||||
# With the session cookie and verification, the entries unlock.
|
||||
payload = _runs(_session_cookie(url, token))
|
||||
assert payload["locked"] is False
|
||||
assert {r["name"] for r in payload["runs"]} == {"launched", "other"} # type: ignore[attr-defined]
|
||||
finally:
|
||||
httpd.shutdown()
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
def test_server_rejects_path_traversal(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
run_dir = _make_run(tmp_path, "guard", status="completed", end_time="2026-01-01T00:00:00Z")
|
||||
secret = tmp_path / "secret.txt"
|
||||
|
||||
Reference in New Issue
Block a user