Skip to content

Pin transitive NuGet dependencies via lock files with locked-mode restore (#500)#983

Merged
JayVDZ merged 5 commits into
mainfrom
feature/dependency-pinning
Jul 14, 2026
Merged

Pin transitive NuGet dependencies via lock files with locked-mode restore (#500)#983
JayVDZ merged 5 commits into
mainfrom
feature/dependency-pinning

Conversation

@JayVDZ

@JayVDZ JayVDZ commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the "NuGet transitive dependency pinning" gap from the OWASP Top 10:2025 assessment (A03/A08). Part of #500.

Direct NuGet versions, Docker base image digests, apt packages, GitHub Actions SHAs, and devcontainer features were already pinned; transitive NuGet resolution was the one remaining unpinned layer. This PR closes it and consolidates the pinning policy into a single canonical page.

Changes

  • packages.lock.json committed for all 17 projects; Directory.Build.props sets RestorePackagesWithLockFile everywhere and forces RestoreLockedMode whenever CI=true.
  • Locked-mode restore enforced explicitly in ci.yml (both jobs), release.yml, and all three production Dockerfiles (restore and publish steps).
  • JIM.Web pins RuntimeFrameworkVersion to the ASP.NET Core runtime bundled by the pinned production base image digests. Without this, the implicit Microsoft.AspNetCore.App.Internal.Assets lock entry floats with whichever runtime patch is installed on the restoring machine (devcontainer: 10.0.7; production SDK image: 10.0.9), breaking locked-mode restore non-deterministically. This pin is a new manual sync point when base image digests are bumped; documented in the policy page.
  • JIM.Web's Dockerfile cache-warming restore step deliberately omits locked mode due to an open upstream bug (dotnet/aspnetcore#64897): an isolated single-project locked-mode restore of a Microsoft.NET.Sdk.Web project inside the SDK container drops the implicit framework-reference package from its comparison and fails NU1004 regardless of lock file content. The publish step (its own implicit restore) is unaffected and enforces the lock file for JIM.Web. Worker/Scheduler are unaffected and enforce on both steps.
  • New regenerate-nuget-lock-files workflow: Dependabot does not reliably regenerate NuGet lock files across project references (dependabot-core#12318, dependabot-core#10863), so on pushes to dependabot/nuget/** branches this workflow runs dotnet restore --force-evaluate and pushes any regenerated lock files back as a GitHub-signed commit via the GraphQL createCommitOnBranch mutation, using the jim-automation App token so required checks re-trigger. Expect a transient red first CI run on Dependabot NuGet PRs before the regeneration commit lands; this is documented in the workflow header, the policy page, and the review-dependabot skill.
  • New canonical policy page engineering/DEPENDENCY_PINNING.md: threat model, a per-layer inventory (mechanism / enforcement / update path for all nine dependency layers, including the deliberate softer pins and why), verification and provenance controls, governance, and operational runbooks. Cross-referenced from root CLAUDE.md, engineering/DEVELOPER_GUIDE.md, engineering/COMPLIANCE_MAPPING.md, and the review-dependabot skill (plus two small pre-existing factual corrections found while consolidating: Actions are SHA-pinned not tag-pinned, and the Actions Dependabot cadence is weekly not daily).
  • OWASP assessment doc moved to engineering/plans/doing/ with status updated and this remediation marked implemented; links repointed.

Validation

  • CI=true dotnet restore JIM.sln --locked-mode passes.
  • dotnet build JIM.sln: 0 errors, 0 warnings. dotnet test JIM.sln: 3,356 passed, 35 skipped (category-gated), 0 failed.
  • All three production images built successfully from this branch with the locked publish step (JIM.Web including the OpenAPI generation stage).
  • New/edited workflow YAML validated.

Docs: n/a - supply chain/CI hardening; the changelog entry is 🔒 (security posture), and the policy documentation lives in engineering/ by design.

🤖 Generated with Claude Code

…tore (#500)

- packages.lock.json committed for all 17 projects; Directory.Build.props
  enables RestorePackagesWithLockFile and forces RestoreLockedMode when CI=true
- Explicit locked-mode restore in ci.yml, release.yml, and all three
  production Dockerfiles (restore and publish steps)
- JIM.Web pins RuntimeFrameworkVersion to the production base image runtime
  so the implicit Internal.Assets lock entry is deterministic; its Dockerfile
  cache-warming restore omits locked mode (dotnet/aspnetcore#64897), with
  enforcement at the publish step
- New regenerate-nuget-lock-files workflow pushes signed lock-file
  regeneration commits to Dependabot NuGet branches (Dependabot cannot
  regenerate them across project references itself)
- New canonical policy page engineering/DEPENDENCY_PINNING.md, cross-referenced
  from CLAUDE.md, DEVELOPER_GUIDE, COMPLIANCE_MAPPING, and the
  review-dependabot skill; two pre-existing factual slips corrected
- OWASP assessment moved to engineering/plans/doing/ with remediation status

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JayVDZ and others added 4 commits July 13, 2026 21:10
…nning

# Conflicts:
#	engineering/plans/doing/OWASP_TOP_10_ASSESSMENT.md
…nning

# Conflicts:
#	engineering/COMPLIANCE_MAPPING.md
…ker builds)

Ubuntu published cifs-utils 2:7.0-2ubuntu0.5 (the re-enabled fix for
CVE-2026-12505) to noble-security/noble-updates on 2026-07-13 and withdrew
2:7.0-2ubuntu0.4 from the archive index, so the jim.worker image build 404s
at apt-get install. Bump the pin to the current release; the other three apt
pins (libldap-common, libldap2, libgssapi-krb5-2) are unchanged and still
available. Same failure mode as #896; the apt-pin-check bot's Sunday run
predated the archive rotation by three hours, and Monday's run had not yet
fired.

Verified: full jim-build Docker build (jim.web, jim.worker, jim.scheduler)
succeeds and the stack comes up healthy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cker review

A Dependabot digest bump that moves the aspnet/sdk base images to a new
.NET patch version needs a companion RuntimeFrameworkVersion bump and lock
file regeneration in JIM.Web, and CI cannot catch the omission (locked-mode
restore still passes against the old pin). DEPENDENCY_PINNING.md documents
the coupling but the review skill's Docker section did not check for it;
add the check so reviewers catch the silent drift at review time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JayVDZ
JayVDZ merged commit 6dd1447 into main Jul 14, 2026
18 checks passed
@JayVDZ
JayVDZ deleted the feature/dependency-pinning branch July 14, 2026 08:20
JayVDZ added a commit that referenced this pull request Jul 14, 2026
#1014)

All five remediation gaps are now merged (#1001 rate limiting, #1002
security headers/CSP, #983 NuGet lock files, #984 expression security
review, #1013 security audit events), completing the assessment's
remediation programme. Move the assessment document to plans/done per the
plan filing convention, set its status to Done with the deferred-items
note, and repoint the three engineering documents that referenced the
plans/doing path.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
JayVDZ added a commit that referenced this pull request Jul 14, 2026
….301 (runtime 10.0.9) (#1015)

The devcontainer's base tag (devcontainers/dotnet:1-10.0-noble) was frozen
upstream on an October 2025 RC-era image and never rebuilt after .NET 10
went GA, and the layered SDK pin (10.0.203, runtime 10.0.7) fell behind
JIM.Web's RuntimeFrameworkVersion 10.0.9 when #983 introduced that pin, so
native JIM.Web runs (jim-build-light, Generate-OpenApiDoc.ps1) failed at
launch with "framework 10.0.9 not found" while Docker builds kept working.

- Switch the base to the maintained 2.x image line, digest-pinned per the
  dependency pinning policy so Dependabot resumes proposing digest bumps
  (it cannot flag a dead tag: the digest never changes).
- Bump the layered SDK pin to 10.0.301, which ships runtime 10.0.9, and
  document it as a sync point with RuntimeFrameworkVersion.
- Drop the stale-Yarn-repo workaround (1.x-era; not present in the 2.x
  image) and update the PowerShell-removal rationale (the 2.x base ships
  stable pwsh, but setup.sh's pinned global tool remains canonical).
- Add the devcontainer SDK pin to the RuntimeFrameworkVersion companion
  checklist in DEPENDENCY_PINNING.md and the review-dependabot skill so
  future .NET patch bumps carry it in the same commit.

Verified: the devcontainer image builds clean on the new base, and native
OpenAPI generation (the failing workflow) succeeds under a scratch install
of SDK 10.0.301. A devcontainer rebuild is needed to pick this up.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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