Repository navigation
Keep the dataset token out of pull-request CI, and fetch it before any variant code - #215
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
MaxGhenis
force-pushed
the
claude/peaceful-hertz-905cce
branch
from
October 22, 2026 01:23
5787e14 to
8d9733b
Compare
The replay-engine job installed each certified variant's pinned third-party environment and ran it (construction guards, premises, diagnostics) before the push-only step that holds PE_UK_PRIVATE_HF_READ_TOKEN. A package from that environment could write $GITHUB_ENV or $GITHUB_PATH, or plant a file, that the token step then ran, and so read the token. The fetch now runs right after the checkout, uv's setup and a read of the index, before the first step that touches .venv-variant. It calls uv by the absolute path setup-uv reports, in an environment emptied to a fixed PATH=/usr/bin:/bin, HOME, the variant and the hub cache, with no project, config or cache, on /usr/bin/python3 under -I, and passes the token on stdin rather than as an argument. The hub cache is the one uk_replay_variants.find_dataset_bytes searches, so a runner that sets XDG_CACHE_HOME no longer sends the dataset somewhere the replay never looks. The step stays push-only, the token stays in that step's env, and the dataset stays on the runner. Tests run the step's script under bash with a recording uv stub and an environment an earlier step made hostile, and check that the fetch comes before every .venv-variant step and after only the checkout, uv, the index read and the pull-request notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…workflow A debug run of this PR's own pull_request CI showed the replay-engine job evaluating secrets.PE_UK_PRIVATE_HF_READ_TOKEN to a masked, non-empty value under the fetch step, which the step's condition then skipped. So every same-repository pull request (the bot's results PRs among them) handed the token to a job that goes on to install and run each variant's third-party packages, where it sits in the runner's memory. The real replay now lives in uk-replay-determinism.yml, which runs on pushes to main only, with its own listing of the certified variants. ci.yml names no dataset token; its replay-engine legs keep the install, guards, premises and diagnostics, and a pull request's run says no real replay ran there. In the new job the fetch still runs before any variant code, now also with every package it loads frozen at --exclude-newer 2026-10-15T00:00:00Z. The job's comment and docs/uk_replay/RELEASE_REPLAY.md say what the order secures (no redirect through $GITHUB_ENV, $GITHUB_PATH or a planted file) and what it does not: the variant's engine still runs as the runner user, with passwordless sudo, while the runner holds the token in memory, and running it as uk_replay_sandbox.sh's unprivileged account is what closes that. The docs no longer claim that pull requests never receive the token. Review fixes in the tests: the only lines naming the token are the emptiness check and the shell's own printf; the script runs under bash -e as GitHub runs it; the pre-fetch steps are checked by word, not substring; the cache test says what it checks; no pull_request workflow names the token; the new workflow's jobs read only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
claude/peaceful-hertz-905cce
branch
from
October 22, 2026 02:58
a3ae69d to
7d3f5b7
Compare
…nters Review of 7d3f5b7: dropping the install step's fetched condition, or ci.yml's pull-request notice, passed every test. Both are now pinned: a leg that fetched nothing installs nothing, since its runner holds the token too. uk_replay_determinism's docstring and RELEASE_REPLAY.md's "Replay in CI" lead-in pointed at the real replay's old home in ci.yml, and the qualification run's run-request steps read as following the CI paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
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.
PE_UK_PRIVATE_HF_READ_TOKENno longer reaches any CI job that runs on a pull request. In the push-only job that holds it, the fetch now runs before any of the variant's code.Problem
ci.yml'sreplay-enginejob installs each certified variant's pinned third-party packages and runs them. It also held the dataset token in a push-only "fetch" step that ran after that code, so a compromised package could redirect the fetch through$GITHUB_ENV,$GITHUB_PATHor a planted file. An independent stack review flagged this, and #210 confirmed it.Review of the first version of this PR found more.
Pull requests received the token too. A debug re-run of this PR's own
pull_requestCI (job 119587048984) evaluatedsecrets.PE_UK_PRIVATE_HF_READ_TOKENto a masked, non-empty value. The step that named it was then skipped, but the runner already had the token. The same leg then installed and ran the variant's packages.That applies to every same-repository PR, including the bot's results PRs, which are exactly where a new release's pins first run. GitHub's docs say secrets a workflow doesn't reference are scrubbed, but referenced ones can be harvested.
Ordering alone can't protect the token where variant code runs beside it. The runner user has passwordless sudo, and the runner holds the job's secrets in memory until the job ends. The tj-actions compromise read secrets from exactly there. This repo's own
uk_replay_sandbox.shheader already says so.What changes
.github/workflows/uk-replay-determinism.yml, on pushes to main only. It holds the real measure-year replay and the token. It lists the certified variants the same wayci.ymldoes, from the index alone. In itsreal-replayjob:uv-pathunder/usr/bin/env -i, keeping onlyPATH=/usr/bin:/bin,HOME,VARIANTandHF_HUB_CACHE.--no-project --isolated --no-config --no-cache --python /usr/bin/python3 --exclude-newer 2026-10-15T00:00:00Z, and Python runs with-I. Every package that loads next to the token is frozen at that date; uv picks the newest version before it.printf, never as a process argument.HF_HUB_CACHEfollows the same ruleuk_replay_variants.find_dataset_bytesuses to find the dataset.ci.ymlnames no dataset token.replay-enginekeeps the install, the installed-environment check, the guards, the premises and the diagnostics on PRs and pushes. A PR run posts a notice that no real replay ran. The step that only reads the index is renamed to say so, and the install step gets the name "Install the variant's exact environment".docs/uk_replay/RELEASE_REPLAY.mdsays where the real replay runs, what the step order secures and what it leaves open. It drops the false claim that pull requests never receive the token.What is still open
In
real-replay, the variant's engine still runs as the runner user while the token is in the runner's memory, so a compromised certified freeze could read it withsudo. The fix is to run the install and the replay asuk_replay_sandbox.sh's unprivileged account, asuk-replay.ymldoes for candidates. That is a redesign of that script and job, so it gets its own PR. Both are said in the job's comment and in the docs.Separately,
ci.yml's push-onlypublish-dbstill names the Supabase secrets. I did not test whether jobs that don't name a secret receive it, so I'm raising this to check, not claiming it.Invariants (tested)
No workflow with a
pull_requestorpull_request_targettrigger contains the token's name. Exactly three steps across all workflows hold it, each under a fixed condition.In
real-replay, the fetch comes before every step that touches.venv-variant. Only the checkout, setup-uv and the index read come before it, and none of them runsuv,uvx,pip,venv,pipelineor$REQUIREMENTS.The script runs under
bash -e, the shell GitHub's log shows for it, with uv replaced by a stub that records what it receives. The job environment would redirect an unshielded uv or Python: impostoruv/env/python3first on PATH,HF_ENDPOINT, a proxy,UV_INDEX_URL,UV_PYTHON,LD_LIBRARY_PATHandPYTHONPATH=.. Even so, uv receives exactly:{PATH, HOME, VARIANT, HF_HUB_CACHE}, apart from what Python itself adds at startup (LC_CTYPE;__CF_USER_TEXT_ENCODINGon macOS)The only lines that name the token are the emptiness check and the
printf.BASH_ENV,LD_PRELOADor abashearlier on PATH would act before the script's first line, so only the step order can keep variant code from setting them.The download asks for exactly the variant's indexed dataset with the token.
fetched=trueis written only after it succeeds. A failed download fails the step and reports nothing. With no token or no sealed namespace, the step posts a notice and never runs uv.Differential test: for all 8 combinations of
HF_HUB_CACHE/HF_HOME/XDG_CACHE_HOMEunset or set to a plain path, the cache the fetch names equals the onefind_dataset_bytessearches. An emptyHF_HOME, or~inside a value, would make the two disagree. The hosted image sets none of these variables.Every job in the new workflow has read-only
contentspermission and nothing more, and both workflows list the matrix from the index.Each of these mutations fails at least one test:
ci.ymlpull_requesttrigger on the new workflowenv -i,-Ior--exclude-neweruv/usr/bin/printfinstead of the shell's owncontents: writeChecked on a real runner
The real fetch only runs on pushes, so this PR's CI cannot exercise it. A throwaway branch ran the step's exact script on ubuntu-latest with uv 0.13.4 (run 39697771059; the branch is deleted). An earlier step had planted these:
uv/python3/envon$GITHUB_PATHHF_ENDPOINT,UV_INDEX_URL,UV_DEFAULT_INDEX,UV_PYTHON, proxies andXDG_CACHE_HOMEin$GITHUB_ENVuv.tomlin the checkout and in~/.config/uvhuggingface_hubpackage in the checkout, withPYTHONPATH=.What happened:
GatedRepoErrorfrom huggingface.co itself./home/runner/.cache/huggingface/hub, wherefind_dataset_byteslooks.The run predates
--exclude-newer; I checked that resolution locally (all 15 packages). One reviewer separately confirmed with real uv that Python receives the token on stdin. The real token's fetch and replay first run on the push that merges this, and I'll watch that run.Reviews
Two independent Opus 5.5 reviews of the first version (Codex lanes were closed) both asked to change the header, which overclaimed. This version addresses that and their test findings.
HF_HOMEand for~inside a value. The test's docstring now names that domain.Locally: the queue, CI-gate, engine-matrix, determinism and results-PR test files give 166 passed.
ruff format --checkis clean, and actionlint reports only the existing SC2129 style hint on the copied index-read step.axiom: n/a: CI only
🤖 Generated with Claude Code