Skip to content

Update nuget network policy for release failure - #740

Merged
Nikola Metulev (nmetulev) merged 2 commits into
mainfrom
zateutsch/fix-nuget-release-network-policy
Aug 12, 2026
Merged

Nikola Metulev (nmetulev) merged 2 commits into
mainfrom
zateutsch/fix-nuget-release-network-policy

Conversation

@zateutsch

@zateutsch Zach Teutsch (zateutsch) commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Nuget failing due to a policy mismatch.

@zateutsch
Zach Teutsch (zateutsch) marked this pull request as ready for review August 12, 2026 06:26
Copilot AI balanced review requested due to automatic review settings August 12, 2026 06:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the release pipeline’s network policy to restore NuGet publishing.

Changes:

  • Replaces CFSClean with CFSClean2.
  • Documents why the policy change is required.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 38.62 MB 38.62 MB ✅ 0.0 KB (0.00%)
CLI (x64) 38.73 MB 38.73 MB ✅ 0.0 KB (0.00%)
MSIX (ARM64) 16.02 MB N/A N/A
MSIX (x64) 17.01 MB N/A N/A
NPM Package 33.42 MB N/A N/A
NuGet Package 33.46 MB N/A N/A

Test Results

❌ 4553 passed, 1 failed, 5 skipped out of 4559 tests in 622.4s (-91.1s vs. baseline)

Test Coverage

✅ 89.1% line coverage, 82.4% branch coverage · ✅ no change vs. baseline

CLI Startup Time

44ms median (x64, winapp --version) · ✅ no change vs. baseline


Updated 2026-08-12 06:45:20 UTC · commit 92d0a5c · workflow run

@nmetulev
Nikola Metulev (nmetulev) merged commit 394c5ef into main Aug 12, 2026
33 of 34 checks passed
@nmetulev
Nikola Metulev (nmetulev) deleted the zateutsch/fix-nuget-release-network-policy branch August 12, 2026 17:39
Zach Teutsch (zateutsch) added a commit that referenced this pull request Aug 12, 2026
Nuget failing due to a policy mismatch.
Nikola Metulev (nmetulev) pushed a commit that referenced this pull request Aug 18, 2026
…750)

## Why

1ES network isolation policy is **pipeline-wide** — it can't be scoped
to a stage or job — and `CFSClean` sinkholes `api.nuget.org`. #740
worked around the blocked nuget.org push by moving the whole release
pipeline to `CFSClean2`, but that also dropped CFS feed enforcement from
the Build stage, which is the part that actually restores packages.

We asked 1ES (netiso). Their answer:

> You cannot publish to NuGet under CFSClean (until ESRP Release task
support NuGet, which they are working on this quarter). If your pipeline
publishes to NuGet and does not consume from it, you are eligible for an
S360 exception.

## What this does

Splits the nuget.org push into its own pipeline so the exception is
scoped to a pipeline that *only* publishes:

| Pipeline | Policy | Does |
|---|---|---|
| `.pipelines/release.yml` | `Permissive,CFSClean` (restored) | build,
test, ESRP sign, GitHub/npm/WinGet/MSLearn releases |
| `.pipelines/release-nuget.yml` (new) | `Permissive,CFSClean2` |
downloads the signed `nuget-packages` artifact and pushes it — restores
nothing |

The new pipeline triggers on completion of the **`Release_GitHub`
stage** of `WinDevCLI - Release` for `rel/v*`. Scoping to that stage
matters: a bare completion trigger fires only when the *entire* run
succeeds, which would have made publishing depend on `Release_WinGet`
(whose PAT expires every 90 days), `Release_Npm` and `Release_MSLearn`.
The stage filter reproduces the `dependsOn: Release_GitHub` the stage
had before the move.

## Signature gate

The `Release_NuGet` stage previously sat behind `${{ if
eq(parameters.DoEsrp, 'true') }}`. The split loses that gate, and the
`nuget-packages` artifact is published whether or not ESRP signing ran —
so a `DoEsrp: false` run could have pushed **unsigned** packages to
nuget.org.

The new pipeline verifies signatures before pushing. Checked against
real packages:

| Input | Result |
|---|---|
| Unsigned `.nupkg` (fresh `dotnet pack`) | `NU3004: The package is not
signed` → exit 1 → step throws |
| Microsoft-signed `.nupkg` | Author + Repository signatures listed →
exit 0 |

It also fails if the artifact contains no `.nupkg` at all, so an empty
or misnamed artifact fails loudly instead of silently pushing nothing.

**Known limitation:** `dotnet nuget verify --all` proves a package
carries a trusted signature, not that *we* signed it — a package with
only a nuget.org repository signature also passes. Pinning
`--certificate-fingerprint` would close that, at the cost of a constant
that breaks releases whenever ESRP rotates the cert. The case this gate
exists for — `DoEsrp: false` producing unsigned output — is caught, and
anything foreign reaching `artifacts/nuget/` implies the build is
already compromised.

## Also

- Sets `useDotNetTask: false` on the push. `DotNetCoreCLI@2` can't use
the encrypted API key from the service connection — present in every
working public example of an external nuget.org push (aspire, teams.net,
playwright-dotnet, Agents-M365Copilot) and missing here.
- `scripts/start-release.ps1` now tells the release owner to monitor
both pipelines. Previously it said "Monitor the release pipeline",
singular, so a failure in the new pipeline would be invisible to whoever
cut the release.

## Follow-ups (not in this PR)

- **Register `release-nuget.yml`** as a new ADO pipeline under `\Windows
Developer CLI`, then file the S360 exception against *that* pipeline.
- **Move to `EsrpRelease`** once it supports NuGet — same shape as the
existing npm publish — and drop the exception.
- **SFI-ES4.2.4 Wave 10** will retire `Permissive` from the policy mix.
Both pipelines will need a default-deny base (`Preferred`/`Good`/`Okay`)
plus add-ons. Deliberately kept separate: `release.yml` has egress CI
never exercises (`aka.ms` and `github.com` in the WinGet stage, GitHub
Models for release notes, ESRP), so that migration should be driven by
the netiso dashboard rather than bundled here.

## Testing

All three pipeline YAMLs parse. The release-job cross-pipeline artifact
input and the `stages:` trigger filter were verified against the 1ES/ADO
schemas, and the ADO pipeline path (`\Windows Developer CLI\WinDevCLI -
Release`) was read from the ADO API rather than guessed. The signature
gate and the release banner were both exercised locally.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Zach Teutsch <88554871+zateutsch@users.noreply.github.com>
Copilot-Session: 5c9d1699-fd50-4ecc-af11-2567ff772a4f
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.

3 participants