Repository navigation
Artifacts: a per-run file token on links, and scripts run in HTML artifacts - #126
Merged
Merged
Conversation
…ifacts An HTML page an agent built rendered blank: every artifact is served `Content-Security-Policy: sandbox`, which runs no script. Allowing scripts was unsafe not because of the origin (it stays opaque) but because the UI put the DEPLOYMENT key on every artifact URL as `?key=`, and a document can always read its own location. So the key leaves the URL first. `fileToken` (auth.ts) is the key attenuated to one scope: an HMAC of `run:<id>` or `job:<id>` under STRUT_API_KEY, no store, rotates with the key, absent with no key. The listings (`GET /artifacts/:runId`, `GET /jobs/:id/files`) mint it as `token`; the global gate also opens a GET/HEAD under that scope with `?t=` (`carriesFileToken`); the UI fetches it once per run or job (`use-artifact-url.ts`) and its links carry `?t=`. Leaked, a token is worth the files the page already shows. Then `text/html` gets `sandbox allow-scripts` (never `allow-same-origin`), the viewer's frame `sandbox="allow-scripts"`, and the file routes `access-control-allow-origin: *` so a page can fetch its sibling data from its opaque origin. Everything else still runs no script. Tests: the token functions, and a gate test that another run's token, a job's token on a run, a token on a non-file route, a write and a tampered token all stay 401. Docs: API.md, AGENTS.md, plans/jobs.md.
Evanfeenstra
added a commit
to stakwork/stakgraph
that referenced
this pull request
Oct 8, 2026
…ump strut (#1753) * lab: hand a file read carrying strut's file token through to strut; bump strut stakwork/strut#126 took the embed JWT off the strut UI's artifact URLs (an HTML artifact now runs script, and a page can read its own URL), so the <img> and frame loads under /lab went out with no credential and labAuth answered with a Basic challenge — the browser's login dialog, on prod. labAuth now passes a GET/HEAD of one run's artifacts or one job's files that carries strut's file token (`?t=`) straight to strut. Strut (stakwork/strut#127, pinned here) signs the token under STRUT_SECRET_KEY when it has no API key — index.ts derives that from API_TOKEN — and refuses any `?t=` it cannot vouch for whatever its gate's mode, so the token is as good as the credential that fetched the listing it came from, and worth only that run's files. Nothing else changes: a read without `?t=`, a write, or any other path still needs the lab credential, and the path is parsed as isUiAsset parses it. * bump strut to the #127 merge commit
Evanfeenstra
added a commit
to stakwork/hive
that referenced
this pull request
Oct 9, 2026
…on, instead of as a static url frame (#5454) A job's plan.html (strut's `Plan Mode`) reached the canvas as a `url` artifact: an iframe on the strut artifact reader, which keeps strut's `Content-Security-Policy: sandbox`, so the page rendered static. Strut now lets scripts run in an HTML artifact (stakwork/strut#126, #127), but only on a read that carries its per-run file token — a read hive never makes, since the reader authenticates to the swarm with the swarm key. So the page goes the way a stored page does. `mapStrutArtifacts` keeps a strut-written `html` as `html`; `readStrutArtifact` hands its viewer `{ swarmId, key }` (the second shape of `ArtifactContents.html`, beside the stored page's slug) with no fetch of its own; `HtmlArtifactSource` gains a third shape whose proxy URL is the strut reader; and `HtmlArtifactFrame` fetches the bytes with the session, re-types them into a blob and renders them in its `allow-scripts` frame under its CSP meta tag — the posture its stored pages already have. No navigation to the reader, no HTML rendered at a hive URL, and the reader route and its `sandbox` header are unchanged for anyone who opens the URL itself. A swarm page has no share address of its own (`address` may return null). Strut's file token is not involved. Tests: the mapping, the content parser, the loader, the proxy URL and the frame with the new shape. CANVAS_CHAT.md updated.
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.
Why
An HTML page an agent built rendered blank: every artifact is served
Content-Security-Policy: sandbox, which runs no script. Allowing scripts was unsafe not because of the origin (it stays opaque undersandbox allow-scripts) but because the UI put the deployment key on every artifact URL as?key=, and a document can always read its ownlocation. A page an agent wrote could have lifted the key and sent it anywhere. A separate origin would not have fixed that either.What
The key leaves the URL first.
fileToken(src/auth.ts) is the key attenuated to one scope: an HMAC-SHA256 ofrun:<id>orjob:<id>underSTRUT_API_KEY, base64url. No store, rotates with the key, absent when no key is configured.GET /artifacts/:runIdandGET /jobs/:id/filesmint it astoken.?t=(carriesFileToken). A token on any other path, for another run or job, on a write, or tampered, stays 401.web/src/use-artifact-url.ts, cached inapi.ts, cleared when the key changes) and its links carry?t=. Leaked, a token is worth exactly the files the page already shows.Then scripts.
text/htmlgetssandbox allow-scripts, neverallow-same-origin. The viewer's frame issandbox="allow-scripts". The file routes addaccess-control-allow-origin: *so a page canfetchits sibling data from its opaque origin; the token gates the read, not the origin. Everything else still runs no script (an.svgopened as a document could).Verified
sandbox allow-scripts, and without a token or with another run's token the route answered 401.?t=and no key. The web app rendered the link and the viewer frame with?t=.Not in this PR
The
urlfields ofGET …/runs/:runId/artifactsand therun.endcallback carry no token. Hive proxies those with its key, so nothing changes for it; adding the token there would let a host hand a link straight to a browser.