mirror of
https://github.com/anchore/syft.git
synced 2026-10-11 21:57:22 +02:00
6ac7afb439c950ec2fc169103c4850c6dfe74c5a
3589
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6ac7afb439 |
Fix missing file hashes when running on windows system (#5341)
* test: add windows unit test job and capture dir resolver path bug - add a `Unit tests (Windows)` job running a new `make unit:windows` task, scoped to the packages that own host path handling (tests needing linux docker fixtures are skipped) - `Unit tests` is now a gate job over the Linux and Windows unit jobs - add a resolver test asserting that locations from `AllLocations` resolve again via `FilesByPath` and use posix paths. This is expected to fail on windows today, which is why file digests come back empty for `dir:` scans (#5325) - fix windows-only test files that no longer compiled Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * chore: drop verbose output from integration tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: windows path handling outside of the file lookup bug - chroot paths are always posix (`ToChrootPath` no longer returns backslash paths on windows), same for the directory source name derived from `base` - `windows.FromPosix` no longer panics on input that isn't volume-encoded (e.g. relative or already native paths) - skip tests on windows that rely on symlink fixtures, shell script fixtures, or emulate windows paths on a posix host - disable `core.autocrlf` on the windows CI job so fixture digests match linux This leaves only the `FilesByPath` lookup failures from #5325. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: skip posix-only fileresolver tests on windows Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: split contained paths on posix separators on windows - `allContainedPaths` cleaned with the native separator but split on `/`, so a root reached through a symlink never had its ancestors indexed on windows - index assertions in tests now key on the posix path the index actually stores - skip symlink fixture tests and a posix-only test helper on windows Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * chore: revert unrelated go.sum churn and allContainedPaths change - restore `go.sum` and `.make/go.sum` (accidentally populated by a local `-mod=mod` invocation) - revert the `allContainedPaths` separator change: production passes native windows paths there, and splitting them on `/` produces drive-relative ancestors, so skip the posix-only test on windows instead - annotate the remaining `filepath` usages that operate on native paths Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: search the file tree with posix paths on windows (#5325) The directory and file resolvers key their file tree on posix paths (`/c/some/path`), but `FilesByPath`, `HasPath` and root-anchored `FilesByGlob` searched it with the native path from the chroot context (`C:\some\path`). On windows every lookup silently missed, so anything that resolves files by path found nothing. This is why file digests were empty for `dir:` scans. Requests are now converted to posix before searching the tree. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
b23e461731 |
Stop reading the mount table for every single-file source (#5257)
Signed-off-by: Darragh McGurk <120114728+dmcg310@users.noreply.github.com> |
||
|
|
8c63a2403c |
fix: conan expat CPE (#5259)
Add libexpat_project vendor and libexpat product candidates for Conan packages named expat, so generated CPEs match the NVD entries used for libexpat vulnerabilities. Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com> Co-authored-by: Will Murphy <willmurphyscode@users.noreply.github.com> |
||
|
|
e4ff7f5973 |
fix(spdx): write a name for the root package when the source has none (#5349)
* fix(spdx): write a name for the root package when the source has none
A source read from another SBOM (a purl list, a CycloneDX document
without metadata.component, an SPDX document without a DESCRIBES
relationship) can have no name. The SPDX encoder then wrote the document
root package with an empty PackageName, which SPDX requires. In
tag-value output the PackageName line is omitted, so the root package's
fields land in the previous section and syft cannot read its own output
back ("received unknown tag SPDXID in CreationInfo section").
Fall back to the document name for the root package, and make the
document name fall back to "unknown" for every source type rather than
only for unrecognized ones.
Signed-off-by: Tony <tjagodka@gmail.com>
* fix(spdx): drop syft's root package on decode for every SPDX version
- the root package name now comes from one fallback chain (source name, image repo name, source ID, document name), so the root purl no longer gets an empty name for a source decoded from CycloneDX
- decoding strips the root syft writes for an unknown source, so `syft convert` round trips stop adding an `unknown` package each pass
- for SPDX 2.1 and 2.2, which have no `primaryPackagePurpose`, the purpose is taken from the source type in syft's `DocumentRoot-<type>-` ID
**behavior change:** decoding a syft-written SPDX 2.1/2.2 document no longer returns the root as a package or its `CONTAINS` relationships, matching 2.3.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
---------
Signed-off-by: Tony <tjagodka@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
|
||
|
|
37d45de9ff |
fix(python): link uv.lock dependents to the locked version they name (#5351)
* fix(python): link uv.lock dependents to the locked version they name A forked uv.lock can lock several versions of one package, and uv records the version on each dependency entry in that case. The parser dropped that field, so every pandas version depended on every numpy version. Carry the locked version through provides and requires as name@version (bare name stays for unique locks) and keep marker text unchanged. Fixes #5340 Signed-off-by: Dev M <devtechedge@gmail.com> * fix(python): pair uv.lock versions without widening public metadata - the locked version on a uv dependency entry is only needed to pair relationships, and the relationships already carry it, so it stays on the internal toml struct instead of `pkg.PythonUvLockDependencyEntry`. The previous approach hid it from JSON but still hashed it into package IDs. - dependency specs are now built from the raw lock entries at parse time rather than read back off package metadata - optional-dependencies get the same version pairing, so an extra that names one locked version no longer links to all of them Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Dev M <devtechedge@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
26927eab02 |
fix: suppress misleading upstream CPE for PHP extension binaries (#5102)
Motivation: syft's PHP interpreter cataloger reports each bundled PHP C extension (openssl, ldap, sqlite3, etc.) as its own package, using the PHP interpreter's own version number as the package version (since these extensions are compiled into a specific PHP release and have no independent version of their own). The classifier already attaches an explicit CPE like cpe:2.3:a:php-openssl:php-openssl:8.5.7:... to make clear this refers to the bundled PHP extension, not upstream OpenSSL. However, that CPE was marked with Source: cpe.GeneratedSource. Syft's package-finalization pipeline (internal/task/package_task_factory.go) only skips its generic, name-based CPE generation when a package already has an "authoritative" CPE (any Source other than GeneratedSource) - so it also generated and appended a second CPE, cpe:2.3:a:openssl:openssl:8.5.7:..., derived from the bare package name. That second CPE is indistinguishable from a real standalone OpenSSL/LDAP/SQLite installation and can cause vulnerability scanners that consume this SBOM to falsely flag PHP hosts as running vulnerable upstream libraries at version "8.5.7" (a PHP version, not a real OpenSSL/LDAP/SQLite release). See anchore/syft#5014 for a full repro. Approach: Mark the classifier's explicit CPE with Source: cpe.DeclaredSource instead of cpe.GeneratedSource, so it is treated as authoritative and the extra name-derived CPE is no longer generated. This mirrors the existing pattern in syft/pkg/cataloger/java/parse_jvm_release.go's newJvmCpe, which uses the same DeclaredSource + comment convention to mark a syft-synthesized (not literally file-declared) CPE as trustworthy and suppress further generation. It also brings this classifier in line with its sibling php-cli/php-fpm/php-apache classifiers in the same file, which already mark their CPEs as authoritative (via NVDDictionaryLookupSource, not applicable here since php-<ext> is not a real NVD dictionary entry). This change only suppresses the extra generated CPE; it does not change the package name, type, version, or PURL, and the existing php-<ext>:php-<ext> CPE is unaffected. Validation: go build ./... go test ./syft/pkg/cataloger/php/... ./internal/task/... ./syft/pkg/cataloger/internal/cpegenerate/... All pass except the two pre-existing docker-image-fixture subtests of Test_InterpreterCataloger, which fail in this sandbox only because the `docker` binary isn't available (exec: "docker": executable file not found in $PATH); confirmed via `git stash` that these two subtests fail identically on main without this change, i.e. this is a sandbox limitation and not a regression. Added a new unit test, Test_getClassifier_declaredCPESource, that directly asserts the classifier's CPE now carries Source: cpe.DeclaredSource. Fixes #5014 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> |
||
|
|
1dc16fbac4 |
fix: report scanner errors and raise the line cap in metadata parsers (#5353)
* fix: report scanner errors instead of returning a partial parse as complete A line longer than `bufio.Scanner`'s 64KB buffer ends the scan with `ErrTooLong`, which only `Err()` reports. Parsers now return what they found along with the error (recorded as an unknown for the file), or log it where there is no error to return. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: allow lines up to 1MB in line-oriented package metadata parsers Adds `internal.NewLineScanner`, a `bufio.Scanner` capped at 1MB per line rather than the 64KB default, the same cap the alpm parser already uses. Long single-line fields (descriptions, license text, dependency lists) now parse instead of ending the scan early. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: use io.ReadFull where a short read was silently accepted - dotnet bundle 7-bit int decoding could read a zero-byte result as a zero byte - wordpress plugin header parsing included the unread tail of its buffer Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: finish moving line scanners to the 1MB cap and checking scanner errors - java manifest, pom.properties, Gemfile.lock, requirements.txt, homebrew formula, alpm and the purl/cpe decoders now use `internal.NewLineScanner` - snap kernel changelog reports a scan error instead of silently dropping the base kernel package - the attestation output reader keeps draining the pipe after a scan error so cosign can't block on a full pipe Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: stop logging the full setup.py line once per dependency match The debug log ran once per regex match and included the whole line, so log volume grew with the square of line length. With 1MB lines that is hundreds of GB of debug output from a single crafted file. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(gentoo): don't dereference a nil location when a LICENSE scan fails `extractLicenses` accepts a nil location, but the scan error log added earlier on this branch dereferenced it. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(gentoo): bound license group expansion on hostile license_groups - dedup within a group uses a set instead of `slices.Contains`, which was quadratic in tokens per line and got much worse under the 1MB line cap - each group is expanded once and memoized, so a chain of groups that reference each other repeatedly no longer expands exponentially - repeated references to the same group within one line are skipped Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
d0e1e7be05 |
Harden cataloger parsers against truncated input (do not panic) (#5355)
* fix(javascript): don't panic on a yarn v1 lockfile line missing its value A v1 `yarn.lock` dependency line without a version constraint, or a `version`/`resolved`/`integrity` line without a value, indexed past the end of the split line. Such dependencies are now skipped and empty fields left empty. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(alpine): don't panic on an apk db ACL or checksum line before its entry An `M:`, `a:` or `Z:` line that comes before any directory or file entry in `lib/apk/db/installed` indexed `files[-1]`. Such a line is now skipped. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(erlang): don't panic on a truncated rebar.lock or .app file The term parser read past the end of the input when a file ended mid-term: whitespace only, an unclosed list, a lone `<`, or a comment ending in `\r`. These now return an "unexpected end of input" error. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(lua): don't panic on a truncated .rockspec The rockspec parser read past the end of the input when a file ended mid-statement: a lone `[`, an unclosed `[[...]]` value or `[...]` index, or a `local` assignment cut off before its `=`, value, or line end. These now return an "unexpected end of input" error. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(swift): don't panic on a Podfile.lock pod without a version Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(gentoo): don't panic on a short obj line in portage CONTENTS Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(spdx): don't panic on an opam checksum without an algorithm The opam cataloger stores checksums verbatim, so a malformed one reached the SPDX encoder and panicked there, outside the cataloger's recover. Checksums without an `=` are now skipped. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor: use strings.Cut where a split was only indexed after a Contains check Also guards `windows.FromPosix` against a path with no separator. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: guard untyped package metadata fields - new `any`-typed metadata fields must be allowlisted with a reason - allowlisted untyped fields are pushed through `SetID` and every encoder with non-finite, deeply nested and large values Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: fuzz every generic cataloger parser One fuzz target over every glob-selected parser in the capabilities data, run through its cataloger task. Each input must not panic, must finish in time and must keep allocations proportional to its size. Seeds come from each parser's testdata plus the string literals in its package. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: scan malformed inputs under memory and time limits A directory of small malformed files (deep nesting, oversized declared counts, truncation, type mismatches, non-finite floats) is scanned by the CLI with a memory limit and an RSS ceiling. The scan must exit 0 and write an SBOM. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: stop a fuzz input once its live heap passes the allocation limit The allocation check only ran after the parser returned, so an input on its way to exhausting memory ran the machine into swap first. The harness now polls the live heap while the parser runs and panics past the same limit, which the fuzz engine saves as a crasher. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: bound nesting depth and error context in the erlang and lua parsers - a deeply nested rebar.lock or .rockspec overflowed the goroutine stack, which is fatal rather than a recoverable panic, so one file could take down the whole scan. Both parsers now stop at 1000 levels with an error. - parse errors echoed whole input lines into the SBOM as unknowns, and lua appended another copy at every nesting level. `PrintError` now clips to a window around the error column, and lua decorates once at the top. - the Podfile.lock error no longer echoes the pod entry. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: close ways the malformed-input checks could pass quietly - a ctx-aware parser hitting the fuzz timeout now fails the input instead of racing the hard deadline - the heap watchdog measures growth from a post-GC baseline rather than an absolute process-wide number - a saved fuzz input naming a target that no longer exists fails instead of being skipped - the CLI malformed-input scan fails on a recovered parser panic, not only on a non-zero exit Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
57bd6a5f9c |
fix(cpe): bound CPE candidate generation for pathological package metadata (#5356)
* fix(cpe): cap sub-selection prefixes to bound candidate cross-product a name with thousands of `-`/`_` separators produced one vendor and product candidate per segment, and the vendor x product cross-product made CPE generation roughly cubic in time and memory (8KB name: ~250s, 4.8GB RSS). `generateSubSelections` now stops after 10 prefixes. The full name is still a candidate, so real package names are unaffected. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(cpe): bound candidate count and field length before the cross-product the prefix cap only covered one way to inflate the vendor x product cross-product in `FromPackageAttributes`. Java groupIDs add a vendor (and, with no artifactID, a product) per dot segment, and .NET executables add one of each per file, so a 1000-segment `Bundle-SymbolicName` still produced ~1M CPEs and several GB. - vendor and product candidates are each capped at 64 (shortest first, so truncation is deterministic) - names, versions, and candidates over 512 bytes are skipped, since every byte is paid once per CPE in key building and regex validation real packages produce around a dozen candidates with names well under 100 bytes, so output for real inputs is unchanged. Also corrects the earlier comment: prefixes only become vendor candidates and the cost was quadratic, not cubic. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
c60aabad37 |
test(golang): make the UPX fixture exercise loader padding (#5354)
UPX pads to a 4 byte boundary before it writes the loader stub. The image-small-upx build happened to need no padding, so the cataloger tests over it passed with or without the fix in #5347. - build the fixture with -X main.Version=1.0.11, which needs 2 bytes of padding, so the existing cataloger tests fail without the fix - add TestImageSmallUPXNeedsLoaderPadding, which fails if a change to the fixture's inputs means it no longer needs padding - run the crafted loader test on both sides of the boundary, so a skip that always rounds up is caught too Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> |
||
|
|
c0072873c9 |
fix(golang): account for UPX's loader padding when unpacking Go binaries (#5347)
UPX pads its output to a 4 byte boundary before it writes the loader stub, and l_lsize counts from that boundary. The block chain resumed at the end of the last PT_LOAD extent plus l_lsize, so whenever the compressed extents ended off a 4 byte boundary it landed 1 to 3 bytes early, read a misaligned b_info, and stopped. The padding between the segments was never filled in, the reconstruction ended at that first hole, and .go.buildinfo in the data segment went with it, so the binary contributed no packages. The loader skip now rounds up to 4 before adding l_lsize, which matches the funpad4 in UPX's own unpacker. The crafted fixture for the tail extents put its stub straight after the head extent, the layout the bug assumed, so it now pads the way UPX does. Signed-off-by: huuyafwww <huuya.yamauchi@3-shake.com> |
||
|
|
236ab25393 |
test(snap): bound the kernel changelog bomb fixture by ratio, not a fixed 1MB (#5352)
Same issue as #5350: go1.27's compress/flate encodes this 512MB fixture to just over 1MB, so the fixture sanity check tripped before the bound was exercised. The line bound itself is fine. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
fa6ea7b10c |
test(debian): bound the bomb fixture by ratio, not a fixed 1MB (#5350)
go1.27's compress/flate encodes 512MB of zeros to ~1MB (1052690 bytes) where go1.26 gives ~510KB, so the fixture sanity check tripped on newer toolchains before the bound was ever exercised. The decompression bound itself is fine (bounded read allocates ~300KB on both). Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
ae7843e17c |
fix(ai): handle malformed GGUF headers without panicking (#5345)
- pre-scan the GGUF metadata KV section and reject values gguf-parser-go would panic on, recurse on without bound, or overallocate for: wrong typed well-known keys, zero alignment, nested or oversized fully decoded arrays, an unreasonable `split.tensors.count`, and empty strings the parser would misread. Keys are matched the way the parser sees them. The KV count, fully read string bytes, and skipped string counts are bounded too. The file becomes an unknown instead of taking down the scan. - `copyHeader` now bounds its own read to the max header size. - non-finite floats in `header` (including inside arrays) become `"NaN"`, `"+Inf"` or `"-Inf"` so JSON encoding can't fail. `header` and `metadataHash` are otherwise unchanged. - a `metadataHash` failure is now an error instead of an empty hash. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
a4d3aedf4e |
fix(ai): GGUF layer handling and safetensors parameter counts (#5343)
* fix(ai): keep merging GGUF layers when one fails to parse a single unparseable layer in an OCI GGUF artifact skipped the merge, so the named model lost its parts and nameless layers were dropped. The error is still returned. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(ai): only send GGUF layers to the GGUF cataloger the media type glob `application/vnd.docker.ai*` also matched config, model.file, license and safetensors layers, so every safetensors OCI model reported spurious "invalid GGUF magic number" unknowns. Match `application/vnd.docker.ai.gguf.v3` exactly. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(ai): don't wrap safetensors parameter counts - tensors whose shape product overflows uint64 are skipped instead of wrapping - per-file and per-shard parameter totals saturate instead of wrapping - parameter count and dominant dtype are computed in one pass - GGUF licenses are created with the scan context, and drop no-op unknown checks Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor(ai): derive safetensors model info from the header in one place the file and OCI layer parsers built identical metadata from a parsed header. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(ai): keep the GGUF media type with the GGUF cataloger also corrects stale comments in the GGUF merge processor. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
50dbb33e66 |
fix(ai): bound model companion file reads and validate their contents (#5344)
Companion files next to AI models (`config.json`, `README.md`, and the OCI model.file, license and model config layers) are now read with named size limits, and are only used when they look like what they claim to be. - JSON companions are read whole up to a limit and skipped when larger, rather than being fully buffered or parsed truncated. OCI companion layers are fetched one byte past the cap so oversize is detectable. - README and license layers read a prefix. The frontmatter block is capped at 256 KB and list values at 32. - dir and OCI scans share one 8 MB safetensors header cap. Directory scans used to allow 100 MB. - `config.json` is only used when it has `architectures` or `_name_or_path`, and is looked up beside the model or in its parent directory, no longer all the way up to the scan root - OCI model.file layers are processed in a stable order, so the chosen name no longer changes between runs - README frontmatter: - a list-form `license:` is accepted and no longer drops `base_model` - `null` values are ignored - delimiters must be exact `---` lines - model names from `_name_or_path` / `base_model`: - Windows paths are handled - `.`, `..` and `/` fall back to the next name source - names are capped at 256 characters Behavior changes worth noting: - a `config.json` two or more directories above the model no longer names it - JSON `config.json` files without `architectures` or `_name_or_path` (e.g. `generation_config.json`, app configs) are ignored - safetensors headers over 8 MB in directory scans are now rejected, matching OCI scans (real shard headers are well under 1 MB) Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
10af8342c0 |
fix: don't fail the whole SBOM when a cataloger panics on one file (#5342)
* fix(generic): recover from parser and processor panics - a panic while parsing one file becomes an unknown for that location instead of failing the whole SBOM - a panic in a package processor is logged as a warning and the unprocessed results are kept - stacks go to the trace log, not into the unknown text Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(unknown): keep errors that unwrap to nil `sync.PanicError` with a non-error panic value (e.g. `panic("boom")`) unwraps to nil, and was being dropped entirely from the error graph instead of being kept as a leaf. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(task): don't put the stack trace in task panic errors Task panics are still fatal, but the error now reads `panic in task "<name>": <value>` and the stack goes to the debug log. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test(generic): cover chained processors when a later one panics Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(file): recover from panics in the executable and file digest catalogers A panic while reading one file is now recorded as an unknown for that file instead of failing the whole SBOM, the same as generic catalogers. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
f7f3c6efc3 |
fix(ai): use go.yaml.in/yaml/v3 to parse model card frontmatter (#5328)
* fix(ai): use go.yaml.in/yaml/v3 to parse model card frontmatter gopkg.in/yaml.v3 panics on some malformed merge-key payloads (for example "<<:\r? 0:"), which crashed the gguf and safetensors catalogers when a model README contained such frontmatter. Switch the Hugging Face frontmatter parser to the maintained go.yaml.in/yaml/v3 fork, which is already used by the other catalogers and returns an error instead of panicking. The existing error path logs and skips the frontmatter. Fixes #5327 Signed-off-by: tk1475 <118763009+tk1475@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: finish moving off gopkg.in/yaml.v3 - move the snap catalogers and `internal/capabilities` to `go.yaml.in/yaml/v3`. The snap parsers decode yaml from scanned sources and hit the same merge-key panic as #5327 - enable `depguard` with a deny rule for `gopkg.in/yaml.v3` so it can't be reintroduced Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: tk1475 <118763009+tk1475@users.noreply.github.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
a8d4fb845f |
fix(spdx): don't panic on a root file without a supplier or null entries (#5326)
The package supplier is optional in SPDX, but fileSource dereferenced it without the nil check containerSource already has, so a valid SPDX 2.3 document whose described package has primaryPackagePurpose FILE and no supplier crashed syft convert and grype sbom: input. A JSON null in files, a package's files or externalRefs (or an SPDX 2.1 package the version converter cannot map) decodes to a nil pointer that the conversion then dereferences. Drop nil entries once at the start of ToSyftModel instead of guarding each loop. Signed-off-by: Tony <tjagodka@gmail.com> |
||
|
|
ff45b9ea63 |
fix: release package collection lock in Ubuntu ESM detection (#5336)
Signed-off-by: Sverrir Sigurðsson <svesig@gmail.com> |
||
|
|
fb55856e33 |
chore(deps): update CPE dictionary index (#5312)
Signed-off-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> Co-authored-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> |
||
|
|
a76b5fa36b |
chore(deps): update tool versions (#5337)
Signed-off-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> Co-authored-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> |
||
|
|
643bf2c24d |
chore(deps): bump github.com/containerd/containerd/v2 (#5335)
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.3.5 to 2.3.6. - [Release notes](https://github.com/containerd/containerd/releases) - [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md) - [Commits](https://github.com/containerd/containerd/compare/v2.3.5...v2.3.6) --- updated-dependencies: - dependency-name: github.com/containerd/containerd/v2 dependency-version: 2.3.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
c5baac0e94 |
chore(deps): bump modernc.org/sqlite from 1.58.0 to 1.59.0 (#5330)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.58.0 to 1.59.0. - [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md) - [Commits](https://gitlab.com/cznic/sqlite/compare/v1.58.0...v1.59.0) --- updated-dependencies: - dependency-name: modernc.org/sqlite dependency-version: 1.59.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
19341fd0a2 |
chore(deps): bump github.com/olekukonko/tablewriter from 1.1.4 to 1.1.5 (#5331)
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.1.4 to 1.1.5. - [Release notes](https://github.com/olekukonko/tablewriter/releases) - [Commits](https://github.com/olekukonko/tablewriter/compare/v1.1.4...v1.1.5) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.1.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
ec8f035584 |
chore(deps): bump docker/setup-buildx-action from 4.3.0 to 4.4.1 (#5329)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.3.0 to 4.4.1. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: 4.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9d39701d50 |
chore(deps): bump github.com/magiconair/properties (#5333)
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.18.11 to 1.18.12. - [Release notes](https://github.com/magiconair/properties/releases) - [Commits](https://github.com/magiconair/properties/compare/v1.18.11...v1.18.12) --- updated-dependencies: - dependency-name: github.com/magiconair/properties dependency-version: 1.18.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
82718d36ca |
chore(deps): bump github.com/dustin/go-humanize from 1.0.1 to 1.1.0 (#5334)
Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.1 to 1.1.0. - [Commits](https://github.com/dustin/go-humanize/compare/v1.0.1...v1.1.0) --- updated-dependencies: - dependency-name: github.com/dustin/go-humanize dependency-version: 1.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
cc1bb1f28c |
chore(deps): bump github.com/hashicorp/hcl/v2 from 2.24.0 to 2.25.0 (#5332)
Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.24.0 to 2.25.0. - [Release notes](https://github.com/hashicorp/hcl/releases) - [Changelog](https://github.com/hashicorp/hcl/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/hcl/compare/v2.24.0...v2.25.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/hcl/v2 dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
14e96fcbf8 |
fix(ui): render TUI log lines on their own rows, with colors and a small header (#5324)
- log lines shown under the TUI were glued onto the last UI row, and the last row could get erased (and overwritten by the report) when the TUI exits. Fixed in `bubbly` v0.2.2 - log colors were lost because the logger latched its TTY check on the first entry, which went to the TUI footer buffer. `go-logger` v0.2.0 checks per entry and supports `TerminalWriter`, which the footer writer now implements (`NO_COLOR` is still honored) - log lines under the TUI get a dim `Logs (non-fatal, see -v for more):` title with a branch pointing at the first entry, so it's clear what the trailing lines are Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
af508550e6 |
test: pin multiplatform OCI fixture by digest (#5323)
Docker Hub rebuilt and re-pushed `busybox:1.38.0`, which changed the per-platform config digests and broke `TestMultiPlatformOCIImageSelection` on uncached runs. - pin the fixture to the busybox 1.38.0 index digest so future re-pushes can't shift it - update the expected per-platform digests to match Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
409ac78f62 |
fix(java): resolve Groovy 4+ group IDs to org.apache.groovy (#5313)
* fix(java): resolve Groovy 4+ group IDs to org.apache.groovy Groovy 4.0 moved from the Codehaus coordinates (org.codehaus.groovy) to the Apache Software Foundation (org.apache.groovy). JARs that ship no pom metadata fall through to the static artifact-to-group map, which carries the 3.x-era org.codehaus.groovy entries for the whole groovy family, so 4.x artifacts were attributed to an obsolete group even though their manifest identifies the Apache vendor. Where the static map attributes an artifact to the historical group, a major-version migration resolves groovy 4.x and newer to org.apache.groovy, while 3.x and earlier keep their historical group. The Eclipse-hosted groovy-eclipse-* artifacts, which share the prefix but kept their Codehaus coordinates, are excluded. Fixes #5311 Signed-off-by: texasich <101962694+texasich@users.noreply.github.com> * refactor(java): share version-aware groovy group ID lookup with CPE generation - move the artifact-to-group lookup behind `cpegenerate.ArtifactIDToGroupID(artifactID, version)` so the PURL and CPE paths agree; the exported mutable map is now unexported - version-dependent entries are expressed as a `groupIDMigration{before, after, atMajor}` (groovy moved to `org.apache.groovy` at 4.0); unconditional entries, including `groovy-eclipse-*` and apache-only 4.0+ modules, stay in the plain map - add apache-only modules (`groovy-ginq`, `groovy-contracts`, `groovy-callsite`, ...) that otherwise took the group from their per-module `Automatic-Module-Name` This changes CPEs for pom-less groovy 4+ jars (all real ones): vendors are now `apache` (e.g. `cpe:2.3:a:apache:groovy:4.0.33`) instead of `codehaus`, matching the PURL, jars with pom metadata, and NVD. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: texasich <101962694+texasich@users.noreply.github.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
41870fec7f |
fix: skip docker:// references in github-actions PURL generation (#5302)
closes #5299: docker:// references are OCI images, not GitHub repos. Returning empty string produces a package with no PURL, matching the treatment for local composite actions. Signed-off-by: Yunare Maia <yunare@gmail.com> |
||
|
|
97fd939355 |
fix(haskell): do not slice past the end of a stack.yaml rev pin (#5310)
An extra-dep can be pinned as "@sha256:<hash>,<size>" or as "@rev:<n>". The hash slice assumed the first form, so a rev pin left the end index at -1 and the parser panicked with slice bounds out of range. A rev pin carries no package hash, so only the sha256 form now yields one. Signed-off-by: Arpit Jain <arpitjain099@gmail.com> |
||
|
|
3f4105a657 |
fix(elf): avoid a panic on a truncated dynamic section (#5321)
Signed-off-by: somaz <genius5711@gmail.com> |
||
|
|
b49f0172f6 |
chore(deps): bump golang.org/x/net from 0.58.0 to 0.59.0 (#5309)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.58.0 to 0.59.0. - [Commits](https://github.com/golang/net/compare/v0.58.0...v0.59.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.59.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
46fd83817f |
chore(deps): bump golang.org/x/tools from 0.49.0 to 0.50.0 (#5307)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.49.0 to 0.50.0. - [Release notes](https://github.com/golang/tools/releases) - [Commits](https://github.com/golang/tools/compare/v0.49.0...v0.50.0) --- updated-dependencies: - dependency-name: golang.org/x/tools dependency-version: 0.50.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
a2ed08dfdd |
chore(deps): bump zizmorcore/zizmor-action from 0.6.3 to 0.6.4 (#5306)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.6.3 to 0.6.4. - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/70fb788f84895a7701f5643d103d587e460b5c99...cc914d7f3750a2d13d75c7f184a1060aa0e9d482) --- updated-dependencies: - dependency-name: zizmorcore/zizmor-action dependency-version: 0.6.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
34e7b0df98 |
chore(deps): bump anchore/go-make/.github/actions/setup (#5308)
Bumps [anchore/go-make/.github/actions/setup](https://github.com/anchore/go-make) from 0.8.0 to 0.8.1. - [Release notes](https://github.com/anchore/go-make/releases) - [Commits](https://github.com/anchore/go-make/compare/430e2175bb166bfd138ef75466adbcc8ddab875f...fa5421b3bf24c9d18f11a736c5eb4172f2b7d683) --- updated-dependencies: - dependency-name: anchore/go-make/.github/actions/setup dependency-version: 0.8.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3b41acb0ff |
chore(deps): bump go.opentelemetry.io/otel/sdk from 1.44.0 to 1.45.0 (#5301)
Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/sdk dependency-version: 1.45.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3325493831 |
chore: pin the bounds from the unbounded allocation work (#5298)
* build(deps): bump go-make to v0.8.1
Picks up golangci-lint 2.13.2 (built with go1.27) in place of 2.11.4 (go1.26).
The old build panics outright on a go1.27 GOROOT with "file requires newer Go
version go1.27", and an older mise-shim build fails the quieter way: it loses
type info and reports zero findings with exit 0, so a ruleguard run comes back
green without having evaluated anything.
Also folds in what the newer linter flags:
- `reflect.Ptr` -> `reflect.Pointer` in three files, applied by --fix
- `tw.CellFormatting{MergeMode: ...}` -> `tw.CellMerging{Mode: ...}` in the
cataloger info table, for a staticcheck SA1019 deprecation
Note the deprecation text names `CellConfig.CellMerging.Mode`, but the field is
`CellConfig.Merging`; `CellMerging` is the type. That command path has no test
coverage, so the swap was checked by rendering the same hierarchical-merge table
both ways and diffing: byte identical.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* chore(lint): tune goconst for data tables
goconst got stricter in golangci-lint 2.13 and now counts strings in map
literals and struct fields, which lights up every lookup table in the repo. It
was already enabled, so this is main going red on a linter bump rather than
anything new in the tree: 101 findings, and the default threshold of 3 means a
vendor name appearing in three rows of a table reads as a magic constant.
Raising `min-occurrences` to 5 takes that to 14. The rest split by whether the
repeated string is data or an actual constant:
- `cpegenerate` is excluded by path. It maps artifact names to vendors,
products, and maven group ids, and repeats strings 35, 83, even 293 times, so
no threshold reaches it. Hoisting those would trade a readable table for
indirection.
- the two SBOM fixture builders in `format/internal/testutil` get a
function-level nolint each. The repeated names and versions are what the
golden files assert against.
- `mysql-cluster` and `php` were genuine repeated literals and are now
constants.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* chore(test): extract measureAlloc into a shared helper
The allocation-budget tests added during the unbounded allocation work grew a
copy of the same helper in every package they touched: four identical
`measureAlloc` definitions plus two more spelled out inline against
`runtime.MemStats`.
Now `testutils.MeasureAlloc`, at the repo root so `internal/spillbuf` can reach
it too. The doc comment carries the two things that were only recorded in some
of the copies: TotalAlloc is process-wide so callers must not use t.Parallel,
and a budget assertion only means something if the fixture would really blow
past it unguarded, so measure the unguarded path as well.
No behavior change, the body was identical in all six.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* chore(lint): add ruleguard rules for overflow and swallowed EOF
Two rules out of the recent unbounded allocation work (#5187, #5195, #5267,
#5291, #5292, #5293, #5294).
`noOverflowingBoundsCheck` flags `offset + size > limit` (and <, <=, >=) where
an operand is unsigned, neither addend is constant, and the operands are at
least 32 bits. Unsigned addition wraps, so an offset out of a file header can
carry the sum past the wrap point and pass a check it should have failed. The
fix is to subtract against the limit instead. Constant addends are excluded
because `pos+4 <= uint32(len(buf))` is the normal way to step a cursor, and the
32-bit floor drops nibbles widened for a comparison. Would have caught four
sites across #5195 and #5291.
`noSwallowedEOF` flags `return nil` / `return nil, nil` on an io.EOF check. The
caller gets a zero value it cannot tell from real data, and anything decoding a
fixed-size struct off it parses the padding as content. That is the shape behind
#5186.
Both decide from the expression alone, and writing the fix deletes the pattern,
so neither needs a suppression anywhere in the tree. That was the bar: rules for
the allocation and decompression classes were tried and dropped, because the
bound they look for lives in a preceding statement (or, for a decompressor
constructor, always downstream), so no correct implementation can silence them.
That would have meant ~15 permanent nolints on code that is already right. The
rules here that do work don't judge whether a bound exists, they route to a
wrapper that takes one: noDirectTempFiles, noDirectELFOpen.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* test: pin allocation budgets on the alpm, deb, and changelog bounds
The bounds added for #5292, #5293 and #5294 are asserted by the error they
return. That keeps passing if the bound is removed and something else rejects
the input later, which is the failure these were written for. Each of these
measures bytes instead, and proves the fixture is really a bomb first so a
fixture that quietly stops being one fails rather than passing.
- alpm mtree: 16MB of newlines, well inside the 64MB byte cap, costs 16.5GB
uncapped against 238MB with maxMtreeLines. That 16.5GB is the measurement the
line cap was added for and it was recorded only in the commit message. Note
what the cap actually buys: 300k entries still cost a few hundred MB, per
concurrent cataloger.
- deb members: 1.28GB against 392KB. Both arms read with io.ReadAll on purpose,
since the stream itself never holds much whatever the cap says; what the cap
protects is a consumer that buffers, and the real one is a tar reader building
entries.
- snap changelog: 1.28GB against 175KB. This one has no explicit cap at all. It
is held by bufio.Scanner refusing a token past MaxScanTokenSize, which is
incidental, so switching to io.ReadAll or a bufio.Reader to pick up long lines
would remove it silently.
Nothing added for the kernel module reader: its copy is already
io.Copy(tempFile, io.LimitReader(rc, max+1)), so peak disk is bounded by
construction and heap is streaming. A heap budget would assert the wrong
resource, and the three existing subtests cover the cap, at-cap fidelity, and
spill cleanup.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* test: pin allocation budgets on the ai and alpm byte caps
Follows the same pattern as the alpm line cap: measure bytes, and prove the
fixture is really a bomb first so one that quietly stops being a bomb fails
rather than passes.
- safetensors: 252MB against 2.6KB. The existing over-cap case deliberately
omits the body, since the guard rejects on the declared length before reading
anything. That is the right test for the guard but it would keep passing if
the check moved after the read, so this one delivers the bytes.
- gguf: 268MB against 168MB. copyHeader io.Copy's whatever it is handed, so
maxHeaderSize holds only because every caller wraps the reader first. This
pins that the limit is where the bound lives, so dropping the wrapper at a
call site fails here.
- alpm byte cap: 740MB against 132MB, the counterpart to the line cap budget.
Worth knowing for anyone writing the next one of these: a single enormous line
does not exercise the alpm byte cap. go-mtree runs its own bufio.Scanner and
refuses a token past 64KB, so that fixture fails as "token too long" before the
byte cap is consulted. The fixture here uses ordinary 1KB lines, enough of them
to pass 64MB while staying under the 300k line cap, so the byte cap is actually
the thing under test.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
* test: assert bytes retained instead of allocated for gguf copyHeader
the bounded arm writes into a bytes.Buffer, whose doubling growth costs a
multiple of what it holds and lands on a different power of two depending on
the platform. it measured 168MB locally and 320MB on CI, past the budget.
buf.Len() states the same property deterministically.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
---------
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
|
||
|
|
02ba369d13 |
chore(deps): update anchore dependencies (#5236)
Signed-off-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> Co-authored-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com>v1.52.0 |
||
|
|
74073aa481 |
fix: update steroscope to include OCI platform metadata (#5128)
* Update steroscope to include OCI platform metadata Signed-off-by: Jason Paulos <jasonpaulos@users.noreply.github.com> * chore(deps): point stereoscope at anchore/main drops the fork `replace` directive and moves to a pseudo-version off stereoscope main (`a22e5ae`, anchore/stereoscope#657). no tag carries it yet, so this should move to a real version once one is cut. `readImageMetadata` now fills os/architecture/variant for every provider, not just oci. the docker tarball provider set neither before, so image metadata and the image purl change for docker-archive input too: - `"architecture": ""` -> `"amd64"` - `"os": ""` -> `"linux"` - `?arch=` -> `?arch=amd64` the empty `arch=` qualifier was never right, but it is user-visible output. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: cover single-platform oci layouts the pr only added assertions to the multiplatform fixture, which already reported the right platform. the behavior that actually changed is single-image layouts: they used to report empty os/arch and silently ignore `--platform`, and now report what the image config says and fail with a platform mismatch. also drops the non-linux skip on the default-platform test. the oci providers default to `linux/<GOARCH>` regardless of host os, so it holds on macos too. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Jason Paulos <jasonpaulos@users.noreply.github.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
0b234b3256 |
perf(javascript): cache npm registry license lookups across runs (#5297)
When javascript.search-remote-licenses is enabled, the lock catalogers query registry.npmjs.org once per package occurrence, on every run, with nothing in front of the network call. The same name@version is fetched again for every lockfile that mentions it, and again on the next scan. This adds a javascriptLicenseResolver that resolves licenses through syft's on-disk cache, keyed on "<name>/<version>" and namespaced as javascript/v1, and routes the package-lock v1, package-lock v2, pnpm, yarn and bun package constructors through it, leaving getLicenseFromNpmRegistry itself unchanged. Failed lookups are not cached: a transient registry failure or a name that 404s must not be able to blank out licenses for the lifetime of the cache entry, since a missing license silently weakens downstream license policy checks. A successful lookup that returns an empty license is a real answer and is still cached. Signed-off-by: Anže Pečar <anze@pecar.me> |
||
|
|
b4ee6e4696 | fix(debian): bound the decompressed members of a .deb archive (#5293) | ||
|
|
2be26a1af9 |
fix(kernel): bound what a compressed kernel module expands to (#5294)
`.ko.gz`/`.ko.xz`/`.ko.zst` modules are spilled to a temp file so ELF parsing can seek over them. The compressed size is bounded by the file in the image but what it decompresses to is not, so a few KB of crafted zstd filled the temp directory until the disk did. `io.Copy` now stops one byte past a 256MB cap and rejects rather than truncating, since a partial ELF reads as a corrupt module instead of a rejected one. The partial spill is removed on the reject path. The cap is a parameter on an inner function so the tests exercise the boundary with a few hundred bytes instead of materializing 256MB, matching `decompressSbomWithLimit` in the graalvm cataloger. The cap bounds one spill, not the process: peak disk is 256MB times however many modules are parsed at once, which is cataloger parallelism (`NumCPU*4` by default, and unbounded when `--parallelism` is negative). Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
30e9e65608 |
Fix: alpm mtree gzip unbounded processing (#5292)
* fix(arch): bound the decompressed mtree listing The ALPM cataloger handed a gzip stream straight to `mtree.ParseSpec`, which materializes every entry before it returns any of them. Nothing capped the decompressed size, so a crafted mtree in a scanned image expanded until the process died. 64KB of input is enough to produce 64MB, and the ratio scales. Reads through a limit now and rejects a listing that exceeds it. The read goes one byte past the cap so that tripping it is distinguishable from a listing that simply ends there, since silently truncating would produce a package missing most of its files. The existing error path already surfaces this as an unknown. The cap is well above any real package: an mtree names every file with its digests, and even the largest packages land far under it. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(arch): bound mtree entry count, not just decompressed bytes The byte cap alone does not bound what the parse retains. go-mtree keeps an entry per line and appends it unconditionally, blank lines included, so short lines cost about 1KB of heap each. Measured on the previous commit: 15KB of gzipped newlines expanding to 16MB, comfortably inside the 64MB byte cap, drove 8.1GB of peak heap over 74 seconds and returned no records and a nil error. Same input now rejects in 0.05s at 51MB peak. Counting lines before parsing is what makes the entry allocation bounded, since the parser materializes all of them before returning. Lines the parser skips only make the count high, never low. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(arch): apply the mtree bounds while streaming Both caps were enforced against a fully buffered listing, so the peak cost scaled with cataloger parallelism, which runs a goroutine per package. They now apply as the stream flows: a limited reader for the byte cap and a counting reader for the entry cap, which has to trip while the parser is still reading since it materializes every entry before returning any of them. Both limits are injectable so the boundary tests can be exact on both sides without allocating their way up to the shipped 64MB. Each cap gets a listing exactly at it, asserting the records parse whole rather than just that no error came back, since quiet truncation is the failure a size check invites. The byte limiter carries one byte of headroom, which is what makes an overrunning listing distinguishable from one that simply ends at the cap. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(arch): refuse mtree line continuations go-mtree collapses a line ending in a backslash into the next one with a quadratic string concat, so the cost is lines times bytes and neither the byte cap nor the line cap can see it. 199k continued lines sitting inside both caps measured 4m34s and 6.1TB of allocation churn, and returned no error. Also re-derived both constants against a real package rather than a guess. `texlive-fontsextra` is 105,847 files and 1,978 directories, so roughly 108k lines at 150-250 bytes each. 16MB would have falsely rejected it, so the byte cap stays at 64MB and the line cap goes 200k -> 300k, which is ~2.8x a real package instead of 1.85x. Rejections are now sentinels so tests can name which bound tripped, and the constants carry the measured ceiling they imply: a crafted listing of minimal fields still reaches ~1.1GB at the byte cap, since go-mtree allocates per whitespace-delimited field. Bounding that further means bounding fields rather than bytes, and legitimate input is already within ~1.3x of the crafted worst case, so there is little to win. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(arch): refuse a continuation spelled with a carriage return The refusal matched the literal bytes backslash-newline, but go-mtree decides "this is a continuation" from the scanner token, and `bufio.ScanLines` drops a trailing carriage return before testing for the backslash suffix. So a listing using CRLF ran the quadratic collapse with nothing watching. Measured: 775KB of gzip expanding to 17MB across 150k lines, inside both other bounds, took 1m33s and returned a nil error. Same input now rejects in 50ms. The check is a single byte pass that counts newlines and looks for a backslash before either line ending, which replaces the two scans and the straddle special case it took to catch only one spelling. A tripped bound is also latched now: `bufio.Scanner` stops at the first error, but nothing about the type said a caller had to, and the refusal was recomputed per chunk. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test(arch): make the bombpkg fixture able to fail The fixture was 310k bare newlines, which go-mtree turns into blank entries with no name, and the parse loop already skips those. So the assertion that a rejected listing leaves an empty file list held whether or not the bound existed. It is now a repeated file line: 0 records with the bound, 310,000 without. Dropped the two tests that asserted nothing as a result. The cataloger-level one was a weaker restatement of what the golden comparison already pins exactly, and the shipped-size-cap one drove a real 64MB listing through the parser to prove a constant had not moved. Asserting the constants directly catches any change rather than only a loosening. Race-mode package runtime goes 15.7s -> 4.4s. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test(arch): make three mtree assertions able to fail The truncated-gzip case asserted the failure was not misreported as the size bound, but ran against the shipped 64MB cap on a 3.5KB listing, so the check it guarded could never fire. It now runs against a cap just above the truncation point, where the two genuinely race. The bomb case asserted an allocation delta from `runtime.MemStats`, which is a process-wide cumulative counter and measures the property indirectly. It now counts bytes pulled through the reader, which is what "gave up while reading" actually means and does not drift with GC or parallelism. Added the one bound that had no test at all. A single line over 64KB is refused by go-mtree's scanner, not by anything here, so a dependency bump that called `scanner.Buffer` would remove it silently. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * docs(arch): correct the mtree bound comments and close the gzip reader The comment said both bounds apply while the listing streams. Only the line cap does. `io.LimitedReader` signals a drained budget with `io.EOF`, which the parser reads as the end of a complete listing, so the byte cap is necessarily a check after the fact. Peak cost is the same either way, since accepting a listing at the cap costs what rejecting one just past it does, but the comment was claiming a mechanism that isn't there. Also trimmed the benchmark writeups down to the reasoning that outlives them. The exact transient-heap and wall-clock figures are tied to a go-mtree version with nothing to detect them going stale, and one of them described the CRLF case as prevented when it wasn't. And `TestMtreeParse` was labelled the regression guard for the continuation refusal, but its fixture has no backslashes in it. `gzReader` now gets a `defer Close` to match the other gzip site in the tree. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(arch): stop the alpm database parser panicking on a malformed backup line A `%BACKUP%` entry is `path\tmd5`, and the parser indexed the second field without checking the split produced one. A line with no tab panics. The database is attacker-controlled, and while the task executor recovers, it recovers at task granularity, so one crafted line drops every Arch package from the SBOM. The same loop also never checked the scanner's error. A field over the 1MB token cap ends the scan, and the entry came back populated up to that field and empty after it, with a nil error saying it was fine. That is the failure the mtree bounds in this branch exist to avoid, two functions up in the same file. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * docs(arch): trim the comments added by this branch the comments were carrying reasoning that belongs in the PR description rather than the code. Kept the parts a future reader cannot recover from the code (the cap sizing, why continuations are refused, why the byte check runs after the parse) and cut the rest. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test(arch): generate the mtree bomb fixture instead of committing it the fixture is a synthetic listing fully described by a line count and a repeated line, but it landed as a committed gzip member, which shows neither and leaves no way to rebuild it when maxMtreeLines moves. the two tests that walk testdata/installed write it first now, and git ignores it. the real pacman listings (gmp, tree-sitter) stay committed since those are captured output rather than something a rule can reproduce. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * correct and simplify comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
e6d364437a |
feat(convert): passthrough-exact-format copies matching syft-json input unchanged (#5279)
* feat(convert): passthrough-exact-format copies matching syft-json input unchanged When a syft-json input SBOM already exactly matches the requested output format and schema version, `syft convert` can write the input bytes out verbatim instead of decoding and re-encoding them. This avoids the lossy round trip (and the associated cost) for callers that normalise SBOMs into a target format without knowing up front whether the source already matches. The behaviour is opt-in and configuration-only: it is enabled with convert.passthrough-exact-format in the config file or the SYFT_CONVERT_PASSTHROUGH_EXACT_FORMAT environment variable, and there is deliberately no command-line flag, since it changes how every invocation in a pipeline behaves rather than varying call by call. It defaults to off, so existing behaviour is unchanged. Pass-through applies only to syft-json input, and only when the document's schema version is identical to the one this build of syft produces; any other input is always converted. Outputs that do not match are still converted, so a mixed set of `-o` values behaves as expected, and every output is resolved and validated before anything is written. Pass-through output is a byte-for-byte copy, so format options such as `format.pretty` and `format.json.legacy` are not applied to it. When the option is enabled a warning is logged stating so and noting the syft-json-only scope, and a second warning is logged whenever an input is found to match and the shortcut is actually taken. The option lives under a `convert:` configuration section (options.Convert), mirroring how attest scopes its own options. The description documents the caveats above and notes that file-path input written with `-o <format>=<path>` keeps memory minimal, whereas piped input or STDOUT output must hold the whole document in memory. Signed-off-by: juanjsebgarcia <github@juangarcia.co.uk> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * perf(convert): stream pass-through output instead of buffering the input Identify, copy, and decode the input through a seekable reader rather than loading the whole document into memory first. File inputs are read in place and pass-through output is an io.Copy from the file. STDIN is used in place when it is a redirected regular file. A pipe or terminal cannot seek, so its contents are drained into an internal/spillbuf buffer: the first 1MB stays in memory and the rest goes to a temp file under the user's TMPDIR (created lazily, only when something actually spills), giving a seekable view without ever holding the document in memory. The temp dir is rooted per invocation via internal/tmpdir, honouring one already attached to the context, and is removed when the command returns. Writing pass-through output over the input file itself is now a no-op rather than truncating the file before it is copied. Measured on a 1GB syft-json document (Apple M1 Max), file in, file out: origin/main full re-encode 86.3s wall 99s CPU 12.0GB max RSS in-memory pass-through 7.4s wall 6.7s CPU 4.2GB max RSS streaming pass-through 7.0s wall 6.9s CPU 3.1GB max RSS and the same 1GB document piped over STDIN with pass-through enabled: io.ReadAll into memory 1.3s wall 0.8s CPU 2.7GB max RSS spill buffer 1.8s wall 1.0s CPU 66MB max RSS The remaining cost of the streaming pass-through is entirely format.Identify, which for syft-json decodes the whole document through a json.Decoder that buffers the full value: measured alone it takes 7.1s and 3.1GB on the same input. Reducing that further belongs to the format package. Signed-off-by: juanjsebgarcia <github@juangarcia.co.uk> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * perf(syftjson): identify documents from their tail instead of a full parse syft always writes the schema block as the last key of a syft-json document, so the format and version can be read from the final bytes of the file. The syft-json decoder's Identify now reads at most 2MB from the end of a seekable input, finds the last "schema" key there, and verifies it sits directly within the top-level object by scanning the remaining bytes for the single closing brace of the document. Anything the tail cannot vouch for (non-seekable input, a reordered document, a nested key) falls back to the existing full parse, so behaviour is unchanged for documents the fast path declines. The verification is position-independent: it also accepts documents whose keys have been reordered so that other small keys follow schema. It rejects nested schema objects, array-wrapped documents, truncated input, trailing content, and schema text inside strings. Supporting changes: - the offset-aware reader the decoder collection hands to decoders now supports io.SeekEnd, which the tail read requires - model.Document documents that Schema must remain its last field, and a test guards that the encoder writes it last in both compact and pretty output - the decoder list documents that syft-json must stay first Since Decode calls Identify before parsing, this also removes one of the two full parses every syft-json conversion used to perform. Measured on a 1GB syft-json document (Apple M1 Max), file in, file out: pass-through, previous commit 7.0s wall 6.9s CPU 3.1GB max RSS pass-through, this commit 0.85s wall 0.05s CPU 66MB max RSS full re-encode, previous commit 85.3s wall 99s CPU 11.5GB max RSS full re-encode, this commit 75.2s wall 91s CPU 9.9GB max RSS Signed-off-by: juanjsebgarcia <github@juangarcia.co.uk> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(syftjson): only identify from the tail when the document starts where the reader does The tail scan vouched for the last 2MB, but nothing checked those bytes belonged to the document the reader was pointed at. So `Identify` started claiming syft-json for files it cannot decode: a document with a byte order mark or a stray log line in front of it, or a syft document sitting after something else in the same stream, where `format.Decode` went on to return an empty SBOM with a nil error. Two O(1) checks: - the first non-space byte at the reader's position has to be `{` - `Decode` errors when the parsed document has no `schema.version`, instead of handing back the empty document it decoded into Streams holding several concatenated documents are deliberately not detected. Proving there is exactly one top-level value means reading all of it, which is the cost the tail path exists to avoid, so `Identify` can still name a later document's version on such a file. `Decode` reports what it actually parsed. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(convert): recognize the input file by identity rather than by path `convert - -o syft-json=sbom.json < sbom.json` truncated the file and exited 0. The passthrough guard compared paths, and a shell redirect does not have one, so it was blind to the one input mode where the truncation was silent. `openConvertInput` now returns the input's `os.FileInfo`, which the STDIN branch already had from its `Stat`, and `isSameFile` compares that instead. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: juanjsebgarcia <github@juangarcia.co.uk> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
c55e1cf2fd |
Fix: graalvm unbounded allocation (#5291)
* fix(java): bound user-controlled reads in the graalvm native-image cataloger Sizes, offsets and lengths in a native-image binary all come from the file being parsed, and several were acted on directly. The PE export directory sized a `make([]byte, Size)` from a uint32 in the optional header, so an 8KB file claiming 4GB reserved 4GB before reading a byte. It now reads what the file actually holds, bounded by the declared size, and still requires the whole directory to be present. This also fixes a latent bug: `unionreader.readerAtAdapter.ReadAt` can return a short read with a nil error, and the old code discarded the count, so squashfs-sourced binaries could parse zero padding as export data. The bounds checks in `decompressSbom` and the PE export walk validated only the end of a range, computed by adding to a value out of the file. Those sums wrap, and a wrapped sum compares as in range while the slice that follows it panics. They now subtract from the known-good length instead. The three `address - sectionBase` subtractions are unsigned and underflowed on an address below the base; they share one guarded helper now. The embedded SBOM decompresses through an `io.LimitedReader`, since the compressed bytes are bounded by the file but what they expand to is not. The limit is checked before the decoder's own error, which would otherwise report a size problem as "not a cyclonedx json document". Hitting it is logged, since the caller reports parse failures at trace level and a dropped SBOM would otherwise go unnoticed. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(java): stop at the first format that parses a native image The format loop tried ELF, Mach-O and PE against every reader and kept going after one of them parsed, so a file that parses as more than one format contributes its packages once per format. No input parses as two formats today, so this is not a behavior change in practice: `debug/pe` rejects an ELF on the optional-header magic, and both `debug/elf` and `debug/macho` reject anything without their own magic. It removes the possibility rather than a live bug. Note this does not deduplicate across readers, which is the outer loop: a universal Mach-O carrying the same native image for two architectures still yields its packages twice. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(java): bound the graalvm export directory read against the file Follow-up to the hardening earlier in this branch, from review. The export directory read was still bounded only by file size, so a large mostly-empty file (cheap to ship inside a compressed layer) was read into memory in full and then discarded. It now weighs the declared size against an absolute cap and against the bytes really present before allocating anything, matching `readDataFromRVA` in `syft/pkg/cataloger/internal/dotnet/pe`. A 64MB file declaring a 4GB export directory went from ~140MB allocated to ~3.6KB. Also from the same review: - dropped the decompressed-SBOM ceiling from 100MB to 16MB. Decoding costs roughly 48x the JSON size in allocation and 6x retained, so the old ceiling was worth several GB of churn per file, and catalogers run concurrently. - `symbolOffsets` is now single-address `symbolOffset`. Two same-typed address arguments in a row could be transposed at a call site and would produce a silently wrong SBOM rather than an error. - `newMachO` now returns an error on a real read failure. It was falling through and dropping the file with no error and no log at all. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(java): reject a short read of the graalvm export directory Second round of review on this branch. The export directory read introduced earlier in the branch only consulted the byte count when `ReadAt` also returned an error, so a reader handing back fewer bytes with a nil error was accepted and the tail of the buffer stayed zeroed, then got parsed as a valid export directory. The count decides now, whatever the error says. `io.ReadFull` would also cover it but spins forever on a reader that keeps answering `(0, nil)`, which is the same hostile input this is meant to reject, so the single read stays. Also in here: - the decompressed SBOM is buffered and size-checked before the decoder is invoked, instead of inferring the overrun from `io.LimitedReader.N` after the fact. That check was only correct because the cyclonedx decoder happens to drain its reader before parsing, and nothing would have caught it if that changed. - four bounds guards, the `newMachO` error path and the SBOM ceiling had no test that failed when they were removed. They do now, verified by deleting each one and watching a named test go red. The ceiling in particular could be raised to 2GB with the suite still green, since every case derived its sizes from the constant. - `nativeImagePE.reader` is dead now that the read takes the reader as an argument. - three comments claimed guarantees the code does not make: that `symbolOffset` cannot be called with transposed arguments, that a rejected file cannot go unrecorded when it is only logged at trace level, and a set of measured allocation multipliers that nothing pins. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * chore(java): trim the graalvm cataloger comments Comment pass over everything this branch added. Cut about a quarter of the comment lines, mostly restatement and detail that was never going to stay true. Dropped: a production comment naming a test, the measured allocation multiples behind the two size ceilings, cross-references to the equivalent constants in `dotnet/pe` and `elfutil`, and test docs that restated their own test name before saying anything. Kept the parts that fail silently if someone tidies them away: why the read is a single `ReadAt` rather than `io.ReadFull`, the note that `readExportDirectory` uses an RVA directly as a file offset, and the fixture notes explaining why a `VirtualAddress` is deliberately huge or why no symbol sits at index 0. Three that were stale rather than verbose: - `nativeImagePE`'s doc still said it holds the underlying reader, which this branch deleted. - `TestFetchSbomSymbols_FindsAllThreeSymbols`' doc had ended up above `TestFetchSbomSymbols_AddressBaseOutOfRangeGuard`, documenting the wrong function. - a comment referenced a commit sha that no longer exists on this branch. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> |
||
|
|
356e601cd5 |
chore(deps): update CPE dictionary index (#5290)
Signed-off-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> Co-authored-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com> |