Skip to content

ci (release): Refactor release workflow and address readiness assessment topics - #371

Open
turbobobbytraykov wants to merge 18 commits into
masterfrom
btraykov/release-workflow-refactoring
Open

ci (release): Refactor release workflow and address readiness assessment topics#371
turbobobbytraykov wants to merge 18 commits into
masterfrom
btraykov/release-workflow-refactoring

Conversation

@turbobobbytraykov

@turbobobbytraykov turbobobbytraykov commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Goal

publish.yml previously 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

  • Seven single-purpose jobs: buildsign-assembliespack → (sbom, dependency-scan) → publishattach-to-release. Only sign-assemblies/pack/publish get Key Vault + OIDC (NuGet Deploy environment); build has its own Release build environment for the strong-name secret; publish is the only job that can push, and sparse-checks out only .github/scripts + the cert pin.
  • dependency-scan waits on pack (though it only reads the project file) so its evidence and the SBOM's dependency data reflect the same point in the pipeline.
  • Every handoff re-verifies the package digest against what pack recorded (Get-PackageDigest.ps1), so no job can act on bytes other than what was signed.
  • All inline PowerShell moved to named scripts under .github/scripts/: Assert-* for gates, New-/Get-/Publish-/Copy-/Invoke- for everything else. Replaces verify-strong-name.ps1.
  • Strong-name check now pins the actual public key (eng/IG.publickey.hex), not just sn.exe -vf's internal consistency.
  • The packed .nupkg is re-validated on both strong-name and Authenticode (Assert-PackageSignatures.ps1) — dotnet pack --no-build only re-zips bin/ output, so checking just the weaker signal there was a real gap.
  • NuGet signature verification is now certificate-fingerprint-pinned (Assert-NuGetSignature.ps1), not just "any valid signature".
  • sbom-tool generates 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.
  • CycloneDX is a second SBOM format, merged from separate .NET and npm documents. dotnet-CycloneDX only sees the .csproj, but the .nupkg also ships the Vite bundle and igniteui-webcomponents theme CSS; New-CycloneDxSbom.ps1 and New-NpmCycloneDxSbom.ps1 generate the two halves, Merge-CycloneDxSbom.ps1 combines them in pure PowerShell (no dependency-manager-distributed tool does this merge: cyclonedx-cli is GitHub-binary-only, cyclonedx-library can't deserialize existing JSON), and Assert-CycloneDxSbom.ps1 fails if either pkg:nuget/* or pkg:npm/* is entirely absent from the result. cyclonedx-npm is a real pinned devDependency, not an npx fetch.
  • Three attestations (provenance, SPDX, CycloneDX), not one, all bound to the same re-verified digest.
  • dependency-scan is advisory only (dotnet list package --vulnerable), attached as evidence; no PR-time blocking equivalent exists yet.
  • Publish-NuGetPackage.ps1 refuses 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.
  • Tool manifest consolidated to one file (.config/dotnet-tools.json: sign, sbom-tool, cyclonedx) — sign-assemblies/pack now restore tools they don't use, traded for a simpler setup.
  • pack passes RepositoryUrl/RepositoryCommit explicitly so the nuspec always carries both.
  • The old PR-label-triggered sbom.yml was deleted; the sbom job in publish.yml is 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.

  • Successful run of Publish NuGet Package, release 0.1.2-alpha.4. All seven jobs succeeded, including sbom running the full generate-npm / generate-.NET / merge / assert / attest sequence.
  • Downloaded the actual released CycloneDX document (gh release download) and independently re-verified it, rather than trusting the workflow's own summary: its SHA-256 matches the shipped .sha256 sidecar, and re-running Assert-CycloneDxSbom.ps1 against 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 the pkg:npm/* components by PURL confirms igniteui-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.
  • Cross-checked the run's own logs (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.
  • All expected release assets are attached: .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).
  • All 14 scripts under .github/scripts/ pass PowerShell AST parsing ([System.Management.Automation.Language.Parser]::ParseFile) with zero syntax errors, and .github/workflows/publish.yml parses as valid YAML (ConvertFrom-Yaml).
  • Confirmed the SPDX side already covers npm components, independently of this change. Ran sbom-tool generate locally against src/IgniteUI.Blazor.GridLite (with npm ci already run) and inspected the resulting SPDX 2.2 manifest directly: 97 packages total, 43 of them pkg:npm/*, including igniteui-grid-lite@0.9.0 and igniteui-webcomponents@7.2.4 by name. This was the load-bearing assumption behind scoping the CycloneDX-merge fix to CycloneDX only rather than also touching the SPDX generation path.
  • Not verifiable further: the specific Authenticode/strong-name/NuGet-signature bytes and the exact attestation subject digests for this release weren't independently re-derived in this session beyond confirming the jobs reported success and the evidence files exist; that trust chain was already exercised and described for the prior alpha.7/alpha.8 releases.

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 no dependencies array, duplicate bom-refs across the two inputs, or vulnerabilities data.
  • Both New-CycloneDxSbom.ps1 and New-NpmCycloneDxSbom.ps1 are pinned to CycloneDX spec version 1.6 explicitly, because dotnet-CycloneDX defaults to 1.7 and cyclonedx-npm's newest supported version is 1.6. If cyclonedx-npm adds 1.7 support later, revisit whether both sides should move to 1.7 together.

Copilot AI lite review requested due to automatic review settings August 27, 2026 16:59
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:02 — with GitHub Actions Active

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

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-artifact is extracting the signed-assemblies artifact into src/, but the artifact paths already start with src/... (src/bin/**, src/obj/**, src/wwwroot/**). This will typically create src/src/..., causing dotnet pack --no-build to 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 evidence job downloads build-output into src/, but the artifact itself contains src/wwwroot/**. This will typically extract to src/src/wwwroot, while eng/Check-BundleBudget.ps1 expects assets under src/wwwroot (from eng/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/$gzip byte 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.

Comment thread .github/workflows/igniteui-blazor-lite-release.yml
Comment thread eng/Check-BundleBudget.ps1 Outdated
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:03 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:13 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:14 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:21 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:47 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 17:48 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 19:30 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 19:31 — with GitHub Actions Active
@turbobobbytraykov
turbobobbytraykov deployed to nuget-org-publish August 27, 2026 19:38 — with GitHub Actions Active

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

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.js matches both app.*.bundle.js and the later *.bundle.js catch-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 keep nuget-org-publish exclusive to the final job.
    environment: nuget-org-publish
    permissions:
      contents: read
      id-token: write

Comment on lines +64 to +67
- name: Restore strong-name key
shell: pwsh
env:
STRONG_NAME_KEY_BASE64: ${{ secrets.IG_STRONG_NAME_KEY }}
Comment thread .github/workflows/igniteui-blazor-lite-release.yml
Comment thread .github/workflows/igniteui-blazor-lite-release.yml Outdated
#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.
@turbobobbytraykov
turbobobbytraykov marked this pull request as ready for review August 31, 2026 07:18

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.

🟡 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 list failure and then initializes nuget_status to 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

Comment thread .github/workflows/ci.yml Outdated

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.

🔵 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 the docs/ 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: write token 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

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.

🔵 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 pack still runs MSBuild project/imported package targets inside a job that has id-token: write and the nuget-org-publish environment. --no-build --no-restore does 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-licenses or deny-licenses policy 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.

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.

🟡 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-assets and evidence, 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
Comment thread eng/Check-BundleBudget.ps1 Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.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.

2 participants