Repository navigation
fix(ci): isolate the lockfix restore and drop the cross-run image cache (#226) - #229
Merged
Merged
Conversation
compute ran dotnet restore of a Dependabot tree on the runner host, in main's cache scope, where restore-time code could reach the Actions runtime token through a later action. The restore now runs in the SDK image the default branch's Dockerfile pins, with no runner env, no docker socket, only pr/ and the restore config mounted, and pr/.git hidden. Staging refuses a lock path that goes through a symlink, since the container now writes pr/. Refs #226
Any job on main can create a cache entry main's image job reads, and the restore-keys ladder took the newest match. The layer cache now lives only within the image job: build 1 writes it and the push build reads it, so the pushed amd64 layers are still the scanned ones. Refs #226
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.
Closes #226.
The
computejob of the Dependabot lockfix (#225) restores a Dependabot-authored tree underworkflow_run, so it runs inmain's cache scope. It holds no credential that can push. Code running during that restore, though, could reach the Actions runtime token and create cache entries thatmain's own CI reads. Those include the image layers the publish job builds from, and the Trivy database and binary the scan job runs. I accepted that risk for #225 and am closing it here.Why not just harden the cache key
GitHub scopes caches per branch, and any job on
maincan create any key that doesn't exist yet. No prefix is reserved for one workflow, so a "trusted" key prefix would close nothing. The fix has to remove the untrusted writer.Changes
.github/scripts/lockfix-restore.shreads the SDK image's@sha256pin from the trustedDockerfile, and fails unless it finds exactly one. It runsdotnet restorein that image with no runner environment, no Docker socket,--cap-drop ALLandno-new-privileges. Only the PR checkout and the trusted NuGet config are mounted. The PR checkout's.gitis hidden behind an empty tmpfs, so the container can't leave a git config or hook for the runner to run later. The container still has network access, which restore needs for nuget.org.setup-dotnetand the restore on the runner are gone..github/scripts/lockfix-stage.shcopies the locks into the artifact. It refuses a lock whose path goes through a symlink, or that is not a regular file. Its tests are inlockfix-stage.test.mjs, which CI now runs next to the classifier tests.ci.ymlno longer restores/tmp/.buildx-cachefrom the Actions cache. The amd64 scan build writes the cache, and the push build reads it in the same job, so the job still pushes the amd64 layers it scanned and booted. Before this change, only the amd64 build wrote that cache, so arm64 built fresh then too; that is unchanged. On the main runs the design looked at (39485202541, 39550235125, 40390728311, 40422300059), the amd64 build took 65 to 73 s with the cache, and restoring plus saving the cache took 13 to 28 s. Those are different commits on different runners, so they don't measure the saving exactly. The change also stops adding a new cache entry of about 940 MB per main run. Existing entries stay until they are evicted or deleted.ci.ymlanddocs/decisions/113-releases.mdnow describe the new setup. The decision record also no longer says arm64 builds from the cache, which did not match the workflow before this change either.Verification
.git/confignever reached the runner, and staging refused the symlinked lock it planted. A control run with the isolation broken on purpose showed four leaks (a plantedACTIONS_RUNTIME_TOKEN, the Docker socket, a host process and the trusted directory) and failed. The control did not remove the.gitmask, so it does not demonstrate that boundary.docker buildxbuild on a fresh builder, reading only the cache the first build wrote, reused every Dockerfile step except the base-image pulls. The real push build, which also builds arm64 and pushes, runs only on GitHub.node --testpasses 23 of 23 across the two lockfix test files.shellcheckandactionlintare clean on the changed files..git, and refused hardlinks and device nodes. It found no defects. I applied two of its nits, the capability flags and the arm64 wording. Round 2 reviewed those and found no defects.Only GitHub can show