* Generate a CycloneDX SBOM per published NuGet package
GitHub's dependency-graph SBOM export reports unresolved version ranges
for most packages and can't see which source projects get merged into
which final package by Numerge. Add a CreateSbom Nuke target that runs
CycloneDX against each project's already-restored assets, reassembles
the Numerge merge groups from numerge.json, and publishes one accurate
SBOM per shipped package as a pipeline artifact.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix SBOM dependency graph merge and cover bundled npm packages
The per-package SBOM merge only kept the dependency-graph (dependsOn)
edges from the first-scanned constituent project, so the flagship
Avalonia package listed 38 components but wired only 3 into its graph.
Union dependsOn edges by ref (each constituent's -sn/-sv override makes
its root collapse onto the shared final-package ref) so all edges are
retained - the Avalonia root now reports 17 direct dependencies.
cyclonedx-dotnet only walks the NuGet graph, so JS that ships inside a
package via a Bun/npm-built webapp was invisible: Avalonia.Browser's
staticwebassets and Avalonia.DesignerSupport's embedded previewer (which
merges into Avalonia). Scan each constituent's webapp/package.json
production dependencies, resolving versions from the installed
node_modules (git-pinned deps become pkg:github purls), so those shipped
components appear in the SBOM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Enrich npm SBOM components to match NuGet component fidelity
Previously the bundled-webapp npm packages were added as bare inventory
entries: no bom-ref, no license, no hashes, direct-only, and orphaned
from the dependency graph - second-class next to the fully-formed NuGet
components cyclonedx-dotnet emits.
Walk the installed node_modules transitively so npm depth matches the
NuGet side (e.g. react-dom now pulls in scheduler, loose-envify,
object-assign; loose-envify pulls in js-tokens), assign each component a
bom-ref, resolve its license from the installed package.json (SPDX id or
expression, with legacy license/licenses object fallback), and wire the
whole tree into the CycloneDX dependency graph - the shipped package now
declares its direct npm deps and every transitive edge below them.
Hashes are intentionally omitted for npm: the verifiable integrity
hashes live in bun's binary lockfile, not the installed tree, and a hash
of the unpacked directory couldn't be checked against a registry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Enrich SBOM root component and verify package contents
Two gaps remained after the npm work. First, cyclonedx-dotnet emits only
type/name/version for the root metadata.component - no manufacturer,
license, or provenance, which a CRA-facing SBOM is expected to carry.
Read the shipped .nuspec and fill in purl, publisher/supplier (also set
as the document-level manufacturer), SPDX license, description,
copyright, and website/vcs external references.
Second, nothing checked what the package actually ships against the
components derived from the dependency graph. Scan the final .nupkg's
binaries: Avalonia's own Numerge-merged modules (e.g. Avalonia.Base,
Avalonia.Controls folded into Avalonia; Avalonia.Win32.Automation into
Avalonia.Win32) are recorded as manufacturer-supplied components with a
SHA-512 of the shipped bytes, and any third-party binary that no
restored dependency accounts for is added and flagged - a regression
guard so bundled binaries can't silently escape the SBOM. Reference
assemblies under ref/ and the package's own primary assembly are skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Exclude type-only @types/* packages from the SBOM
The DesignerSupport previewer's package.json lists @types/react and
@types/react-dom under "dependencies" rather than "devDependencies", so
they were being scanned in. TypeScript declaration packages are stripped
by esbuild and never appear in the shipped bytes, so they fall outside
the SBOM's scope of delivery. Skip @types/* at both the direct and
transitive level (which also drops csstype, only reachable via
@types/react), leaving the SBOM listing exactly the npm packages that
are actually bundled and delivered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add SBOM coverage rules to Copilot review instructions
Instruct code review to flag two cases that would otherwise let a
package ship without accurate SBOM coverage (EU CRA): a newly published
NuGet package not covered by CreateSbom, and a change to what
dependencies are delivered with a component - notably adding npm/JS to a
previously .NET-only component, bundling third-party binaries directly,
or new Numerge merge groups - without a corresponding update to
nukebuild/SbomGenerator.cs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Link package-content binaries into the dependency graph
The binary:* components from the .nupkg content scan were added to
components but had no dependency node or root edge, leaving them
unreachable for SBOM consumers that traverse the graph from the root -
so the shipped merged modules (and any flagged third-party binary)
didn't appear in the delivered dependency tree. Give each its own leaf
node and a dependsOn edge from the root component.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Prune dangling dependency edges from generated SBOMs
cyclonedx-dotnet leaves dependsOn edges pointing at packages it excluded
as dev dependencies (e.g. analyzers stripped by -ed), so after exclusion
the graph references components that aren't in the SBOM. Drop any
dependsOn target that doesn't resolve to a present component, keeping the
dependency graph internally consistent - consistent with treating those
build-only dev dependencies as out of scope of delivery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Reference upstream issue for dangling-edge prune
Cite CycloneDX/cyclonedx-dotnet#761 in the PruneDanglingDependencyEdges
comment so a future reader knows the dangling dependsOn edges are an
upstream bug (still reproducing in 6.2.0), not something introduced here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Resolve CycloneDX via NuGetPackage instead of a global tool install
`dotnet tool update --global CycloneDX` mutated the developer's (and CI
agent's) global tool set as a side effect of running the build. Follow the
same pattern as the other build tools (ApiCompat, ApiDiff, ilrepack): pin
the package via PackageDownload in _build.csproj and resolve it through a
[NuGetPackage] Tool, passed into SbomGenerator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Reuse existing nuspec/numerge parsing helpers in the SBOM generator
Drop the hand-rolled Numerge* POCOs in favour of Numerge.MergeConfiguration.
LoadFile (already used by CreateNugetPackages), and have BuildToNuGetCache
call SbomGenerator.ReadPackageId instead of duplicating the nuspec-id
extraction inline, so the two can't drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Resolve each package's nupkg from a prebuilt id->path map
GenerateForPackage's fallback re-globbed and re-parsed every nupkg in the
output directory once per final package whenever the versioned-filename glob
missed - O(n^2) archive reads. Build the id->path map once in Generate
(reading each nupkg's id a single time) and look each package up in it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Degrade unrecognised git/URL npm specifiers instead of throwing
ResolveNpmComponent routed any git/URL dependency range to a GitHub-only
parser that threw NotSupportedException on anything else (GitLab/Bitbucket,
a raw tarball URL, ...). Because CreateSbom is a hard release dependency,
one such spec would fail the whole build. Fall back to a generic component
(preferring the installed on-disk version) with a warning instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Guard against an npm package.json with no version field
A package.json without a "version" (valid for private packages) made the
null-forgiving ["version"]! throw an NRE, aborting the build. Fall back to
the declared range with a warning instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Don't emit a file-license path as an SPDX id
A nuspec <license type="file"> (from PackageLicenseFile) stores a file path,
not an SPDX id. It was being fed to SpdxToLicenses and emitted as
{license:{id:"licenses/LICENSE"}}, an invalid SPDX id that can fail license
validation. Map type="file" to a license name instead. (Avalonia's own
packages all use PackageLicenseExpression=MIT, so this is a latent-correctness
fix rather than a live bug.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Generate and publish SBOMs from the macOS CI job too
CreateSbom was only wired into CiAzureWindows, so the macOS job's NuGetOSX
artifact - which carries the real macOS-native binaries the Windows build
can't produce - shipped without matching SBOMs. Wire CreateSbom into
CiAzureOSX and publish its output as a SBOMOSX artifact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Embed each package's CycloneDX SBOM inside its .nupkg
The SBOM previously existed only as a detached build artifact. Also embed
it at _manifest/cyclonedx/bom.cdx.json inside the shipped .nupkg so it
travels with the package, mirroring the _manifest/ layout Microsoft.Sbom.
Targets uses for SPDX. A .nupkg is an OPC package, so [Content_Types].xml
gains a json default or strict OPC readers (including NuGet signature
verification) would reject the modified package. Skips already-signed
packages to avoid invalidating their signature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address Copilot review: SBOM zip ordering, nuspec read, component key
- Order ZipFiles after CreateSbom so zipped NuGet artifacts include the
embedded SBOM.
- Read the .nuspec entry with an ordinal EndsWith and dispose its stream.
- Key components by purl/bom-ref, falling back to name+version so distinct
versions sharing a name aren't de-duplicated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Model bundled assemblies as a composition, not dependencies
The numerge-folded first-party assemblies (Avalonia.Base, Avalonia.Controls,
etc.) are constituents of the package, not dependencies of it. Represent them
as a CycloneDX "assembly" via a top-level compositions entry with
aggregate=complete instead of drawing dependsOn edges from the root - contains
and depends-on are distinct relationships. They stay flat top-level components
so their per-assembly hashes remain visible to scanners that ignore nested
components, and the dependency graph is left holding only genuine dependencies.
Also carry the package's licence onto these first-party components, which
previously came out blank (cyclonedx-dotnet only sets it on the root).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Bump MicroCOM to 0.11.4, add workaround for WinRT bug
* menu.h - use ComPtr for COM stored references
* Replaced raw COM pointers with smart ones
* fixes
* Use weak com ref from AvnMenuItem
* Better smart pointer handling
* Avalonia native readme update
* review
* missing dif
* Just call the ctor like a normal person
* Add update-api command
* Api diff command
* Missed flag
* Restrict commands running on fork PRs
* Add concurrency
* Filter github.event.comment.author_association even before workflow started
* Use steps.pr.outputs.sha
* Only push api/ changes