* build source PURL from metadata and gate it to CycloneDX 1.6+
- the source PURL is built from package metadata instead of editing the binary PURL: only `distro` (plus `epoch` and `rpmmod` for rpm) is carried over, subpath is dropped, and the ref is skipped when the PURL type does not match the metadata ecosystem
- one emission rule for all ecosystems: only when the source name or version differs from the binary, so self-sourced rpm and alpm packages no longer get a redundant ref
- source RPM names must end in `.src.rpm` or `.nosrc.rpm`, and `nosrc` maps to `arch=nosrc`
- `source-distribution` refs are dropped when encoding below spec 1.6, since cyclonedx-go does not downgrade them (CycloneDX/cyclonedx-go#291)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* limit source-distribution refs to deb and rpm
- apk origins and alpm pkgbases are build recipes, not published source packages, and their names are usually also real binary packages, so a `source-distribution` ref there would point at a binary package. They keep relying on the `upstream` PURL qualifier.
- document why this is an external reference rather than `pedigree.ancestors`, with links to the CycloneDX spec discussion (CycloneDX/specification#612)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
---------
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* fix: stop unbounded recursion decoding self-referencing CycloneDX metadata
SafeTensorsModelInfo and GGUFFileHeader both carry a Parts field that is a
slice of their own type. When a CycloneDX component with that metadata is
decoded from properties, the slice decoder tries element 0, which decodes a
struct whose Parts slice tries element 0 one level deeper, and so on. The
recursion never finds a base case, building longer property-name prefixes
on every level, so memory grows until the process is killed (seen in
grype on any machine-learning-model component written by syft).
Return early from struct decoding when no property is named by the prefix
or nested below it: none of the fields could be decoded anyway, and the
recursion is now bounded by the depth of the properties that exist.
Refs anchore/grype#3767
Signed-off-by: Vamshidher Reddy Jannapu Reddy <v.jannapu@gmail.com>
* perf: look up nested CycloneDX property names in a sorted index
The struct check added in the previous commit scanned every property of a
component for every struct it decoded. Decoding a slice of structs, such as
a package's locations, does that once per element, so a component with n
properties took O(n^2): 20,000 location properties took 5.3s to decode
instead of 18ms, a cost that an untrusted SBOM can choose.
Sort the property names once per Decode/DecodeInto call and binary-search
them instead. The result of each check is unchanged; decoding is back to
O(n log n) (27ms for the same 20,000 properties).
Refs anchore/grype#3767
Signed-off-by: Vamshidher Reddy Jannapu Reddy <v.jannapu@gmail.com>
---------
Signed-off-by: Vamshidher Reddy Jannapu Reddy <v.jannapu@gmail.com>
Symlinks resolve to their target, so the same coordinates could show up
once per path that reaches them and get hashed again each time. The
results map hid this but the work was repeated. Drop repeated
coordinates before digesting.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Motivation: the envoy binary classifier returns no package for envoy 1.37.1,
1.40.0 and 1.40.1 (the official envoyproxy/envoy images), while neighbouring
versions such as 1.37.0 and 1.38.0 are detected.
Approach:
- the main matcher (NUL-delimited version followed by
envoy_reloadable_features) only accepted 1.3x; widen it to 1.[34]x, which
covers 1.40.0 and 1.40.1.
- in the 1.37.1 binary the version string sits between different neighbouring
strings, so the existing matchers do not fire. Add a matcher anchored on the
preceding "DeltaDiscoveryResponse.removed_resources" string.
- add snippets, config.yaml entries and test cases for 1.37.1, 1.40.0, 1.40.1
(linux/amd64 images, pinned by digest).
Validation:
- cd syft/pkg/cataloger/binary && go test . -run
'Test_Cataloger_PositiveCases/envoy' : the three new fixtures fail without
the classifiers.go change and all envoy cases pass with it.
- golangci-lint run ./syft/pkg/cataloger/binary/... : 0 issues.
- Built syft before/after and scanned the full linux/arm64 envoy binaries from
the official images: 1.37.1, 1.40.0 and 1.40.1 were not reported before and
are reported with the right version after; 1.37.0 and 1.38.0 are unchanged.
- Not run: the rest of the package test suite needs docker, which is not
available here (Test_PEPackageCataloger etc.), and snippets for other
binaries are not downloaded locally.
The contrib-build fallback matchers still only accept 1.3x; they are untouched.
Report: https://github.com/anchore/syft/issues/5470
Assisted-by: claude-sonnet-5-5 (via Claude Code)
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
The istio pilot-agent/pilot-discovery classifiers missed every
-snapshot.N build and the older -alpha.N/-beta.N/-rc.N layouts.
Two defects:
1. The doubled-version matcher anchored on a bare x.y.z followed by
NULs, but pre-release builds repeat the full version including the
suffix (1.15.0-beta.0 NUL NUL NUL 1.15.0-beta.0), so the anchor never
matched. Only the capture group had been widened previously.
2. -snapshot.N was absent from every alternation, and snapshot builds
store the version only once with no leading copy, so no existing
matcher could reach it.
Widen the leading anchor to accept the same optional pre-release suffix
it already accepts in the capture group, add -snapshot.N to each
alternation, and add a matcher for the standalone snapshot layout. The
new matcher is second so it can never shadow a normal build.
Adds real captured snippets for pilot-agent and pilot-discovery at
1.0.0-snapshot.0, 1.5.0-alpha.0, 1.15.0-beta.0 and 1.19.0-rc.0.
Fixes#5060
Signed-off-by: phatlc <phatle.hsd@gmail.com>
In the loop over `rootPkgs`, the error appended to `unknownErr` was incorrectly
using the outer `err` (from `packages.Load`) instead of the individual package
error `e`. This could cause:
- nil or stale error values to be recorded when package-specific errors existed.
- Loss of detailed per-package error information in the final error report.
Changed `unknown.Append(..., err)` to `unknown.Append(..., e)` to properly
capture each package's load error.
Signed-off-by: idhyt <idhyt3r@gmail.com>
* chore: let go-make own the go version
Drops syft's own Go version pin so CI and releases use go-make setup's default. The pin predates the go-make port (#4773, carried through #4923) and had to be kept in sync in four places.
- removes the `go-version` input from `.github/actions/bootstrap` and the three `go-version: "1.26.2"` overrides in `validations.yaml`
- builds move from Go 1.26 to go-make's default (currently `1.27.2`). `go.mod` stays at `go 1.26.8`, so nothing changes for library consumers
- this is groundwork for the emSign/eMudhra CA fix: Go `1.27.3` distrusts the compromised intermediates in `crypto/x509`. anchore/go-make#170 bumps the default, and the go-make pin here gets bumped once that is released. The `v1.55.0` images also still carry `emSign ECC Root CA - G3`, so a release is needed after the bump
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* chore: reformat generated license list with go 1.27 gofmt
go 1.27 gofmt aligns long map keys differently, so the json schema drift check rewrote this file.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
---------
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* test: catalog GGUF models larger than the header
gguf-parser-go v0.26.3+ rejects a header whose tensors declare more data than follows it. Syft parses only a 50 MB copy of each model, so any larger model is dropped.
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
* fix: fix parser with rollback and verify with test
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
* fix: bound GGUF tensor info before the parser reads it
The header pre-scan stopped after the metadata KVs and left tensor infos to gguf-parser-go. Older releases of the library allocate whatever length a tensor name declares, so a tiny file could force a multi-GB allocation, or a fatal out of memory that panic recovery can't catch.
- tensor names are bounded like metadata keys (non-empty, length limit, shared string budget)
- dimension count is capped at `GGML_MAX_DIMS`
- a tensor count larger than the header now fails at EOF in the pre-scan
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* fix: give the GGUF header copy the model's real size
The cataloger copies at most 50 MB of a model and parses that copy. Newer gguf-parser-go releases check the tensor data the header declares against the file size, so the truncated copy of any larger model was rejected.
- after the header is validated, the copy is extended to the size the resolver reports for the model, leaving the tail as a hole the parser never reads
- sources that report no size (OCI model artifacts today) keep the old behavior
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* chore(deps): restore gguf-parser-go v0.26.4
The header copy now carries the model's real size, so the pin to v0.26.0 and its dependabot ignore are no longer needed. This also brings back upstream's string and array length bounds, and the library's own `IsNumeric`.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* fix: keep the extended GGUF header copy sparse on Windows
NTFS reserves disk for the full length of a file extended with truncate unless the file is marked sparse, so cataloging a large model on Windows reserved the model's size in the temp dir until cleanup.
- the header copy is marked sparse (`FSCTL_SET_SPARSE`) before it is extended
- if the filesystem can't make it sparse (FAT32, exFAT), the copy is not extended, and models larger than it fail to parse as before
- `golang.org/x/sys` moves from an indirect to a direct dependency
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
The go-make module and its action need to move in lockstep, but dependabot bumps them in separate PRs that drift apart. Automation now bumps both together, so dependabot ignores go-make in both ecosystems.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>