Repository navigation
ci(deps): repair Dependabot NuGet PRs automatically (#224) - #225
Merged
Merged
Conversation
…t PRs (#224) Port mforce/cluckwork's dependabot-lockfix workflow. After CI on a Dependabot NuGet PR, a credential-free job reverts csproj files Dependabot modified to the merge base and regenerates every packages.lock.json; a second job applies them with trusted default-branch scripts, classifies the diff and pushes a non-forced commit with the App token. CI runs the classifier's tests.
The PR tree controls where each regenerated lock is copied. A symlink on that path would let a poisoned artifact overwrite the trusted scripts in the job that holds the write token. Copy only onto a path with no symlink that git tracks as a 100644 file. Move the csproj revert into lockfix-revert.sh, run from a trusted default-branch checkout in both jobs. Drop the JSON check on the artifact: a poisoned lock can still be valid JSON. State that compute still gets the Actions runtime token.
One source with pattern `*` maps every package to the only source, so the packageSourceMapping block constrained nothing.
This was referenced Oct 26, 2026
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 #224.
Dependabot's NuGet group PRs arrive red. They leave some
packages.lock.jsonfiles stale, so CI's locked restore fails withNU1004, and they add aPackageReferencethatCollectify.PostgresMigrationsdoes not need. I fixed #213, #216 and #223 by hand. This PR adds a workflow that makes the same fix automatically after CI, so a NuGet group PR goes green without a hand commit.Why a workflow and not a project change
I ran the real Dependabot NuGet updater locally on 52cce9e, the commit before #223. Its output matched Dependabot's #223 commit (89a16da) line for line.
<PackageReference Include="..."/>to that project.Collectify.PostgresMigrationsa direct reference of its own did not stop the edit. Turning onCentralPackageTransitivePinningEnabledstopped it, but two packages then failed to update.Neither project-side change I tried fixed both problems. The edits Dependabot makes here only add a
PackageReferencewith no version, because versions live insrc/server/Directory.Packages.props. For these version-only updates, reverting the edit loses nothing, and the #223 replay below shows it.What it does
.github/workflows/dependabot-lockfix.ymlis a port of the lockfix workflow that runs in mforce/cluckwork (mforce/cluckwork#203). The main functional addition is the csproj revert. The port is also hardened beyond the original: the copy step checks each destination path,computereads its scripts and NuGet config from a trusted checkout ofmain, and the lock allowlist lives in one place.workflow_runafter CI. It acts only on a first-attempt run that Dependabot started on adependabot/nuget/*branch of this repo.computejob. This job has no credential that can push. It checks out the exact SHA CI ran on, reverts every csproj the PR modified to the merge base withmain(lockfix-revert.sh), runsdotnet restore src/server/Collectify.slnx --force-evaluatewith a trustednuget.lockfix.configthat allows only nuget.org, and uploads the lock files.commitjob. This job holds a GitHub App token capped tocontents: write, and it runs no project code. Its scripts come frommain. It copies the locks onto the PR checkout and runs the same revert. A classifier (lockfix.mjs) then refuses the commit unless every change is an allowlisted lock file, or a csproj whose content equals the merge base. The push is not forced, so it fails if the branch moved.In
release-please.yml, therelease-pleaseandgroomjobs now read the App secrets from the newapp-tokenenvironment, which onlymaincan use. Nothing else in that file changes.Where to start reading
.github/workflows/dependabot-lockfix.ymlholds the security model and both jobs..github/scripts/lockfix-apply.shchecks paths before copying the locks, then classifies and pushes..github/scripts/lockfix.mjsholds the allowlist and the classifier.lockfix-revert.shis the shared csproj revert..github/scripts/lockfix.test.mjshas 18 tests. CI runs them in the client job, next to the vuln-gate tests.release-please.yml,ci.ymlandCONTRIBUTING.mdare small.Verification
node --test .github/scripts/lockfix.test.mjspasses 18 of 18; I re-ran it myself. The worker removed each of the two destination checks in turn, and a test failed each time. The first worker reported the same for three classifier checks.actionlintis clean on the changed lines, andshellcheckis clean on both scripts.workflow_runalways runs the copy onmain, so the first real run will be the next Dependabot NuGet PR after this merges.Review
Two local review rounds ran before the push.
computejob plus a symlink in the PR tree could overwrite a trusted script while it was running, inside the job that holds the token. The reviewer proved this in a sandbox. The cluckwork original has the same hole. The fix refuses any destination whose real path leaves the PR checkout, or whose git mode is not100644.Remaining risk
computestill receives the Actions runtime token, which can write tomain's cache scope. The image job restores its layer cache by theimage-layers-prefix. To use this, an attacker would need code to run during a restore of a Dependabot-authored tree. NuGet restore does not grant that on its own. The round 1 reviewer traced this path but did not exploit it end to end. I accept this risk for this PR and will track hardening the image cache key as a separate follow-up.Deferred to follow-ups
lockfix.mjsexits 0 ("commit") ifmain()is skipped. Its known trigger is fixed and tested; giving "commit" its own exit code would make it fail closed.lockfix-revert.shcould pass--literal-pathspecs. Any extra match is reverted to the base and checked again by the classifier.After merge
RELEASE_APP_CLIENT_IDandRELEASE_APP_PRIVATE_KEYat https://github.com/mforce/collectify/settings/secrets/actions. Until then, the environment gate protects nothing. Don't delete them before the merge, because release-please onmainreads them until this lands.