ci (release): Refactor release workflow and address readiness assessment topics - #371
ci (release): Refactor release workflow and address readiness assessment topics#371turbobobbytraykov wants to merge 18 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR strengthens the IgniteUI.Blazor.Lite release pipeline by splitting the release workflow into least-privilege jobs, adding supply-chain evidence generation (SBOM + attestations), introducing enforced bundle-size budgets, and publishing readiness documents (accessibility, performance, nullable plan). It also improves NuGet package provenance metadata and pins signing identities in-repo.
Changes:
- Refactors the GitHub release workflow into isolated build/sign/pack/evidence/SBOM/publish/attach jobs with digest-verified handoffs.
- Adds enforced static web asset bundle budgets plus reporting (
eng/Check-BundleBudget.ps1,eng/bundle-budgets.json) and publishes related docs. - Improves NuGet provenance and metadata (
Authors, repository URL publishing, embed sources), and documents verification steps in README/CHANGELOG.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/IgniteUI.Blazor.Lite.csproj |
Adds NuGet authors + repository/source metadata; clarifies nullable opt-out tracking. |
README.md |
Documents release verification and links to new readiness/perf/nullable/accessibility docs. |
eng/IG.publickey.hex |
Pins the strong-name public key used for assembly identity validation. |
eng/IG.authenticode-certificates.sha256 |
Adds an allowlist of approved Authenticode signing cert fingerprints. |
eng/Check-BundleBudget.ps1 |
Implements bundle measurement + budget enforcement + release evidence reporting. |
eng/bundle-budgets.json |
Defines bundle groups/totals and budget thresholds used by the checker. |
docs/performance.md |
Publishes performance budget policy and local reproduction steps. |
docs/nullable-migration-plan.md |
Documents staged plan to re-enable nullable analysis for the shipped library. |
docs/accessibility-conformance.md |
Publishes WCAG conformance claim, scope, verification approach, and known failures. |
CHANGELOG.md |
Records new release evidence, signing/provenance changes, and breaking strong-name signing. |
.gitignore |
Ignores artifacts/ produced by release evidence jobs/scripts. |
.github/workflows/igniteui-blazor-lite-release.yml |
New multi-job release workflow with signing, provenance checks, SBOM + attestations, and release attachments. |
.github/scripts/verify-strong-name.ps1 |
Validates strong-name signing against a pinned public key (not just sn -vf). |
.github/scripts/Assert-NuspecRepository.ps1 |
Fails release if nuspec provenance metadata is missing/incorrect. |
.config/sbom-tool/dotnet-tools.json |
Pins sbom-tool via a dedicated tool manifest for the SBOM job. |
Suppressed comments (3)
.github/workflows/igniteui-blazor-lite-release.yml:244
actions/download-artifactis extracting thesigned-assembliesartifact intosrc/, but the artifact paths already start withsrc/...(src/bin/**,src/obj/**,src/wwwroot/**). This will typically createsrc/src/..., causingdotnet pack --no-buildto use the unsigned/unbuilt checkout outputs instead of the downloaded signed ones.
- name: Download signed assemblies
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: signed-assemblies
path: src
digest-mismatch: error
.github/workflows/igniteui-blazor-lite-release.yml:358
- The
evidencejob downloadsbuild-outputintosrc/, but the artifact itself containssrc/wwwroot/**. This will typically extract tosrc/src/wwwroot, whileeng/Check-BundleBudget.ps1expects assets undersrc/wwwroot(fromeng/bundle-budgets.json). That mismatch will make the budget check fail even when the build produced assets.
- name: Download build output
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: build-output
path: src
digest-mismatch: error
eng/Check-BundleBudget.ps1:151
- Same rounding issue for totals: comparing
result.RawKiB/result.GzipKiB(rounded) can let a total exceed its budget without failing the build. Use$raw/$gzipbyte totals for the enforcement condition.
if ($result.RawKiB -gt $total.maxRawKiB) {
$problems += "Total '$($total.id)' is $($result.RawKiB) KiB raw, over its $($total.maxRawKiB) KiB budget."
}
if ($null -ne $total.maxGzipKiB -and $result.GzipKiB -gt $total.maxGzipKiB) {
$problems += "Total '$($total.id)' is $($result.GzipKiB) KiB gzipped, over its $($total.maxGzipKiB) KiB budget."
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…d refactor comments
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
docs/accessibility-conformance.md:34
- This states that the axe and keyboard suites currently run and gate releases, but the status section below says neither suite exists yet. Describe these layers in future tense so consumers do not mistake planned verification for completed evidence.
1. **Automated scanning.** An axe-core scan runs over every component in the Playwright integration suite, asserting the `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, and `wcag22aa` rule sets. It gates pull requests and the release, and the resulting report is attached to the GitHub release as evidence.
2. **Keyboard operation.** Covered by the same suite: tab order, roving tab stops, arrow-key navigation, activation, and focus restoration.
3. **Screen reader smoke testing.** Manual, once per major release, against the matrix below.
docs/performance.md:32
- The generated files do not match exactly one pattern: for example, an
app.<hash>.bundle.jsmatches bothapp.*.bundle.jsand the later*.bundle.jscatch-all. The checker intentionally assigns the first match, so document that ordering rule instead of claiming uniqueness.
Bundle filenames are content-hashed, so budgets are expressed as patterns rather than filenames. Every produced file must match exactly one group — an asset that matches none fails the check, so a new bundle cannot enter the package without someone budgeting for it.
.github/workflows/igniteui-blazor-lite-release.yml:226
- The pack job is also placed in the NuGet publishing environment while holding
id-token: write. NuGet's OIDC policy matches repository/workflow/ref/environment claims rather than the job name, so this job can mint the same short-lived publish credential as the nominal publish-only job. Move package signing to a separate environment and keepnuget-org-publishexclusive to the final job.
environment: nuget-org-publish
permissions:
contents: read
id-token: write
| - name: Restore strong-name key | ||
| shell: pwsh | ||
| env: | ||
| STRONG_NAME_KEY_BASE64: ${{ secrets.IG_STRONG_NAME_KEY }} |
#365 Pull requests are gated by dependency-review (fails on High and above). The release scans what it ships and records the report as a release asset, but stays advisory so a finding never holds up a publish. PR #365 enables nullable analysis outright and makes the staged migration plan moot, so the doc and its references are removed and the csproj nullable block is left exactly as master has it to keep that PR merging cleanly.
…ng the release evidence
There was a problem hiding this comment.
🟡 Changes recommended
The dependency scan can report false success after command failures, and a write-capable CI job uses a mutable action tag.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
.github/workflows/igniteui-blazor-lite-release.yml:446
- The NuGet scan discards every
dotnet listfailure and then initializesnuget_statusto success. If restore or the advisory query fails (for example, because a feed is unavailable), the job reaches the “No vulnerable shipped dependencies reported” branch even though no successful scan occurred. Capture the restore/query exit status and report a scan error separately from a clean result; whether that error warns or blocks can remain consistent with the intended advisory policy.
dotnet list ./src/IgniteUI.Blazor.Lite.csproj package --vulnerable --include-transitive \
> artifacts/dependency-scan/nuget-vulnerable.txt 2>&1 || true
- Files reviewed: 14/15 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Shared OIDC environment scope defeats the intended credential isolation, with additional security and documentation issues requiring correction.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
README.md:205
- This README is also packed as the NuGet package README (
IgniteUI.Blazor.Lite.csproj:40,86-88), but these relative links resolve within NuGet.org rather than back to the repository, and thedocs/files are not packed. Use absolute repository URLs so package consumers can open both documents.
- [Accessibility conformance](docs/accessibility-conformance.md) — the WCAG 2.2 AA claim, its scope, how it is verified, and the known unfixed failures.
- [Performance targets and measurements](docs/performance.md) — the enforced bundle size budgets and the runtime targets.
.github/workflows/ci.yml:28
- This new job has a
pull-requests: writetoken but runs checkout through a mutable tag. The release workflow already pins the same v7.0.1 action to an immutable commit; use that pin here so a retargeted tag cannot execute with the write-enabled job token.
uses: actions/checkout@v7.0.1
.github/workflows/igniteui-blazor-lite-release.yml:151
- The signing, packing, and publishing jobs all use the same GitHub environment with
id-token: write. Environment-based GitHub OIDC subjects do not identify the job, and NuGet Trusted Publishing authorizes the repository/workflow/environment combination, so the signing jobs can also request a NuGet publishing credential (and the publish job matches the Azure federation). This defeats the intended credential isolation. Use separate environments and federated identities for Azure signing and NuGet publishing, and restrict each provider's trust policy accordingly.
environment: nuget-org-publish
permissions:
contents: read
id-token: write
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…SHAs for action versions
There was a problem hiding this comment.
🔵 Needs a closer look
The release-critical security workflow requires human review, particularly the remaining OIDC exposure during MSBuild packing.
Review details
Suppressed comments (2)
.github/workflows/igniteui-blazor-lite-release.yml:290
- This
dotnet packstill runs MSBuild project/imported package targets inside a job that hasid-token: writeand thenuget-org-publishenvironment.--no-build --no-restoredoes not prevent Pack targets from executing, so repository or dependency build logic can request the OIDC token intended for Key Vault signing. To preserve the stated least-privilege boundary, create and validate the unsigned nupkg in a credential-free job, then pass only that immutable artifact to a checkout-free package-signing job with Key Vault OIDC access.
- name: Pack NuGet package
run: >
dotnet pack ./src/IgniteUI.Blazor.Lite.csproj
--configuration ${{ env.BUILD_CONFIGURATION }}
--no-build
--no-restore
.github/workflows/ci.yml:15
- The configured action does not currently block any licenses: no
allow-licensesordeny-licensespolicy is supplied, and an undetected license is only reported rather than failed. This comment therefore promises a license gate that the job does not implement; either add an explicit policy or describe only the High/Critical vulnerability gate.
# Blocks a pull request that would introduce a High or Critical advisory, or a
# dependency under a license the package cannot ship. Pushes to master skip it —
# the action needs the two-commit range a pull request gives it.
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…t of this PR The accessibility conformance and performance documents move to their own pull requests against master, so they can be reviewed as documents rather than as an appendix to a workflow refactor. The README section keeps only the supply chain prose it actually still owns. The 'Verify SBOM output' step and the two SBOM_* budget variables that only it read move to a separate branch: the check needs reworking, and leaving it here would hold the rest of the workflow behind that rework.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
NuGet audit outages can be silently missed, and hidden assets can bypass bundle-budget enforcement.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
.github/workflows/igniteui-blazor-lite-release.yml:29
- The PR description's core architecture still says this is a seven-job workflow and omits
build-assetsandevidence, but the workflow defines nine jobs; the linked successful run also reports nine. Update the Decisions/Validation text so reviewers can accurately assess all job and permission boundaries.
build-assets:
eng/bundle-budgets.json:6
- This policy requires bundle-budget increases to be recorded in
docs/performance.md, but that file does not exist in the repository. Add the referenced document or point the note to an existing audit-trail location so contributors can follow the stated process.
"note": "Budgets are the measured size plus roughly 10-15% headroom. Raise one only with a recorded reason in docs/performance.md; a bundle that grows past its budget is a product decision, not a build detail."
- Files reviewed: 26/28 changed files
- Comments generated: 2
- Review effort level: Balanced
|
|
||
| $nugetReportPath = Join-Path $OutputDirectory 'nuget-vulnerable.json' | ||
|
|
||
| dotnet restore $ProjectPath | Out-Null |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Goal
publish.ymlpreviously ran as a single unauthenticated job: checkout, restore, build, pack, and push to NuGet.org with no signing, no SBOM, and no supply-chain evidence. This branch rebuilds the release workflow so that a published GitHub release produces a strong-named, Authenticode-signed, NuGet-signed package with SPDX and CycloneDX SBOMs, three independent attestations, and an advisory dependency scan, all attached to the release — while keeping every job scoped to only the permissions and secrets it actually uses.Decisions
build→sign-assemblies→pack→ (sbom,dependency-scan) →publish→attach-to-release. Onlysign-assemblies/pack/publishget Key Vault + OIDC (NuGet Deployenvironment);buildhas its ownRelease buildenvironment for the strong-name secret;publishis the only job that can push, and sparse-checks out only.github/scripts+ the cert pin.dependency-scanwaits onpack(though it only reads the project file) so its evidence and the SBOM's dependency data reflect the same point in the pipeline.packrecorded (Get-PackageDigest.ps1), so no job can act on bytes other than what was signed..github/scripts/:Assert-*for gates,New-/Get-/Publish-/Copy-/Invoke-for everything else. Replacesverify-strong-name.ps1.eng/IG.publickey.hex), not justsn.exe -vf's internal consistency..nupkgis re-validated on both strong-name and Authenticode (Assert-PackageSignatures.ps1) —dotnet pack --no-buildonly re-zipsbin/output, so checking just the weaker signal there was a real gap.Assert-NuGetSignature.ps1), not just "any valid signature".sbom-toolgenerates SPDX 2.2 and 3.0 from one invocation (New-Sbom.ps1) — two invocations disagreed on ClearlyDefined licence data and cross-detected each other's manifest as a build component.dotnet-CycloneDXonly sees the.csproj, but the.nupkgalso ships the Vite bundle andigniteui-webcomponentstheme CSS;New-CycloneDxSbom.ps1andNew-NpmCycloneDxSbom.ps1generate the two halves,Merge-CycloneDxSbom.ps1combines them in pure PowerShell (no dependency-manager-distributed tool does this merge:cyclonedx-cliis GitHub-binary-only,cyclonedx-librarycan't deserialize existing JSON), andAssert-CycloneDxSbom.ps1fails if eitherpkg:nuget/*orpkg:npm/*is entirely absent from the result.cyclonedx-npmis a real pinned devDependency, not annpxfetch.dependency-scanis advisory only (dotnet list package --vulnerable), attached as evidence; no PR-time blocking equivalent exists yet.Publish-NuGetPackage.ps1refuses to overwrite an existing NuGet.org version instead of--skip-duplicate, so a rerun's evidence never attaches to a release whose published bytes differ..config/dotnet-tools.json:sign,sbom-tool,cyclonedx) —sign-assemblies/packnow restore tools they don't use, traded for a simpler setup.packpassesRepositoryUrl/RepositoryCommitexplicitly so the nuspec always carries both.sbom.ymlwas deleted; thesbomjob inpublish.ymlis now the only SBOM source.Validation
The CycloneDX merge pipeline has now run in CI and its output was independently re-verified against the actual published release, not just against a local test run.
sbomrunning the full generate-npm / generate-.NET / merge / assert / attest sequence.gh release download) and independently re-verified it, rather than trusting the workflow's own summary: its SHA-256 matches the shipped.sha256sidecar, and re-runningAssert-CycloneDxSbom.ps1against the downloaded file locally reproduces the same result the workflow reported:CycloneDX 1.6: 61 components (44 NuGet, 16 npm), 60 licensed, 44 with an author. Listing thepkg:npm/*components by PURL confirmsigniteui-grid-lite@0.9.0,igniteui-webcomponents@7.2.4, and their full resolved runtime tree (lit,@lit/context,@lit-labs/virtualizer,@lit-labs/ssr-dom-shim,@lit/reactive-element,lit-element,lit-html,@floating-ui/dom/core/utils,igniteui-i18n-core,tslib,@types/trusted-types) are all present with correctly-encoded scoped PURLs (e.g.pkg:npm/%40lit-labs/virtualizer@2.1.1) — this is the concrete resolution of the original review comment.gh run view --log) against both the local test and the downloaded-artifact re-check:npm CycloneDX 1.6: 15 production components,Merged .NET (45 components) and npm (16 components),CycloneDX 1.6: 61 components (44 NuGet, 16 npm)all match exactly..cdx.json+.sha256, both SPDX zips, the dependency-scan zip, the.nupkg+.sha256, and all three attestation bundles (provenance.sigstore.json,sbom-spdx.sigstore.json,sbom-cyclonedx.sigstore.json)..github/scripts/pass PowerShell AST parsing ([System.Management.Automation.Language.Parser]::ParseFile) with zero syntax errors, and.github/workflows/publish.ymlparses as valid YAML (ConvertFrom-Yaml).sbom-tool generatelocally againstsrc/IgniteUI.Blazor.GridLite(withnpm cialready run) and inspected the resulting SPDX 2.2 manifest directly: 97 packages total, 43 of thempkg:npm/*, includingigniteui-grid-lite@0.9.0andigniteui-webcomponents@7.2.4by name. This was the load-bearing assumption behind scoping the CycloneDX-merge fix to CycloneDX only rather than also touching the SPDX generation path.Open
Merge-CycloneDxSbom.ps1's JSON merge is hand-written rather than backed by an upstream tool's test suite. It has now succeeded against this project's real BOMs both locally and in a real release (see Validation), but not against edge cases such as a document with nodependenciesarray, duplicatebom-refs across the two inputs, or vulnerabilities data.New-CycloneDxSbom.ps1andNew-NpmCycloneDxSbom.ps1are pinned to CycloneDX spec version 1.6 explicitly, becausedotnet-CycloneDXdefaults to 1.7 andcyclonedx-npm's newest supported version is 1.6. Ifcyclonedx-npmadds 1.7 support later, revisit whether both sides should move to 1.7 together.