Repository navigation
Conversation
Synced HTML renders from /file?path=, so a relative src resolves against /api/p/<id>/ rather than the page's folder, and the frame's opaque origin means the browser sends no session cookie for the page's subresources. Every relative image in a report 401'd. The frame now loads from /api/p/<id>/raw/<token>/<path>, minted per reader at /api/p/<id>/frame. The token is an HMAC (key shared through the storage root so any hub process verifies it) over project, the page's folder, reader and a 4h expiry; authGate resolves it before the provider and the raw route serves through serveBlob, so folder rules and revocation apply per request. HtmlView and the visual editor use it; pinned versions keep their blob URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
src="shot.png". That covers every screenshot report underprojects/larkin/. This PR fixes it./file?path=…, soshot.pngresolved to/api/p/<id>/shot.png. And the sandboxed (opaque-origin) frame sends no session cookie for its subresources, so even a correct absolute URL returned 401. I confirmed the second one in Chromium: the request had no cookie,naturalWidthwas 0, and an absolutefile?path=also failed./api/p/<id>/raw/<token>/<path>. The token is a short-lived HMAC credential scoped to the page's folder. Relative paths resolve by ordinary URL rules, and the token stands in for the cookie.../x.png) still don't load.How it works
GET /api/p/{project}/frame?path=<page>(PermRead, samelookupas/file) returns{url}for the caller.GET /api/p/{project}/raw/{token}/{path...}:authGateresolves the token before the provider (frameUser, same shape asappUser).handleRawchecks that the project matches and that the path is under the token's folder, then serves throughserveBlob. So the sandbox CSP, nosniff, folder rules and revocation all apply per request. A?path=in the query can't redirect it. View flags (comment,edit,print) pass through.hub/frame-keyin the storage root (outside every project prefix). It's created on first use withPutIf(VersionAbsent)where the backend supports it, then read back, so all hub processes share one key. Volume/desktop mode has no root and uses a per-process key.useFrameURL(React Query, 1h staleTime against a 4h TTL).HtmlViewandVisualEdituse it. Pinned history versions keep their blob URL. If minting fails, the frame falls back to the oldfile?path=URL.routeLocal.Tests
frame_test.go: relative siblings and subfolders load cookieless;../and%2E%2Eescapes are refused; forged, tampered and expired tokens get 401; a folder rule or revocation applied after minting takes effect; a token for one project fails on another; minting needs read; the query can't override the path; a secondServeron the same root verifies the token.browse.spec.ts: "an html page's relative images load" (sibling + subfolder PNG,naturalWidth === 1).go test ./internal/webapp ./cmd/bdrivepasses. The e2e run over browse/fullscreen/review-comments/inline-edit/print/sidebar/solo-handoff had 125 passing.browse.spec.ts:14(tab title) also fails onorigin/main.review-comments.spec.ts:171flaked once and passed on rerun.Architecture changes
webapp-server.md: addsframeToken(frame.go),Server -frameKey, and the edgesServer ..> frameTokenandframeToken ..> Backend.webapp-frontend.md: addsuseFrameURLto hooks.🤖 Generated with Claude Code