ci(publish): build, sign, pack, sbom generation and publish jobs - #30
ci(publish): build, sign, pack, sbom generation and publish jobs#30turbobobbytraykov wants to merge 26 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an opt-in GitHub Actions workflow to generate and (for releases) attach an SPDX 2.2 SBOM for the project’s NuGet package, using a pinned sbom-tool .NET local tool manifest stored under a nested .config directory.
Changes:
- Introduces
.github/workflows/sbom.ymlto generate SBOMs on PR label (generate sbom) and on published releases, and to upload/attach the resulting artifacts. - Adds
.config/sbom-tool/dotnet-tools.jsonto pinmicrosoft.sbom.dotnettoolfor reproducible restores isolated from the repo’s root tool manifest.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| .github/workflows/sbom.yml | New workflow to build/pack and generate SBOM artifacts, then attach SBOM to releases. |
| .config/sbom-tool/dotnet-tools.json | New nested local-tool manifest to pin sbom-tool used by the SBOM workflow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…for improved security (we hope)
…for improved security (we hope)
16b3990 to
a4d1ec5
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
There was a problem hiding this comment.
🟡 Changes recommended
Release-tag interpolation permits PowerShell injection, and scan failures can publish a package without required evidence.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
.github/workflows/publish.yml:427
- The release tag is interpolated into a double-quoted PowerShell command here. A valid tag containing a PowerShell subexpression such as
$()would be evaluated by this attestation-capable job; construct the path from the runtime environment variable instead.
-OutputFile "${{ runner.temp }}/sbom/cyclonedx/${{ env.PACKAGE_ID }}.${{ env.VERSION }}.cdx.json"
.github/workflows/publish.yml:433
- This second direct interpolation of the release tag has the same PowerShell command-injection path as the merge step. Use the runtime environment variable so the tag remains data rather than generated script text.
-BomPath "${{ runner.temp }}/sbom/cyclonedx/${{ env.PACKAGE_ID }}.${{ env.VERSION }}.cdx.json"
- Files reviewed: 19/20 changed files
- Comments generated: 2
- Review effort level: Balanced
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The security-sensitive publishing and hand-written SBOM merge pipeline warrants final human review despite successful end-to-end validation.
Review details
- Files reviewed: 19/20 changed files
- Comments generated: 0 new
- Review effort level: Balanced
… that SPDX SBOM generation uses
There was a problem hiding this comment.
🟡 Changes recommended
Release-tag validation and final digest verification are missing, and SPDX retry selection can retain an incomplete SBOM.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
.github/scripts/New-Sbom.ps1:143
- Selecting the “best” attempt solely by the absolute licensed count can replace a complete SBOM with an incomplete retry. For example, 81/81 packages replaces 80/100 and silently drops 19 components, contradicting the comment that a degraded retry can never replace a better document. Prioritize component count, then license count when totals match.
.github/workflows/publish.yml:42
- The linked alpha.9 run included a
Validate release tagstep invokingAssert-ReleaseVersion.ps1, but the current workflow proceeds directly from checkout to setup and that script is absent. Consequently, the cited end-to-end run does not validate this head, and unsupported release tags now flow into MSBuild properties, package paths, and feed URLs instead of failing at ingress. Re-add the validation step and script, then exercise the current workflow.
- name: Setup .NET
- Files reviewed: 19/20 changed files
- Comments generated: 1
- Review effort level: Balanced
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Secret-backed signing, public publishing, attestations, and a custom SBOM merge warrant final human review despite the successful release validation.
Review details
- Files reviewed: 19/20 changed files
- Comments generated: 0 new
- Review effort level: Balanced
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
blazor-gridlite@0.0.0component in the npm branch —cyclonedx-npm's main-component metadata is read straight frompackage.json's ownname/version(the internal, private build-tooling manifest), and the tool has no flag to override it. Harmless (confirmed present, structurally correct, in the actual released document), but a reader inspecting the merged BOM's structure will see it.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.