Skip to content

Artifacts: a strut behind a host's gate signs file tokens too; only a token read runs script - #127

Merged
Evanfeenstra merged 1 commit into
mainfrom
fix/artifact-token-host-gate
Oct 8, 2026
Merged

Evanfeenstra merged 1 commit into
mainfrom
fix/artifact-token-host-gate

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Contributor

Why

#126 took the key off artifact URLs. But a strut behind a host's gate, like mcp's /lab, has no STRUT_API_KEY of its own: it minted no token, the UI's <img> and frame URLs went out bare, and labAuth answered with a Basic challenge, which is the browser's login dialog. That is what prod showed.

What

  • Signing. fileToken signs with STRUT_API_KEY, else STRUT_SECRET_KEY when it is set, never its dev default. The lab derives the secret key from API_TOKEN, so it signs.
  • The invariant a host can rely on. Every file route refuses a ?t= strut cannot vouch for, whatever the gate's mode (carriesBadFileToken). A host gate in front of an open strut can hand a token-bearing file read straight through, and strut judges it. With no secret at all nothing is minted and every ?t= is refused.
  • Scripts only on a token read. allow-scripts goes out only when the request was opened by its file token. Read with the key as ?key=, or through a host's gate that carries its own credential in the URL, HTML runs no script: strut cannot know what else that URL holds.
  • UI fallback. When the listing minted no token (a strut with no secret, behind a host gate that takes its credential as ?key=), the link carries the key as it used to, and the server serves that page with no script. Old behavior exactly.

Verified

1368 unit tests pass; engine and web type-checks and the web build are clean. New tests: the secret-key signing and precedence, a bad ?t= refused on an open strut (listing, artifact file, job listing), ?key= reads served without scripts, the no-secret case.

The other half

stakgraph: labAuth hands GET/HEAD under /lab/artifacts/:id and /lab/jobs/:id/files carrying ?t= through to strut, and bumps the pin to this commit. PR to follow.

…ly a token read runs script

#126 took the key off artifact URLs, but a strut behind a
HOST's gate (mcp's /lab) has no STRUT_API_KEY of its own: it minted no
token, the UI's <img> and frame URLs went out bare, and labAuth answered
with a Basic challenge — the browser's login dialog, on prod.

Three things, so the lab (and any host gate) works with tokens:

- `fileToken` signs with STRUT_API_KEY, else STRUT_SECRET_KEY when set
  (never its dev default). The lab derives the secret key from
  API_TOKEN, so it signs.
- Every file route refuses a `?t=` strut cannot vouch for, whatever the
  gate's mode (`carriesBadFileToken`). That is the promise a host gate
  relies on when it hands a token-bearing file read straight through.
- `allow-scripts` only on a request the token opened. Read with the key
  as `?key=`, or through a host's gate with its own credential in the
  URL, HTML runs no script: strut cannot know what else that URL holds.

And the UI falls back to `?key=` when the listing minted no token (a
strut with no secret at all behind a host gate), which is the old
behavior exactly — served with no script.

mcp's half: labAuth hands GET/HEAD under /lab/artifacts/:id and
/lab/jobs/:id/files carrying `?t=` through to strut.
@Evanfeenstra
Evanfeenstra merged commit c439836 into main Oct 8, 2026
1 check passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant