ci(security): add fix-dependabot-alerts automation#328
Conversation
Modernizes the .NET package management stack for the whole repo: 1. Central Package Management (CPM) - introduces root Directory.Packages.props as the single source of truth for every package version. All <PackageReference> entries across the 20 SDK-style csproj files become version-less; the previously per-project version sprawl (e.g. 3 different Microsoft.NET.Test.Sdk versions) collapses to a single declaration. 2. Central Transitive Pinning (CentralPackageTransitivePinningEnabled) - lets a <PackageVersion> entry in Directory.Packages.props force a transitive dependency to a specific version across every project, without having to add per-project top-level <PackageReference> entries. This becomes the override mechanism for fixing transitive security advisories. 3. Lockfile mode (RestorePackagesWithLockFile=true) - generates packages.lock.json next to each csproj on every restore, committed to source control. Enables deterministic CI restores via dotnet restore --locked-mode (opt-in) and gives Dependabot accurate transitive vulnerability detection. 4. NuGetAudit transitive mode (NuGetAuditMode=all) - surfaces known advisories for both direct and transitive dependencies as NU1901-NU1904 warnings at restore time. On by default in SDK 8.0+; this just enables transitive scanning. Side effects (vulnerabilities discovered by the new audit and fixed in this PR): - Microsoft.SemanticKernel 1.68.0 -> 1.71.0 (CVE-2026-25592 / GHSA-2ww3-72rp-wpp4, critical: arbitrary file write via AI agent function calling. Pulled in transitively as Microsoft.SemanticKernel.Core.) - Microsoft.Bcl.Memory pinned to 10.0.4 via transitive pinning (CVE-2026-26127 / GHSA-73j8-2gch-69rq, high: DoS via Base64Url out-of-bounds read. Was being pulled in at 10.0.2 transitively via System.Text.Json 10.x.) Other version reconciliations (chose the higher of conflicting versions): - Microsoft.NET.Test.Sdk: tests on net8.0 use 18.0.1; the orphan net470 project uses VersionOverride=17.5.0 - xunit: net8.0 tests use 2.9.3; orphan net470 project uses VersionOverride=2.4.2 - xunit.runner.visualstudio: net8.0 tests use 3.1.5; orphan net470 project uses VersionOverride=2.4.5 - System.Text.Json 10.0.0 -> 10.0.2 (required by SK 1.71 transitive graph) - Microsoft.Extensions.AI 9.10.2 -> 10.2.0 (required by SK 1.71) - System.Numerics.Tensors 10.0.1 -> 10.0.2 (required by SK 1.71) Verified locally: - dotnet restore TypeChat.sln: clean, zero NU190x audit warnings - dotnet restore tests/EmojiApp/Emoji.sln: clean - dotnet build TypeChat.sln -c Release /warnaserror: 0 warnings, 0 errors - dotnet test tests/TypeChat.UnitTests: 154 passed, 0 failed Note: tests/TypeChat.Tests.Pre6 (net470) is an orphan project not included in TypeChat.sln and not built by CI; it does not generate a lockfile because TypeChat.TestLib only targets net8.0. This is a pre-existing condition unrelated to this change. This PR is preparation for the upcoming fix-dependabot-alerts workflow port - with CPM + transitive pinning + lockfiles in place, automated transitive vulnerability remediation becomes a one-line edit to Directory.Packages.props per fix instead of editing many csproj files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per review feedback: NuGet lockfiles are an opt-in feature (not the .NET community default; the dotnet/runtime repo does not commit them either) and without CI enforcement (dotnet restore --locked-mode) they are write-only churn that adds noise to PRs without providing determinism. This commit removes them; CPM + transitive pinning still gives us a single source of truth for versions, and NuGetAudit (mode=all) still catches transitive CVEs at restore time. The 19 deleted files were introduced by the previous commit on this branch and never enforced. - Delete 19 packages.lock.json files - .gitignore: add packages.lock.json so opportunistic local lockfile restores don't get committed again - Directory.Build.props: drop RestorePackagesWithLockFile (audit kept) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With RestorePackagesWithLockFile removed from Directory.Build.props the default `dotnet restore` no longer produces lockfiles, so the ignore pattern was paranoid. A developer who explicitly runs `dotnet restore --use-lock-file` presumably wants the output visible. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves conflicts from main commits #314 (Microsoft.Extensions.Configuration.Binder 10.0.1 -> 10.0.2) and #319 (Dependabot grouping config). Conflict resolution: - 3 csproj files (examples/typechat.examplesLib, src/typechat.meai, src/typechat.sk): kept ours (version-less PackageReferences, as required by CPM). - Directory.Packages.props: bumped Microsoft.Extensions.Configuration.Binder PackageVersion from 10.0.1 to 10.0.2 to absorb the #314 change centrally so all consumers pick it up via CPM. - .github/dependabot.yml, OpenAIConfig.cs, integration_tests.yml: merged in cleanly from main without conflict. Validation: - dotnet restore: clean, 0 NU190x warnings - dotnet build /warnaserror: 0 warnings, 0 errors - dotnet test tests/TypeChat.UnitTests: 154/154 passed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a daily-scheduled workflow that consumes the repo's open Dependabot alerts, fixes them by editing Directory.Packages.props (Central Package Management), and opens a PR with the result. Fix strategy is single-mode for .NET: - If the alerted package is in Directory.Packages.props, bump its Version to the minimum patched version from the advisory. - Otherwise (transitive dependency), add a new PackageVersion entry pinning the transitive package to the patched version. CPM applies this pin to every project in the solution. After each fix, verify by parsing 'dotnet restore --force' output for NU1901-NU1904 warnings tagged with the alert's GHSA URL. If the warning disappears, run a full clean build and unit-test pass before keeping the change; otherwise roll back via the persisted backup. Includes a DEP_MOCK_ALERTS_FILE env var escape hatch for local seed-testing without hitting the GitHub API. Verified end-to-end: seeded SK 1.71->1.68 downgrade, mocked alert, fixer added a Microsoft.SemanticKernel.Core transitive pin at 1.71.0, restore clean, build + test verified. Modeled on the npm/pip equivalent in microsoft/TypeChat. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Sanitize env (drop GH_TOKEN etc.) on dotnet restore — package- provided MSBuild props/targets evaluated during restore should not see CI credentials. - Capture an audit-warning baseline before applying fixes and reject any change that introduces a new (package, GHSA) audit warning that wasn't there pre-fix. Catches bumps that drag in a different vulnerable transitive. - Never downgrade an existing central PackageVersion entry. If the current pin is already >= the advisory's first_patched_version (stale alert, VersionOverride at the project level, etc.) leave it alone and report unfixable instead of lowering the version. - Record rollback cooldown on restore/audit failure (previously only recorded on build/test failure), so a broken bump isn't retried every scheduled run. - Include NuGet's legacy 4th numeric segment in version comparisons so 4.0.5.10 doesn't compare equal to 4.0.5.2. - Workflow find now prunes ./.git when wiping bin/obj before final clean verification. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Ports a fix-dependabot-alerts automation to typechat.net: a Node script that consumes Dependabot alerts and edits the root Directory.Packages.props to raise vulnerable NuGet packages to their first_patched_version, verifying each change via dotnet restore (NuGetAudit), dotnet build /warnaserror, and dotnet test, with rollback + cooldown on failure. A daily GitHub Actions workflow runs the script under a GitHub App token and opens a single squash PR with the surviving fixes. Several csproj files and Directory.Build.props/Directory.Packages.props are included here (carried from the dependency PR #321) to provide the Central Package Management surface the script edits.
Changes:
- New
tools/scripts/fix-dependabot-alerts.mjsperforming alert grouping, CPM-aware edits, restore/audit verification, build+test verification, and persistent rollback-state cooldown. - New
.github/workflows/fix-dependabot-alerts.ymlscheduled daily; uses an App token, restores rollback-state cache, runs the script, does a final clean build, and opens/dedups a PR. - CPM scaffolding (
Directory.Packages.props,Directory.Build.props, version-less<PackageReference>entries across csproj files) carried over from #321.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tools/scripts/fix-dependabot-alerts.mjs |
Core remediation script: alert fetch/group, CPM edit, restore+audit verification, build/test gate, rollback + cooldown. |
.github/workflows/fix-dependabot-alerts.yml |
Daily workflow: App token auth, runs script, final clean build, creates PR, dedups previous auto PRs. |
Directory.Packages.props |
Central Package Management list of versions (from #321). |
Directory.Build.props |
Enables NuGetAudit in all mode repo-wide (from #321). |
src/Directory.Build.props, examples/Directory.Build.props |
Import the root Directory.Build.props to preserve the props chain. |
src/typechat/TypeChat.csproj, src/typechat.schema/TypeChat.Schema.csproj, src/typechat.program/TypeChat.Program.csproj, src/typechat.sk/TypeChat.SemanticKernel.csproj, src/typechat.meai/TypeChat.Extensions.AI.csproj, src/package/nuget.props |
Drop per-project Version= for CPM. |
tests/TypeChat.UnitTests/...csproj, tests/TypeChat.TestLib/...csproj, tests/TypeChat.IntegrationTests/...csproj, tests/TypeChat.Tests.Pre6/...csproj, tests/EmojiApp/Emoji.csproj, examples/typechat.examplesLib/TypeChat.ExamplesLib.csproj |
Drop Version=; Pre6 uses VersionOverride for net470-compatible pins. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Use bash heredocs for the commit message and PR body so the resulting text isn't rendered as a code block (every line was previously indented 10 spaces by YAML, which the markdown renderer treats as a code block at >=4 spaces). - Bump runner Node to 24 (current LTS). - Refuse to modify a central PackageVersion entry when the current version and the advisory's first_patched_version have incomparable prerelease tags (e.g. 1.2.3-alpha vs 1.2.3-beta). semverGte returns false in both directions for incomparable prereleases, which would have bypassed the no-downgrade guard. Now throws unfixable instead so a human can decide. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the TypeAgent workflow pattern: keep persist-credentials: false on checkout (so the token isn't reachable from dotnet/MSBuild scripts during the verify phase), but at the very end of the job re-inject the workflow's own GITHUB_TOKEN (already scoped to contents:write at the workflow level) for the git push. The App token is now used only where it must be — gh api dependabot/ alerts (which the default token can't reach) and gh pr create (so the PR identity is the bot, not github-actions). Means the App no longer needs Contents permission at all. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Ports the
fix-dependabot-alertsautomation from microsoft/TypeChat (#339) to this repo. Daily scheduled workflow consumes the repo's open Dependabot alerts and opens a PR applying any fixes that survive a full restore + build + test verification.Fix strategy (single-mode for .NET / CPM)
This repo uses Central Package Management — every version lives in the root
Directory.Packages.props. The fixer:gh api.Directory.Packages.props→ bump itsVersionattribute to the advisory'sfirst_patched_version.<PackageVersion Include="..." />pin so CPM applies it to every project.dotnet restore --forceand parses NU1901–NU1904 audit warnings to verify the alert's GHSA is gone AND no new vulnerable transitive was introduced.dotnet build /warnaserror -m:1anddotnet testas the final gate.Safety properties
VersionOverride); refuses to write when current and patched versions have incomparable prereleases.(package, GHSA)warning.GH_TOKEN/GITHUB_TOKEN/NUGET_API_KEY) passed todotnet restore/build/testso package-provided MSBuild props/targets can't exfiltrate CI credentials.(package, props sha).4.0.5.10≠4.0.5.2); prereleases treated as lower than the same release.Local verification
Validated end-to-end with
DEP_MOCK_ALERTS_FILE(a local-only escape hatch that reads alerts from JSON instead of callinggh api):Microsoft.SemanticKernel1.71→1.68, fed a mock critical alert onMicrosoft.SemanticKernel.Core(1.71.0 patched). Fixer inserted the pin,dotnet restorecame back clean, build + test passed, reportedapplied=1.unfixablewith no file modifications.Files
tools/scripts/fix-dependabot-alerts.mjs— fixer script (~26KB)..github/workflows/fix-dependabot-alerts.yml— daily workflow; GitHub App auth; rollback-state cache; final clean-build verification (wipes per-projectbin/obj, prunes./.git).Repo-level prerequisites
The workflow expects these settings configured outside this PR:
DEPENDABOT_APP_ID(Settings → Secrets and variables → Actions → Variables tab) — the GitHub App's ID. App IDs aren't sensitive, so this is a variable, not a secret.DEPENDABOT_APP_PRIVATE_KEY— the App's private key. The App needs Contents: write, Pull requests: write, Dependabot alerts: read.Without those the workflow will fail at the auth step; it will not partially run.
Caveats / known limitations
Directory.Packages.propskeeping its current<PackageVersion Include="X" Version="Y" />adjacency convention; non-adjacent attribute orderings would silently miss the existing entry and add a duplicate pin. Worth replacing the regex with a real XML attribute parser if the convention ever changes.