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
2 changes: 1 addition & 1 deletion backend/docs/INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ Event `type` is one of: `files_committed`, `hint`, `capture_check`, `stage`, `ve

`GET /v1/captures/{id}/result` → `200` `ResultResponse` (next section), or a minimal body while the run has not written one. `404` / `410` match status. Signed `verdictUrl` and `previewUrl` last 15 minutes.

`GET /v1/captures/{id}/review` → `200 text/html` when a review page exists, else `404 result_not_ready`.
`GET /v1/captures/{id}/review` → `200 text/html` when a review page exists, else `404 result_not_ready`. The stored page is what compose published; the API rewrites it at serve time so it stays judgeable after the baked signed URLs expire: photo and raw-file links become reviewer-gated `GET /v1/captures/{id}/review/files/{name}` URLs, the three.js import becomes the same-origin vendored module at `GET /v1/review-static/three.module.js`, and a run-status section is injected from live state (per-stage status, error codes, stand-in stages, degraded stage names, run ID and git SHA). Each rewritten link carries a short-lived signed review token (`?rtok=...`, HMAC'd with `UPLOAD_URL_HMAC_SECRET`, scoped to the capture and file, expiring after `HSS_INGEST_RESULT_URL_TTL_S`) so the browser can fetch photos and the module without a bearer header; requests with the reviewer bearer still work, and anonymous or device tokens are refused.

### 8. Amendments

Expand Down
16 changes: 16 additions & 0 deletions backend/server/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import re
import time
from collections.abc import Callable
from datetime import datetime
Expand Down Expand Up @@ -45,10 +46,25 @@ def _log_route(path: str) -> str:
return path


# The review page's browser subresources (photos, raw files, preview.ply and the
# vendored three.js module) cannot send a bearer header, so their routes check the
# bearer-or-signed-review-token pair themselves; the token is HMAC'd with the
# deployment's UPLOAD_URL_HMAC_SECRET and bound to one capture and file. Exempting
# these shapes here does not open them: without a valid token or reviewer bearer
# the routes still answer 401/403 (Greptile finding on PR #284, routes/result.py:125).
_REVIEW_SUBRESOURCE_SHAPES = (
re.compile(r"^/v1/captures/[^/]+/review/files/.+$"),
re.compile(r"^/v1/captures/[^/]+/preview\.ply$"),
re.compile(r"^/v1/review-static/three\.module\.js$"),
)


def _needs_auth(request: Request) -> bool:
path = request.url.path
if path == "/v1/healthz":
return False
if any(shape.fullmatch(path) for shape in _REVIEW_SUBRESOURCE_SHAPES):
return False
return not path.startswith("/v1/uploads/")


Expand Down
Loading