Compare commits

...
Author SHA1 Message Date
Alex Goodman cc326e45a6 fix: preserve quoted args when deferring to tagged install script (#5363)
The installer word-split the original arguments when piping into a tagged release's `install.sh`, so `-b '/tmp/syft install path'` arrived as three words and the trailing word was treated as a release tag.

- argument parsing moved into `parse_install_args` so `main` keeps its original args
- tagged script is invoked with `"$@"` instead of the unquoted `PROGRAM_ARGS`

Port of anchore/grype#3738

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-10-01 15:00:50 -04:00
Alex GoodmanandKeith Zantow ef356eccea fix: sign release checksums with a sigstore bundle (#5362)
* fix: sign release checksums with a sigstore bundle

The cosign update rejects the legacy `--output-certificate` / `--output-signature` flags alongside the new bundle format, which broke signing during the v1.53.0 release.

- checksums are now signed into a single `checksums.txt.sigstore.json` bundle (signature, certificate, and tlog proof)
- `install.sh -v` verifies with `--bundle` for releases >= v1.53.0 and keeps the `.sig` / `.pem` flow for older releases

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* chore: smooth the cutover to sigstore bundle verification

- `install.sh -v` now hints that bundle verification needs cosign v2.5.0 or newer when verification of a bundle-signed release fails
- install tests keep exercising the legacy `.sig` / `.pem` path against v1.52.0 once the latest release is bundle-signed
- touch the install test environment Dockerfiles so the cached images rebuild with a current cosign

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: only hint at the cosign minimum when cosign is actually too old

- `install.sh -v` used to print the cosign v2.5.0 note on any failed bundle verification, including a missing bundle or a bad signature. It now only prints it when cosign reports it couldn't read the bundle (`bundle does not contain cert`), which is how v2.2.4 through v2.4.1 fail on a sigstore bundle.
- `verify_sign` is now covered with a stub cosign, so the bundle path is exercised before any bundle-signed release is latest

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: stop the install when a release asset is missing or fails its checksum

- `install.sh` now stops when the checksums file, the signature bundle, or the asset can't be downloaded, instead of handing an empty or error-page file to the next step
- a sha256 mismatch between the asset and the checksums file now stops the install. Before this the mismatch was logged and the asset was installed anyway, so `-v` only ever vouched for `checksums.txt`, not the binary

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* chore: pin cosign in the install test images

The install test images pulled whatever cosign was latest, unverified. They now install cosign v3.1.3 checked against its published sha256, so the install tests always run with a cosign that can verify sigstore bundles (releases >= v1.53.0 need v2.5.0 or newer).

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2026-10-01 15:00:37 -04:00
dependabot[bot] db7e445c54 chore(deps): bump github.com/ulikunitz/xz from 0.5.16 to 0.5.17 (#5366)
Bumps [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) from 0.5.16 to 0.5.17.
- [Commits](https://github.com/ulikunitz/xz/compare/v0.5.16...v0.5.17)

---
updated-dependencies:
- dependency-name: github.com/ulikunitz/xz
  dependency-version: 0.5.17
  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>
2026-10-01 18:03:47 +00:00
dependabot[bot] d654c00bd6 chore(deps): bump github.com/bmatcuk/doublestar/v4 from 4.10.0 to 4.10.2 (#5365)
Bumps [github.com/bmatcuk/doublestar/v4](https://github.com/bmatcuk/doublestar) from 4.10.0 to 4.10.2.
- [Release notes](https://github.com/bmatcuk/doublestar/releases)
- [Commits](https://github.com/bmatcuk/doublestar/compare/v4.10.0...v4.10.2)

---
updated-dependencies:
- dependency-name: github.com/bmatcuk/doublestar/v4
  dependency-version: 4.10.2
  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>
2026-10-01 18:00:41 +00:00
Alex Goodman c00be184b5 fix: revert hashstructure to v0.6.0 to keep artifact IDs stable (#5364)
* fix: revert hashstructure to v0.6.0 to keep artifact IDs stable

hashstructure v1 changed `SlicesAsSets` hashing (every slice now gets a finalizing hash pass, and duplicate elements are deduplicated), which changed nearly every artifact ID syft produces.

- pin `github.com/gohugoio/hashstructure` back to v0.6.0 and have dependabot ignore major bumps
- make the aggregate `Unit tests` job fail when a dependency fails; previously it was skipped, which satisfies the required check and let #5277 auto-merge with failing unit tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* ci: fail unit test gate on any failed, cancelled, or skipped dependency

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* ci: require every unit test dependency to succeed

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-10-01 17:40:34 +00:00
dependabot[bot] 29d2de72c8 chore(deps): bump github.com/gohugoio/hashstructure from 0.6.0 to 1.1.0 (#5277)
Bumps [github.com/gohugoio/hashstructure](https://github.com/gohugoio/hashstructure) from 0.6.0 to 1.1.0.
- [Release notes](https://github.com/gohugoio/hashstructure/releases)
- [Commits](https://github.com/gohugoio/hashstructure/compare/v0.6.0...v1.1.0)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hashstructure
  dependency-version: 1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 15:35:21 +00:00
anchore-oss-update-botandanchore-oss-update-bot ae649b09cf chore(deps): update anchore dependencies (#5304)
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>
2026-10-01 11:11:11 -04:00
Alex Goodman f76ae8e57c feat: report the perl interpreter as a cpan package (#5132)
The `perl-binary` classifier now emits `pkg:cpan/perl@<version>` instead of `pkg:generic/perl@<version>`.

`perl` is itself a CPAN distribution and carries advisories under that name, so typing it as `generic` left every one of them unreachable.

This changes an existing purl. Anything keyed on `pkg:generic/perl` (allowlists, policy, SBOM diffs) needs updating.

Builds on the cpan cataloging branch, which adds the package type this uses.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-30 15:45:46 -04:00
King StarandWill Murphy 509f1a1a5b fix(java): filter Tomcat server CPE for embedded EL (#5322)
Signed-off-by: King Star <54024410+jstar0@users.noreply.github.com>
Co-authored-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2026-09-30 15:34:29 -04:00
Alex Goodman 8d5567c423 feat: catalog CPAN distributions installed by perl clients (#5131)
* feat: catalog CPAN distributions installed by perl clients

Adds `perl` as a language and `cpan` as a package type, with two catalogers behind them.

- `perl-cpan-installed-cataloger` reads `.meta/*/install.json` (cpanm, cpm, carton) and `auto/**/.packlist` (anything installed through `ExtUtils::MakeMaker` or `Module::Build`, including CPAN.pm). Both globs are unanchored, so a local-lib or carton application tree is found the same as a system install.
- `perl-cpan-meta-cataloger` reads an unpacked release's own `META.json` or `META.yml`, gated on a sibling `MANIFEST` so source checkouts are ignored.

Packages are keyed on the **distribution**, not the module, because that is what CPAN, MetaCPAN and the advisory data all use. `LWP.pm` belongs to `libwww-perl` and reporting it as `LWP` would make every advisory for it unreachable. The distribution name comes from `install.json`'s `dist` field, and from the `auto/` path for packlists.

purls are `pkg:cpan/<distribution>@<version>`, with the PAUSE author added as an `author` qualifier when the evidence carries it. Metadata comes in two types: `cpan-distribution` for installed evidence and `cpan-unpacked-release` for a release sitting on disk. The tiers differ in more than a name, so a consumer should not have to string-match a cataloger name to tell them apart: an unpacked release has no PAUSE path and therefore no author and no file list, and "installed and loadable by the interpreter" is a materially stronger claim than "a source tree exists here".

A packlist's version comes from `perllocal.pod`. EUMM writes it and the packlist from the same variables in the same install target, so the `auto/` path segments and the perllocal `Module` name are the same key, and the recorded `VERSION` is what was evaluated at build time rather than what can be read back statically. Scraping `$VERSION` out of the main `.pm` is the fallback, since `NO_PERLLOCAL` suppresses the file and Module::Build never writes one. Three rules pick the stanza: dashed module name, longest `installed into` libdir that prefixes the packlist path, and last match wins because the file is append-only. Without the libdir rule a second perl on the image silently supplies the version.

Where scraping is the fallback, it reads more than a plain `our $VERSION = '...'`. A version declared in the package statement (`package Foo::Bar v1.0.0;`) counts, which matters because a distribution can declare it that way and carry no `$VERSION` at all: `CPAN::02Packages::Search` is one, and without this it reports no version and matches nothing. Fully qualified `$Foo::Bar::VERSION` counts too, which is what older Dist::Zilla emitted and what real `JSON::PP` 2.27300 still carries. `qv('1.2.3')` is handled. A version computed at runtime is not, and those are reported without one rather than guessed at.

Packlist entries are parsed the way `ExtUtils::Packlist` writes them: a line can carry space-separated metadata after the path (`/path/to/File.pm type=file`), which is what `installperl` produces, so the suffix is stripped rather than the line being cut at its first space.

`META.yml` is read alongside `META.json`, because about 43% of current CPAN releases ship no `META.json` and they skew old, which is where the advisories are. Where both sit in one directory the `META.json` wins. A `META.yml` a strict parser rejects skips that directory rather than failing the scan, which is routine rather than defensive for pre-spec releases.

A packlist is literally a file list, so its paths are surfaced through `pkg.FileOwner`. They are reported as recorded, unfiltered: a path the packlist claims and the filesystem lacks means the file was removed or overwritten out from under the installer, which is worth seeing rather than hiding.

Build leftovers under `~/.cpanm/work` and `~/.cpan/build` are skipped. They genuinely are unpacked release tarballs, `MANIFEST` included, so the `MANIFEST` gate admits them and a path exclusion is the only signal available. Without it every distribution on an image that did not clean up is reported twice. The cost is that a tarball deliberately kept under `~/.cpan/build` stops being reported.

Two behaviors that look wrong but are not:

- a distribution can be reported twice at different versions. `libwww-perl` 5.836 bundles `HTTP-Date`, `HTTP-Message` and `LWP-MediaTypes`, and installing it over the modern standalone releases overwrites their `.pm` files. Both versions are genuinely present, so the merge refuses to pair disagreeing versions rather than hiding one.
- a distribution with no readable version is reported without one rather than dropped, so it stays visible.

The coverage boundary is stated in full in the `package perl` doc comment. In short: modules installed from distro packages are out of scope, since packagers strip CPAN metadata with `NO_PACKLIST` and deb, rpm and apk already report them. Core and dual-life distributions bundled with the interpreter have no coverage, because nothing on disk carries their versions; the interpreter itself is reported separately. Vendored trees, `App::FatPacker` output and PAR archives are invisible, because what they carry is module identity with no offline map to a distribution. A packlist-derived name is the installer's `NAME` and may not be the distribution name, which is left to the vulnerability data to resolve.

One correction worth calling out, since it is easy to arrive at twice: a sibling `MANIFEST` is the only thing separating an unpacked release from a source checkout. An earlier version of the guard also rejected any tree containing a `dist.ini`, on the theory that it marked a Dist::Zilla source tree. dzil ships `dist.ini` *inside* the tarballs it builds and lists it in the generated `MANIFEST`, so that exclusion was silently skipping real releases, `URI` among them.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test(cli): bump coverage image package count for the new perl fixture

The CPAN fixture added to `image-pkg-coverage` (`URI-5.35`) is shared with the CLI suite through a symlink, so the squashed package count goes from 42 to 43.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(perl): take CPAN distribution name and version from install.json dist

cpanm writes the main module's `$VERSION` into `version` and the release distvname into `dist`, and the two often disagree (`Carp-Assert-More-2.9.0` records `2.009000`). Trimming `version` off `dist` failed on those, so the whole distvname became the package name, and the module version never paired with the packlist record for the same install, which reported the distribution twice.

- name and version are now split out of `dist` at the last `-` followed by a version, as CPAN::DistnameInfo does
- a `-TRIAL` suffix is dropped, so `Try-Tiny-0.26-TRIAL` reports as `Try-Tiny` `0.26`
- the PAUSE path fallback uses the same split

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-30 14:15:05 -04:00
Mayank Sekhar d61c2cada3 fix(binary): correct traefik version detection on big-endian arches (#5281)
The traefik-binary classifier regex required a NUL byte or replacement
character immediately before the version string. On some architectures
(e.g. s390x), the real version string in the binary has no such prefix,
so the classifier instead matched an earlier, unrelated NUL-prefixed
numeric string elsewhere in the binary (e.g. '2.7.0' from an unrelated
printf-style format constant), producing an incorrect version.

This adds an additional pattern, tried first via MatchAny, that matches
version strings terminated by two NUL bytes without requiring a NUL
prefix. The original pattern remains as a fallback for older traefik
binaries (e.g. v1.x) that use single-NUL termination.

Fixes #4980

Signed-off-by: Mayank Sekhar <mayankshekharsingh@gmail.com>
2026-09-30 16:51:03 +00:00
0bbcff1ae3 Fix hang when scanning a Java resource adapter (.rar) file (#5348)
* fix: scan a Java resource adapter (.rar) given as a file source

The file source picks an extractor for the scan target from its file name
alone. A Java resource adapter archive (JCA) uses the .rar extension but is
a zip file, so it was handed to the RAR extractor. With rardecode v2.2.0 the
RAR extractor does not return on non-RAR input (an infinite loop in its
signature search), so `syft scan --from file adapter.rar` never finished.
The same bytes named .jar scan in under a second.

When the name says RAR but the content is a zip archive, leave the file
unextracted, the same way .jar, .war and .ear scan targets are handled. The
java cataloger already reads .rar files (anchore/syft#4136) and now finds the
resource adapter and its nested jars. Real RAR archives are still extracted.

Signed-off-by: Ismael Donmez <ismael.donmez@chainguard.dev>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(deps): bump github.com/nwaples/rardecode/v2 from 2.2.0 to 2.4.1

rardecode v2.2.0 loops forever in its RAR signature search when the input
does not start with a RAR signature (fixed upstream in nwaples/rardecode
f501ac8, "fix infinite loop in findSig", released in v2.2.2). syft reaches
that search for any file source whose name contains ".rar", so a scan of a
self-extracting RAR, or of any other file with such a name, never finished.
With v2.4.1 a self-extracting RAR is extracted, and a file that is not a RAR
archive fails fast with "rardecode: RAR signature not found", the same way a
file named .zip that is not a zip archive fails today.

github.com/mholt/archives v0.1.5 (the latest release) still requires v2.2.0,
so the newer version is selected here.

Signed-off-by: Ismael Donmez <ismael.donmez@chainguard.dev>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: clarify the .rar zip log line and cover self-extracting RAR sources

- the debug line no longer calls every `.rar`-named zip a resource adapter
- a self-extracting RAR (stub before the signature) given as a file source is
  still extracted

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Ismael Donmez <ismael.donmez@chainguard.dev>
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>
2026-09-30 15:54:36 +00:00
anchore-oss-update-botandanchore-oss-update-bot f21e439a63 chore(deps): update SPDX license list (#5315)
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>
2026-09-30 15:50:47 +00:00
Will Murphy c511d509ef fix: match opensource.org license URLs across SPDX URL forms (#5361)
SPDX license list 3.29.0 rewrote every opensource.org seeAlso URL from
opensource.org/licenses/<ID> to opensource.org/license/<ID> (and e.g.
opensource.org/license/mit/ to opensource.org/license/MIT). Package metadata
in the wild (Maven POM <license><url>, npm, etc.) still uses the older forms,
so regenerating the list would stop LicenseByURL from resolving them.

Fall back to a normalized index of the opensource.org entries that ignores the
www. prefix, /licenses/ vs /license/, case, and a trailing slash. The index is
built from the generated urlToLicense map, so it works with both the current
and the updated license list without changes to the generator. URL forms that
would normalize to different licenses are left out rather than guessed.

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2026-09-30 15:07:12 +00:00
Alex Goodman fdeb78fc19 Speed up unit tests in CI (#5360)
* ci: drop gen-5 instance families from the test runner

m5a runners run the unit suite ~2.3x slower than m8id (15m vs 6.5m for `go test`), and when spot capacity for newer families dries up runs-on falls back to them. Keeping gen-6 through gen-8 leaves a wide spot pool without the slow tail.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test: shrink decompression bomb fixtures

The bomb and heap-bound tests pushed 100MB-16GB through gzip and parsers, which under `-race` made them the slowest tests in the unit suite. Each now uses the smallest fixture that still separates the bounded path from the unbounded one.

- arch mtree caps are measured at a small cap and scaled to the shipped constants, so raising a cap still fails the budget
- snap and debian bombs expand to 32MB instead of 512MB (4x their 8MB budgets)
- golang ELF bombs still declare 256MB but deliver 64MB

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-30 10:58:11 -04:00
Alex Goodman 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>
2026-09-30 09:08:13 -04:00
Darragh McGurk b23e461731 Stop reading the mount table for every single-file source (#5257)
Signed-off-by: Darragh McGurk <120114728+dmcg310@users.noreply.github.com>
2026-09-29 21:44:47 +00:00
KR RavindraandWill Murphy 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>
2026-09-29 21:43:53 +00:00
Antoni (Tony) JagodkaandAlex Goodman 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>
2026-09-29 21:30:52 +00:00
Dev MandAlex Goodman 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>
2026-09-29 17:23:22 -04:00
Pujitha PaladuguandPujitha Paladugu 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>
2026-09-29 17:15:36 -04:00
Alex Goodman 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>
2026-09-29 16:52:58 -04:00
Alex Goodman 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>
2026-09-29 16:26:42 -04:00
Alex Goodman 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>
2026-09-29 16:01:21 -04:00
Will Murphy 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>
2026-09-29 19:00:42 +00:00
huuya 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>
2026-09-29 14:25:04 -04:00
Alex Goodman 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>
2026-09-29 11:19:17 -04:00
Alex Goodman 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>
2026-09-29 10:25:30 -04:00
Alex Goodman 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>
2026-09-29 14:03:31 +00:00
Alex Goodman 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>
2026-09-29 09:40:17 -04:00
Alex Goodman 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>
2026-09-29 09:33:57 -04:00
Alex Goodman 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>
2026-09-29 09:21:03 -04:00
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>
2026-09-28 19:22:45 +00:00
Antoni (Tony) Jagodka 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>
2026-09-28 15:01:13 -04:00
Sverrir Sigurdsson ff45b9ea63 fix: release package collection lock in Ubuntu ESM detection (#5336)
Signed-off-by: Sverrir Sigurðsson <svesig@gmail.com>
2026-09-28 17:10:55 +00:00
anchore-oss-update-botandanchore-oss-update-bot 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>
2026-09-28 01:50:42 +00:00
anchore-oss-update-botandanchore-oss-update-bot 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>
2026-09-27 10:26:41 +00:00
dependabot[bot] 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>
2026-09-25 19:53:30 +00:00
dependabot[bot] 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>
2026-09-25 13:35:26 +00:00
dependabot[bot] 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>
2026-09-25 13:29:56 +00:00
dependabot[bot] 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>
2026-09-25 13:29:46 +00:00
dependabot[bot] 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>
2026-09-25 13:29:27 +00:00
dependabot[bot] 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>
2026-09-25 13:29:03 +00:00
dependabot[bot] 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>
2026-09-25 13:28:33 +00:00
Alex Goodman 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>
2026-09-24 10:40:52 -04:00
Alex Goodman 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>
2026-09-24 10:05:59 -04:00
texasichandAlex Goodman 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>
2026-09-23 17:20:28 +00:00
Yunare Maia 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>
2026-09-23 14:13:22 +00:00
Arpit Jain 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>
2026-09-23 14:07:57 +00:00
somaz 3f4105a657 fix(elf): avoid a panic on a truncated dynamic section (#5321)
Signed-off-by: somaz <genius5711@gmail.com>
2026-09-23 14:02:12 +00:00
dependabot[bot] 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>
2026-09-18 13:30:41 +00:00
dependabot[bot] 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>
2026-09-18 13:30:01 +00:00
dependabot[bot] 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>
2026-09-18 13:29:50 +00:00
dependabot[bot] 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>
2026-09-18 13:29:37 +00:00
dependabot[bot] 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>
2026-09-18 00:39:32 +00:00
Alex Goodman 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>
2026-09-17 14:11:16 -04:00
anchore-oss-update-botandanchore-oss-update-bot 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>
2026-09-17 10:09:46 -04:00
Jason PaulosandAlex Goodman 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>
2026-09-16 01:40:54 +00:00
Anže Pečar 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>
2026-09-15 17:32:28 -04:00
Alex Goodman b4ee6e4696 fix(debian): bound the decompressed members of a .deb archive (#5293) 2026-09-14 18:17:56 -04:00
Alex Goodman 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>
2026-09-14 17:40:41 -04:00
Alex Goodman 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>
2026-09-14 19:59:20 +00:00
JuanandAlex Goodman 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>
2026-09-14 14:33:03 -04:00
Alex Goodman 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>
2026-09-14 13:49:19 -04:00
anchore-oss-update-botandanchore-oss-update-bot 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>
2026-09-14 09:30:19 -04:00
dependabot[bot] ee05a595b6 chore(deps): bump github.com/hashicorp/go-getter from 1.8.8 to 1.8.9 (#5272)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.8.8 to 1.8.9.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.8.8...v1.8.9)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.9
  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>
2026-09-11 21:46:07 +00:00
dependabot[bot] 05c6817c07 chore(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2 (#5262)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.1 to 1.83.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.1...v1.83.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 21:28:05 +00:00
Alex Goodman d85c72a08e fix: harden .NET PE/ELF parsing against malformed binaries (#5267)
* fix(dotnet): bound the bundle signature search by the file, not the headers

The single-file bundle marker search sized its buffer from `calculatePEEndOffset` /
`calculateELFEndOffset`, both of which sum user-controlled header fields. A tiny file
declaring 4GB sections drove an 8.6GB allocation, and on the ELF side the sum could
overflow negative and panic in `make`.

The search now clamps its window to the real file length before sizing anything. The PE
and ELF paths had grown two copies of the same find-and-read sequence, so they collapse
into one `bundle.ExtractDepsJSON`, which lets the signature search and the header reader
both go unexported.

Also guards `read7BitEncodedInt` against returning a negative length, which on a 32-bit
`int` would seek a manifest walk backwards and re-read the same bytes per declared file.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(dotnet): stop a truncated version resource from spinning forever

`readIntoStruct` reported a truncated read as success and left the destination struct
zeroed. Its callers advance their loop counters by the offsets it updates, so the string
table loop consumed nothing, advanced nothing, and never reached its length bound: a
version resource whose header claims more content than the blob carries hung the parse.

A truncated read is now an error, and the two places where hitting the end of a blob is
normal say so explicitly. That distinction is what keeps a well-formed resource whose last
child is `VarFileInfo` from losing its `FileVersion` fallback. io.EOF and
io.ErrUnexpectedEOF mean the same thing here, so both take the same path.

The string table walk moves into its own function, since the two new arms put
`parseVersionResourceSection` over the funlen limit.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(dotnet): bound PE resource parsing by what the file actually holds

Resource sizes and RVAs are user-controlled uint32s that sized buffers before anything
checked the file held that many bytes, so a small crafted section could reserve up to 4GB.

The walk now carries the section's reader and its base RVA for its whole lifetime, so
`reader.Size()` is the single authoritative bound and one `offsetOf` helper rejects every
RVA the section does not hold. That replaces the per-node section value the recursion used
to synthesize, whose size argument was only correct by convention.

Bounding the allocations alone turns the OOM into a hang, though: nothing in the format
stops entries from aliasing, so thousands of them may name one fat blob and every offset
still checks out. A 256KB section built that way drove 24GB of allocation over two minutes
with peak memory flat. Capping entry *count* does not help since the cost is per byte read,
so the walk now charges every read against a budget of a few times the section size. Real
binaries come in at about 1x, since a well-formed section's entries partition it.

A resource tree that stops early no longer fails the whole file. It says nothing about the
CLR directory or an embedded deps.json, and dropping the package from the SBOM over one
malformed section is worse than reporting the fields we did get.

Also widens the entry counts before summing them (two uint16s can sum past 0xFFFF and wrap),
and bounds the name length and section header count against the reader for the same reason.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(dotnet): bound resource recursion depth and cap declared sizes absolutely

Review follow-ups on this branch. The headline one is a real crash: the resource
walk had no depth bound, and the `budget` comment claiming depth "falls out for
free" was wrong, since the budget scales with the section while the stack does
not. A chain of directories at distinct RVAs (which the `dirs` set does not
catch) recursed until the goroutine stack gave out. A 13.7MB crafted PE took the
whole process down with `fatal error: stack overflow`, which is unrecoverable,
so unlike every other bug on this branch no caller could log-and-continue past
it. Now capped at 32 levels, versus the 3 the format actually uses.

The other bounds moved from "the file" to a real ceiling. Clamping an allocation
to the bytes remaining in the file is not enough on its own: a mostly empty file
costs almost nothing inside a compressed layer, so a cheap artifact could still
authorize a multi-gigabyte read. Both the PE data directories and the bundle
marker search now cap the same way `elfutil` already does.

Also hardened in the bundle reader:

- `headerOffset` is validated against the file. It comes straight out of the
  file bytes and everything downstream seeks to it, so it was the least
  trustworthy value in there and the only one not being checked.
- a manifest entry count that could not fit in the remaining bytes is rejected
  rather than walked for hundreds of millions of reads.
- a non-positive `searchLimit` falls back to the whole file instead of reading
  nothing. It is only ever an optimization hint, so treating it as authoritative
  let one bogus ELF `p_offset` hide a bundle from us entirely.
- a short read no longer aborts the search. `unionreader` can return fewer bytes
  than the file reports, and the marker may well be in what we did get.

`pe.Read` now reports partial parses instead of choosing between dropping the
package and hiding the problem. It was inconsistent: a malformed resource tree
was non-fatal while an unreadable resource *section* one call away was fatal, and
the non-fatal path only reached a trace log, so the unknowns task could not see
that a file had been cataloged on incomplete evidence. Both now land on a new
`File.ParseErr`, which the dotnet and PE catalogers join into their unknowns and
still keep the package.

Worth noting for changelog purposes: the `isTruncated` guard fixes a
user-visible bug beyond the hardening. A version resource leaving 1 to 5
trailing bytes cuts the next struct in half, which reports as
`io.ErrUnexpectedEOF` rather than `io.EOF`; that used to propagate out and skip
the `VS_FIXEDFILEINFO` fallback, so those binaries lost their version entirely.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(dotnet): treat a zero bundle offset as unbundled, not malformed

The apphost ships the 32-byte bundle signature compiled in with its 8-byte header
offset left as zero, and only `HostWriter.SetAsBundle` fills that slot in when you
publish as a single file. So a zero offset means "never bundled", which is what
every framework-dependent .NET executable looks like.

The bounds work on this branch folded that case in with the hostile ones, so the
most common shape a .NET binary takes started reporting a parse failure. It
surfaces through the PE cataloger rather than the dotnet one, since an apphost
carries no CLR metadata of its own: the package still comes out, with a spurious
unknown attached to it. `main` had this right and said so in a comment; the
regression is on this branch.

Reproduces on a fixture already in the repo. Of the 53 PE files in
`image-net8-app`, `/app/dotnetapp.exe` was the only one reporting anything, and
the whole image is clean again now.

The three outcomes are now distinct, since collapsing any two of them loses
something the caller needs: zero means there is no bundle, an error means we could
not tell, and a positive offset is the answer.

`Test_Read_DotNetDetection` gains the apphost and, more usefully, an assertion
that a well-formed binary reports no partial parse at all. That one line is what
would have caught this, and it now covers all six fixtures. The unit case that
pinned the old behavior asserted the bug, so it flips.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(dotnet): measure readers with the shared helper and report refusals

Two things, both about not re-deriving what the campaign already has.

`internal/file.ReaderSize` exists on `main` for exactly this job, and `upx.go`
already uses it. Its doc comment is the problem statement here almost verbatim: a
declared size is worth nothing until you weigh it against a byte count the reader
can actually back, which it does by reading the last byte. This branch was
hand-rolling `Seek(0, io.SeekEnd)` in three places, one of them reimplementing the
helper's save-and-restore dance verbatim, and then absorbing the reader-lies case
downstream by tolerating short reads.

Adopting it means the bundle reader and `readDataFromRVA` now take the reader
types they were always being handed (`unionreader.UnionReader` and `io.ReaderAt`),
and the reads move to `ReadAt`, which leaves the caller's cursor alone and drops
two seeks. Net effect is a smaller diff than what it replaces, and the case the
`shortReader` fake was invented for is now caught up front instead of tolerated:
the fake was modeling a lie about length, which is not what a short squashfs read
actually does. It now models the real shape (honest length, read stops early) and
a second reader covers the lie.

Three places also still returned "there is nothing here" when what they meant was
"I could not tell", which is the thing `File.ParseErr` was added to stop:

- an ELF that `elfutil` refused to parse read as "not an ELF". `ErrDeclaredSizeExceeded`
  is exported precisely so a refusal can be told apart from a broken file, and the
  caller upstream already turns an error into an unknown.
- a `deps.json` past the 3MB cap returned "". That is the file's entire dependency
  list, so this handed back an SBOM that looked complete and silently was not.
- the marker search clamped at 512MB and then reported no bundle. It cannot claim
  there is no bundle when it declined to look everywhere one could be, so a miss
  after clamping is now an error.

`readSizeRecorder` records `ReadAt` alongside `Read`, or the allocation-bound tests
would have quietly stopped measuring anything.

Also renames `findSignatureOffset` back to `findBundleHeaderOffset`, since it
returns a header offset and reading it as "no signature" is what the last bug was,
and fixes the stale `ExtractDepsJSONFromBundle` doc comment.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test(dotnet): assert allocation sizes directly in the bundle search guards

The PE and ELF search guards asserted the largest read the reader was asked for.
That happened to track the allocation, since the whole buffer goes to ReadAt, but
nothing said so. Both now measure the bytes allocated with the same `measureAlloc`
helper the resource guards use, and keep the read-size check as a separate claim.

Also names the reason a reader can report a size it cannot back: `debug/elf` and
`debug/pe` hand out `io.NewSectionReader(r, 0, 1<<63-1)`, whose `Size()` is nominal
rather than measured, so answering a size is not on its own evidence the bytes are
there. Renames `lyingReader` to `unbackedSizeReader` and regroups the two reader
implementations, which had their methods interleaved.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-11 21:25:10 +00:00
dependabot[bot] 1ca1fa73d3 chore(deps): bump github.com/gpustack/gguf-parser-go (#5274)
Bumps [github.com/gpustack/gguf-parser-go](https://github.com/gpustack/gguf-parser-go) from 0.26.0 to 0.26.3.
- [Release notes](https://github.com/gpustack/gguf-parser-go/releases)
- [Commits](https://github.com/gpustack/gguf-parser-go/compare/v0.26.0...v0.26.3)

---
updated-dependencies:
- dependency-name: github.com/gpustack/gguf-parser-go
  dependency-version: 0.26.3
  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>
2026-09-11 21:11:53 +00:00
dependabot[bot] a675c81143 chore(deps): bump modernc.org/sqlite from 1.57.0 to 1.58.0 (#5278)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.57.0 to 1.58.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.57.0...v1.58.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.58.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>
2026-09-11 21:11:47 +00:00
dependabot[bot] 875daec82b chore(deps): bump github.com/google/go-containerregistry (#5275)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.22.0 to 0.22.1.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.22.0...v0.22.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.22.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>
2026-09-11 21:11:44 +00:00
Juan fb59c4e65c perf(spdx): make findRootPackages linear instead of packages x relationships (#5280)
Decoding an SPDX document into the syft model was quadratic in document
size. findRootPackages (spdxhelpers/to_syft_model.go), which locates the
packages the document DESCRIBES, scanned every relationship for every
package. On a 119MB SPDX JSON document (70,701 packages, 153,301
relationships) that is 1.1e10 comparisons and 91% of all CPU samples; on a
238MB document it is 4.3e10 comparisons and 95%. A 1.3GB document did not
finish in 91 minutes. Everything else on the decode path is map-indexed and
linear, and spdx/tools-golang's own parse is linear (4.5s and 9.3s for the
two documents above). The scan dates from 9480f10c (top-level SPDX package
support, 2023).

The function now indexes the DESCRIBES / DESCRIBED_BY relationships once,
counting how many times each element is described by the DOCUMENT, then
walks the packages once. Semantics are unchanged: results follow package
order, an element described by more than one edge still appears once per
edge (so the existing "exactly one root" check behaves as before), only
edges touching DOCUMENT count, and nil entries are skipped.

Measured on an Apple M1 Max, `syft convert <spdx> -o <format>=<file>`:

  input       target          before (main)      after
  119MB       spdx-json@2.3   68.2s / 1.3GB      10.4s / 1.3GB
  119MB       spdx-json@2.2   70.2s / 1.3GB      12.6s / 1.3GB
  238MB       spdx-json@2.3   291.2s / 2.3GB     20.9s / 2.6GB
  1.3GB       spdx-json@2.3   killed at 91min    115.5s / 13.6GB
  1.3GB       spdx-json@2.2   not attempted      136.3s / 14.1GB
  1.3GB       syft-json       not attempted      102.6s / 14.1GB
  1.3GB       cyclonedx-json  not attempted      114.1s / 13.1GB

Isolating the conversion step (ToSyftModel) from the tools-golang parse:
119MB 72.3s -> 1.5s, 238MB 265.8s -> 3.1s. The remaining decode time and
all of the memory are in tools-golang, which parses the document several
times over; that is a separate, upstream concern.

Output equivalence was checked by converting 10 SPDX documents (the 8 SPDX
fixtures in this repository plus the 119MB and 238MB documents) to
syft-json, spdx-json@2.3, spdx-json@2.2, spdx-tag-value and
cyclonedx-json@1.6 with both binaries and comparing the results with
timestamps and generated ids removed: all 50 pairs are identical.

Tests pin the original semantics (including the duplicate-edge corner case)
and guard the complexity with a 50k x 100k document that must complete in
under 10s; the old implementation takes about a minute on it.

Signed-off-by: juanjsebgarcia <github@juangarcia.co.uk>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-11 21:11:21 +00:00
Alex Goodman c4800b2393 Fix slow pe tests (#5285)
* test(dotnet): speed up pe fixture tests

the pe package took ~90s under -race, nearly all of it loading docker archive
fixtures. two things:

the three bundle fixtures published with `SelfContained=true`, so each `hello.exe`
carried a full .NET runtime (~60MB, 33MB tar). the bundle format is what those
fixtures exercise and it is identical framework-dependent, so they now publish
with `SelfContained=false` (160KB exe, 2MB tar). the race detector multiplies the
layer inflate + diffID hashing by ~15x, which is why the size mattered so much.

`fixtureFile` also loaded the image and built a squashed resolver per table row,
so `image-net8-app` was loaded four times for four files in it. resolvers are now
built once per distinct fixture from the parent test.

87s -> 28.5s under -race, and ~60MB less fixture cache.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(dotnet): read the right file type from V1 single-file bundles

V1 bundles (.NET Core 3.x) predate the `unknown` member at the head of the
manifest file type enum, so their types are all one less than V2+: deps.json is
2, runtimeconfig.json is 3. we were matching on the V2+ value, which silently
matched runtimeconfig.json instead, so every .NET Core 3.x single-file app
reported its runtimeconfig as the embedded deps.json and its dependencies went
unseen.

the existing fixture test asserted the buggy output (it expected
`runtimeOptions`, which only ever appears in a runtimeconfig). the assertions
now pin the framework moniker out of each fixture's real deps.json, so reading
the neighboring file can't pass.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test(dotnet): drop the 64MB jruby binary from the net8-app fixture

the `image-net8-app` fixture wget'd a 64MB jruby release binary at build time to
serve as "a PE that is not a .NET binary", which was 64MB of the image's 65MB.
every test that loads that image pays for it, in both the dotnet cataloger and
the pe package, and it sits in the fixture cache twice.

the same jruby binary already has its own fixture in the binary cataloger
(`image-jruby`), asserted through the same `pe.Read`, so the coverage is not
lost. the negative case here now uses `/app/dotnetapp.exe` -- the native apphost
that is already in the image -- which pairs nicely with the single file case,
same apphost with the app bundled in and CLR evidence present.

also drops a github release download from the fixture build.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-11 15:27:20 -04:00
Alex Goodman eb58f698bd Fix oom when running unit tests in CI (#5284)
* fix oom when testing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* right-size CI test runners

the `r` family and `ram=32+128` were sized for the tmpfs footprint rather
than the workload. with the caches back on disk the build+test peak fits in
16GB, so move to `m`/`c` at `ram=16+32`.

- ~27% cheaper per instance-hour on spot, and a much deeper pool to schedule
  against (~20 x86 types vs 7)
- `cpu=4+8` with `ram=16+32` lets price-capacity-optimized opportunistically
  land an 8 vCPU box when it's cheap
- plain `volume=100gb` replaces the provisioned throughput. a 4 vCPU instance
  caps near 150MB/s of EBS and gp3's free baseline is already 125MB/s, so the
  override bought nothing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-11 14:58:30 -04:00
Alex Goodman de98400c39 fix(test): drop apt from java-no-main-package fixture (#5283)
the base image is digest-pinned to a bullseye snapshot whose InRelease has
expired, so `apt-get update` now exits 100 on any cold fixture build. rather
than disable Check-Valid-Until (which just delays the next break), skip apt
entirely:

- `less` was never used and `unzip` already ships in the base image
- rezip with `jar cfM` from the base JDK instead of installing `zip`

the `-M` matters here: this fixture intentionally has no META-INF/MANIFEST.MF,
and plain `jar cf` would generate one.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-11 14:44:45 -04:00
Alex Goodman 9e520d1442 fix(test): use immutable URL for dpkg archive fixture (#5282)
`archive.ubuntu.com/ubuntu/pool/main` only keeps the current version of a
package, so the pinned `zlib1g 1:1.3.dfsg-3.1ubuntu2.1` URL started 404ing.
`curl` without `-f` wrote the error page into `zlib1g.deb` and exited 0, so
the fixture image built clean and just contained a non-deb, failing
`TestDpkgArchiveCataloger` only when the fixture cache was cold.

Now points at the launchpad `+files` URL, which is immutable. Also added `-f`
to the other fixture Dockerfiles that curl a pinned artifact so the next bit
of URL rot fails the build instead of quietly poisoning a fixture.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-11 13:45:16 -04:00
dependabot[bot] 3ec2e33d73 chore(deps): bump github.com/klauspost/compress from 1.19.2 to 1.20.0 (#5276)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.19.2 to 1.20.0.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.19.2...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.20.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>
2026-09-11 14:34:14 +00:00
dependabot[bot] 40a4ec72b9 chore(deps): bump golang.org/x/time from 0.15.0 to 0.16.0 (#5273)
Bumps [golang.org/x/time](https://github.com/golang/time) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/time/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/time
  dependency-version: 0.16.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>
2026-09-11 13:57:52 +00:00
dependabot[bot] 268897ca82 chore(deps): bump zizmorcore/zizmor-action from 0.6.2 to 0.6.3 (#5270)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.6.2 to 0.6.3.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/3dc1ecc9bcb9e94e9b2c709687979e1298497054...70fb788f84895a7701f5643d103d587e460b5c99)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.3
  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>
2026-09-11 13:53:48 +00:00
dependabot[bot] 031de8ddab chore(deps): bump golang.org/x/mod from 0.40.0 to 0.41.0 (#5271)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/mod/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.41.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>
2026-09-11 13:34:10 +00:00
Alex Goodman 1d25dfe4da fix(golang): bound allocations from UPX-packed binary headers (#5195)
* fix(golang): bound UPX decompression by what the input can justify

A UPX block's `sz_unc` was passed straight to `make([]byte, n)`, so four bytes of
attacker input spanned the full uint32 range. A ~300KB file could drive multi-GB
resident memory, which Go reports as a fatal runtime OOM that no `recover` on this
path can contain. Syft parses binaries out of arbitrary images, so that is reachable
from any scan.

Two bounds do the work, and UPX's own invariants make them exact:

- all blocks together reconstruct `p_filesize`, so a running remainder caps the sum.
  Bounding only per-block would leave the total at (block count x original size), so
  the remainder is the part that actually closes it
- `p_filesize` sizes the output buffer, so it is capped both absolutely and against
  the size of the file on disk. The absolute cap alone left a ~60 byte header able to
  claim 500MB; the ratio alone cannot work either, since LZMA encodes a run of N
  equal bytes in O(log N) and a `go:embed` of 120MB of zeros legitimately packs 209x

The output buffer is also allocated on the first block that survives validation
rather than up front, so a header claiming a large size with nothing decodable behind
it costs nothing.

Alongside that, several ways the block loop could be steered off its own buffer:

- `parseELFPTLoadOffsets` bounds-checked program headers with `phStart+phentsize >
  len(buf)`, which overflows for a `p_offset` near 2^64 and lets an out-of-range entry
  through into a slice index. Switched to the subtraction form, and a `phentsize`
  shorter than an ELF64 program header is rejected since the reads use fixed offsets
- block placement had the same overflow shape, and on failure it skipped the copy and
  fell through. `outputOffset` derives from the rejected offset, so a `p_offset` at the
  uint64 ceiling wrapped it to 0 and the next block landed on the reconstructed ELF
  header, still returning success. Out-of-range placement now ends the block chain,
  keeping the blocks already placed since those often carry `.go.buildinfo`
- the UPX 2-byte LZMA header carries `lc`/`lp` as nibbles and `pb` as three bits, so
  they can hold values LZMA does not permit. Folded into the props byte they wrap
  (`lc=15, lp=15, pb=7` gives 465, which truncates to 209) and the stream mis-decodes
  instead of failing
- blocks decode directly into a slice of the output buffer instead of a per-block
  buffer that is then copied, so one block no longer doubles peak memory
- the block count is capped: the size budget alone still allows millions of 12-byte
  blocks, each spinning up an LZMA reader

Verified against the `image-small-upx` fixture, a real `upx --best --lzma` binary:
four blocks, max `sz_unc` equal to `p_blocksize` exactly, blocks summing to 0.68 of
`p_filesize`, and a 1.96x expansion against the packed file, so every bound holds with
headroom on genuine output.

Note `p_blocksize` is deliberately not used as a ceiling on `sz_unc`. It adds nothing
the remainder does not already cover, and real output sits at exactly `p_blocksize`,
so it would run with zero headroom against a value the format does not promise.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(golang): report UPX binaries we cannot unpack as unknowns

A packed Go binary we fail to unpack means its packages are silently missing from the
SBOM. `getBuildInfo` checked the decompression error for nil and discarded it, so a
rejected file surfaced only the original `buildinfo.Read` error, and that one is
deliberately silenced because it is usually just "not a Go binary".

`decompressUPX` now marks the cases worth reporting with `errUPXDecompress`: it got
past the header and the method dispatch and still could not unpack the file. Everything
else stays quiet, which matters more than it sounds. `upx` defaults to NRV2B unless
`--lzma` is passed and only LZMA is implemented here, so treating any decompression
failure as reportable would attach a golang-cataloger unknown to every packed non-Go
binary in an image. Making the reportable case opt in rather than the quiet case a
growing list also means a guard added later is silent by default.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor(golang): drop the vendored xcoff parser

The only thing the golang cataloger read from it was `TargetMachine`, which is the same
two magic bytes `getGOARCHFromBin` had already matched on to dispatch there. So a full
XCOFF walk (string table, symbol table, every relocation table) ran to recover a value
that was already in hand.

`getGOARCHFromBin` reads those two bytes directly now. That is also strictly more
correct: `xcoff.NewFile` bailed with "no symbol table" when `symptr == 0`, so a stripped
XCOFF binary reported no arch at all.

Fixtures move to `testdata/xcoff/`, since they are no longer a package's own testdata.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* feat(internal/file): report a reader's real size behind one helper

Bounds get written as "does this claim exceed what the reader actually holds", and the
subtlety is that a reader's own answer cannot be trusted: an `io.SectionReader` reports
the nominal length it was built with, so one constructed with `1<<63-1` will happily
claim to hold 8EB. Confirming the last byte is readable is what separates a real size
from a nominal one.

Returns `(int64, bool)` rather than a bare size, since there are five ways to not know
and collapsing them to 0 makes every caller reinvent "0 means stop bounding".

The doc also names the wrapper trap: a type embedding `io.ReaderAt` as an interface
promotes only `ReadAt`, so it answers no size at all however large the reader beneath it
is, and a bound written against it silently does nothing.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(golang): bound every section a reader can expand, not just the name table

`CheckSectionNameTable` bounds the one section `elf.NewFile` expands while parsing, which
is enough for a caller that only parses. It is not enough for one that goes on to read
sections: those are expanded lazily, so a 260KB ELF declaring a compressed `.symtab`
drove 1.3GB of allocation through `goversion`, which opens the file with `debug/elf`
inside its own package and cannot be routed through `elfutil.NewFile`.

`CheckAllSections` is that gate, and is deliberately named as the superset so the
relationship to the narrow one is structural rather than documented.

Also exports `ErrDeclaredSizeExceeded`. A refusal here costs the SBOM a package, so
callers report it as an unknown, and matching on an error string to decide that is not
something to build a reporting policy on.

A ruleguard rule now flags `buildinfo.Read` and `version.ReadExeFromReader` outside
`scan_binary.go`. Both open `debug/elf` internally and are only bounded there by the
wrappers that gate the reader first, which the existing rule on `elf.NewFile` could not
see through.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* feat(internal/spillbuf): sparse offset-addressed buffer that spills to disk

For output that arrives out of order, at offsets the input itself declares: a
decompressor placing extents, an archive rebuilding a file from chunks. A plain `[]byte`
cannot do that without either pre-sizing to a length the input claims or growing to the
furthest offset it names, and both hand a hostile input an allocation knob.

The load-bearing property is that it reports only what it actually stored. `Size` is the
contiguous run written from offset zero and a read past it is `io.EOF`, never the zeros
an unwritten region would hand back for free. Sparse storage serving holes as real
content is what lets a write offset stand in for output nothing produced, so anything
sizing an allocation against this reader (`saferio` in `debug/elf` does exactly that)
stays bounded by work actually done.

`FirstGap` answers where the next write fits, so callers filling holes do not need the
extent bookkeeping and cannot re-derive that rule wrongly, and it only reports gaps
lying entirely inside `[0, within)`. The extent type is unexported for the same reason.

The rest of the contract worth stating: reads and writes after `Close` fail with
`os.ErrClosed` rather than looking like an empty buffer, and a write that fails leaves
the buffer unchanged.

Memory is bounded by the limit rather than by how much is written: filling 1MB and
filling 64MB cost about the same. What lands on disk past that limit is the caller's
bound to set, not this package's. Growth is geometric and capped, because sizing the
tier to exactly what each write needs is quadratic once a caller streams through it in
small chunks.

The golang cataloger is the only consumer today, and nothing else in the tree has this
shape.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(golang): rebuild UPX binaries through spillbuf and read the reconstruction everywhere

Two amplification paths were still reachable. A 128 byte input allocated 16MB by
trusting `sz_cpr` before weighing it against the bytes the file actually holds, and the
reconstruction was assembled in a `[]byte` sized from the header's declared original
size, so a 301KB input allocated 300MB.

The reconstruction is a `spillbuf.Buffer` now, which is what makes the second one go
away: blocks are placed at the offsets the file declares, nothing is pre-sized from a
claim, and the reader's length is the contiguous run actually rebuilt. That last part is
the invariant everything downstream leans on. A reader reporting the declared size would
serve the holes between placed blocks as zeros it never stored, and a 64 byte block
parked at `p_filesize-64` then made a 300KB input hand out a 64MB reader: measured, that
drove 269MB through `getBuildInfo` and returned nothing, silently.

Placement and storage are separate concerns. `upx.go` places blocks at the offsets the
file declares and asks the buffer how much was rebuilt and where the next gap is; it
knows nothing about temp files or memory limits.

The remaining bounds are ratios against the input wherever they can be, since an
absolute ceiling only ever drops a large legitimate binary. The one absolute left is on
`p_filesize`, because padding an input is nearly free, and the LZMA dictionary is paid
for in its own block's `sz_cpr`.

Interface change: `scanFile` takes a context and hands back the reconstruction for each
binary that turned out to be packed, and the caller owns it. Everything after the build
info read (crypto settings, arch, symbols, the version scan) reads it, so a packed
binary no longer reports its packages with no symbols at all.

Also narrows what gets reported. This cataloger runs on every executable in an image, so
reporting every `getBuildInfo` error attached an unknown to every corrupt ELF, odd PE and
non-Go Mach-O slice in it. Now only four gaps, the ones that cost the SBOM something
syft could have had: a packed file we could not decode (`errUPXDecompress`), a header
the size bounds refused (`errUPXSizeRefused`), a reconstruction that came up short
(`errUPXPartial`), and an ELF expansion `elfutil` declined
(`ErrDeclaredSizeExceeded`). An unimplemented UPX method stays quiet on purpose: `upx`
defaults to NRV2B and only LZMA is implemented here, so reporting it would attach an
unknown to most packed non-Go binaries in an image.

That filtering is a new pattern. No other cataloger picks which of its errors become
unknowns, and the reason this one does is that it runs on every executable in an image
rather than on files a glob already selected.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* chore: ignore local spec/ planning notes

`/specs` was already ignored; `/spec` is the same thing under the singular name and was
not, so working notes landed in commits.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor(golang): name what the unpack hands back, not where it stores it

the scan's signatures said `*spillbuf.Buffer` all the way down, which told
every reader below the scan that it cares whether the reconstruction lives in
memory or on disk. it does not. two interfaces instead, each naming only what
its side actually uses:

- `unpackedContents` for the consumers: `ReaderAt`, `Closer`, and a `Size` that
  is the contiguous run rebuilt from zero
- `blockSink` for the decoder, which additionally needs `WriterAt` and
  `FirstGap` to place blocks

this costs one thing worth flagging. `Close` on a nil `*spillbuf.Buffer` was
nil-safe, so callers just deferred it; widened into an interface, a nil pointer
becomes a non-nil interface and the same call panics. `closeUnpacked` absorbs
that, and it is now the only way an owner releases contents. `readerFor`,
`seekerFor` and `closeUnpacked` are the three places that resolve the nil.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* docs(elfutil): say that CheckAllSections is the decompression-bomb gate

the doc led with "bounds every section syft can drive debug/elf into
decompressing", which describes the mechanism without ever naming what it is
defending against. a reader landing on the call site could not tell that this
is the decompression-bomb check.

state the attack instead: a compressed section header declares its own
decompressed size, debug/elf believes that number and allocates it on open, and
nothing forces it to match what the compressed bytes actually yield. the 260KB
ELF that drove 1.3GB of allocation now lives in the doc rather than only at the
one call site that happened to mention it.

comments only.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* docs(golang): move the overflow note onto the bounds test it describes

the "subtraction form" note sat above the phStart assignment with ten lines of
wrap analysis between it and the `if` it was talking about, so the subtraction
it names reads as missing. it is `hdrLen-phStart < phentsize`.

move it onto that test and name the form it is avoiding (`phStart+phentsize >
hdrLen`, which can overflow past the buffer end) so the referent is not several
paragraphs away.

comments only.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* chore(golang): drop the internal/ README left over from xcoff

that README existed to record where the vendored xcoff package came from and
that Go keeps it internal, which is a provenance note worth having while
third-party code sat there. dropping xcoff took the reason with it, and
repurposing it to point at gotestdata just made it redundant: gotestdata has
its own README covering why it is under internal/, why it is not testdata/, and
what belongs in each.

internal/ now holds one self-documenting directory.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-10 09:21:19 -04:00
dependabot[bot] 0c049806bd chore(deps): bump github.com/containerd/containerd/v2 (#5265)
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.3.4 to 2.3.5.
- [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.4...v2.3.5)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.3.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 02:29:44 +00:00
Alex Goodman 9addda3da5 fix(test): pull elf fixture build tools from archive.debian.org (#5264)
bullseye went EOL and deb.debian.org dropped it, so the elf executable
fixture image stopped building and took the whole unit test job with it
(fixture generation runs before any test does). main has been red on this
since 2026-09-08.

two separate breakages, both handled here:

- the base repo moved to `archive.debian.org`, whose Release files are
  permanently expired, so `Acquire::Check-Valid-Until=false` is required

- `bullseye-security` is worse off: its index is still served but every
  package in the pool behind it 404s, and it has not landed on the archive
  yet. dropping it is the only option right now. these are throwaway
  fixture build tools, so base bullseye versions are fine.

verified by rebuilding the image and running the fixture `verify` target,
which still reports `PASS: all security features accounted for`.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-09 10:53:58 -04:00
anchore-oss-update-botandanchore-oss-update-bot 767c83d513 chore(deps): update CPE dictionary index (#5256)
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>
2026-09-08 11:22:39 -04:00
dependabot[bot] 392539dc28 chore(deps): bump github.com/CycloneDX/cyclonedx-go (#5252)
Bumps [github.com/CycloneDX/cyclonedx-go](https://github.com/CycloneDX/cyclonedx-go) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/CycloneDX/cyclonedx-go/releases)
- [Commits](https://github.com/CycloneDX/cyclonedx-go/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: github.com/CycloneDX/cyclonedx-go
  dependency-version: 0.12.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>
2026-09-04 13:30:25 +00:00
dependabot[bot] 3dd674c53b chore(deps): bump anchore/sbom-action from 0.24.0 to 0.24.2 (#5250)
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.24.0 to 0.24.2.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/e22c389904149dbc22b58101806040fa8d37a610...3ad7283483fc7af8ff2b4ea19663c2d5ca935e26)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-version: 0.24.2
  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>
2026-09-04 13:29:14 +00:00
dependabot[bot] fd7517bbba chore(deps): bump github.com/google/go-containerregistry (#5251)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.21.9 to 0.22.0.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.21.9...v0.22.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.22.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>
2026-09-04 13:29:06 +00:00
Rez Moss c935b71c9e fixed java-archive-cataloger returns unknown ver for uber-jars , fixed #5163 (#5164)
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-09-03 16:09:20 -04:00
dependabot[bot] 77031752fa chore(deps): bump google.golang.org/grpc from 1.83.0 to 1.83.1 (#5245)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.0 to 1.83.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.0...v1.83.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 08:27:04 +00:00
Vaibhav Srivastava 12d5544b2a docs: fix typo implmentation -> implementation (#5243)
Signed-off-by: Vaibhav Srivastava <vaibhavsri1712@gmail.com>
2026-08-31 17:21:05 +00:00
anchore-oss-update-botandanchore-oss-update-bot cc13d1dbf1 chore(deps): update CPE dictionary index (#5244)
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>
2026-08-31 13:03:10 -04:00
anchore-oss-update-botandanchore-oss-update-bot af937d74c0 chore(deps): update tool versions (#5242)
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>
2026-08-29 10:24:02 +00:00
dependabot[bot] 4017979670 chore(deps): bump docker/setup-buildx-action from 4.2.0 to 4.3.0 (#5237)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/bb05f3f5519dd87d3ba754cc423b652a5edd6d2c...37fe631027851001ddb9b187196cc803df7f5f0e)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.3.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>
2026-08-28 13:33:27 +00:00
dependabot[bot] 82d273fac5 chore(deps): bump github.com/gpustack/gguf-parser-go (#5239)
Bumps [github.com/gpustack/gguf-parser-go](https://github.com/gpustack/gguf-parser-go) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/gpustack/gguf-parser-go/releases)
- [Commits](https://github.com/gpustack/gguf-parser-go/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: github.com/gpustack/gguf-parser-go
  dependency-version: 0.26.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>
2026-08-28 13:32:35 +00:00
dependabot[bot] 1417c1989b chore(deps): bump modernc.org/sqlite from 1.56.0 to 1.57.0 (#5238)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.56.0 to 1.57.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.56.0...v1.57.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.57.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>
2026-08-28 13:31:08 +00:00
anchore-oss-update-botandanchore-oss-update-bot 91a0032987 chore(deps): update anchore dependencies (#5085)
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>
2026-08-27 12:25:09 -04:00
Ankush Pathak f91bf4528b fix: correct Apache Derby group ID in purl generation (#5090)
Signed-off-by: Ankush Pathak <ankush.pathak@chainguard.dev>
2026-08-27 10:55:31 -04:00
anchore-oss-update-botandanchore-oss-update-bot c5fc6998d3 chore(deps): update CPE dictionary index (#5221)
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>
2026-08-27 09:30:58 -04:00
Pujitha PaladuguandPujitha Paladugu d3734ddeb2 fix(binary): detect grafana security-patch release versions (#5213)
---------
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
2026-08-27 04:20:09 +00:00
Matt Van HornandChristopher Phillips bf82010f3c fix(lua): skip rockspec with no package name (#4825)
---------
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-08-21 17:36:47 -04:00
Pujitha PaladuguandPujitha Paladugu 7ca1f22395 fix(dotnet): correct inverted dependency-of relationship direction in packages.lock.json parser (#5143)
Fixes #5125

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
2026-08-21 18:53:45 +00:00
Sueun ChoandChristopher Phillips 93cf89363b fix(rpm): keep the epoch when parsing RPM manifest packages (#5201)
---------

Signed-off-by: Sueun Cho <sueun.dev@gmail.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-08-21 14:47:23 -04:00
dependabot[bot] 34ef7dcbfe chore(deps): bump golang.org/x/mod from 0.39.0 to 0.40.0 (#5208)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/mod/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.40.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>
2026-08-21 13:50:49 +00:00
dependabot[bot] 29edf90038 chore(deps): bump github.com/hashicorp/go-getter from 1.8.6 to 1.8.8 (#5207)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.8.6 to 1.8.8.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.8.6...v1.8.8)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.8
  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>
2026-08-21 13:50:46 +00:00
dependabot[bot] 766907e6ed chore(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.0 (#5206)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.11.1 to 1.12.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.11.1...v1.12.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.12.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>
2026-08-21 13:29:57 +00:00
dependabot[bot] e820f2d21a chore(deps): bump golang.org/x/tools from 0.48.0 to 0.49.0 (#5205)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.48.0 to 0.49.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.48.0...v0.49.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.49.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>
2026-08-21 13:29:25 +00:00
Alex Goodman 39c9c690df fix: move image hardlink handling upstream during image indexing (#5196)
* chore(deps): pin stereoscope to the hardlink-inode-adoption branch

temporary pin to anchore/stereoscope#670 so the hardlink changes can be
exercised end to end. needs re-pinning to a release tag before merge.

pulls transitive bumps along with it: docker/cli, docker/go-connections and
gabriel-vasile/mimetype.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: drop the image resolver hardlink workaround

stereoscope now adopts a hardlink's target at index time, so a hardlinked name
arrives already described as a regular file with the target's size, mime type
and content. the resolver-side fix from #5029 sat on top of that doing the same
job a second time, gated on a file type that no longer shows up, so both
`resolveHardLinkTarget` implementations and the extra tree walk they cost on
every `FilesByPath` are gone.

`file.NewVirtualLocationFromImage` stays as-is. it is exported and syft is v1,
so it keeps working for anyone using it, it just has no callers in syft now.

this picks up `FilesByMIMEType`, which #5029 explicitly could not fix, so
hardlinked names now reach mime-driven catalogers. on images built around
multi-call binaries (busybox and friends) that is a lot more file and executable
entries than before; distro and toolchain images move by about one entry.

one shape regresses: a hardlink stereoscope could not adopt (a link name that is
absent, empty, names a directory, names another un-adopted link, or points into
a lower layer) keeps `TypeHardLink`, and syft goes back to collapsing it onto
its target's path. no mainstream builder emits the cross-layer case.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump stereoscope to main

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-20 20:17:41 +00:00
addielarue c4597a08a2 fix(file-resolver): don't abort scan on inaccessible symlink target (#5170)
Signed-off-by: addielarue <bhasmerutika@gmail.com>
2026-08-20 16:16:45 -04:00
Christopher Angelo Phillips 236755a6b1 fix(binary): gzip classifier & GNU gzip identity string (#5202)
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-08-20 16:02:14 -04:00
Alex Goodman 168eb64235 fix(javascript): pass a yaml node to the pnpm v5 collision test (#5203)
#5188 changed `pnpmV6LockYaml.Parse` to take a `*yaml.Node` and #5175 added
this test against the old `[]byte` signature. both were green on their own
branches, so main does not compile.

the v9 and v6 cases in the same test already go through the `yamlDocument`
helper, this is just the v5 one that was missed.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-20 16:01:52 -04:00
Xenira 490732aa58 fix(cpe): fix generated cpe for rust packages
---------
Signed-off-by: Xenira <1288524+Xenira@users.noreply.github.com>
2026-08-20 15:01:24 -04:00
Sueun Cho fd4796b0d8 fix(python): keep epoch-pinned requirements in the SBOM (#5161)
parsePinnedVersion rejected any version constraint containing "!" to
skip the "!=" exclusion operator. PEP 440 epochs use "!" as well
(e.g. "1!2.0.0"), so an exact pin like "pkg == 1!2.0.0" was treated as
unparseable and returned "". parseRequirementsTxt drops a requirement
whose version resolves to "", so the package never made it into the
SBOM at all in either the pinned or guessed path.

Check for the "!=" operator as a substring instead of the bare "!"
character, which leaves the epoch separator alone.

Signed-off-by: Sueun Cho <sueun.dev@gmail.com>
2026-08-20 13:06:17 +00:00
78f39ee3a5 fix(javascript): strip underscore peer-dep suffixes from pnpm v5 lockfile versions (#5175)
pnpm v5 lockfiles encode resolved peer dependencies as an
underscore-delimited suffix of the dependency path, e.g.
/acorn-jsx/5.3.2_acorn@8.8.0 or a hashed form like
/webpack-cli/4.10.0_fzn43tb6bdtdxy2s3aqevve2su. Only the v6+
parenthesized form was stripped, so for v5 lockfiles the suffix leaked
into reported versions and PURLs, and peer variants of the same
name+version were not deduplicated.

Strip the suffix on the v5 code path (version < 6.0) wherever a version
is extracted: top-level dependencies values, packages keys, and
per-package dependencies map values. Only values with a leading digit
are stripped so link:/file:/git specifiers are unaffected.

Fixes #5174

Signed-off-by: codeAnqiang-ma <273298913+codeAnqiang-ma@users.noreply.github.com>
Co-authored-by: codeAnqiang-ma <273298913+codeAnqiang-ma@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 08:40:18 -04:00
5c6cf08a8c fix(binary): detect multi-arch ingress-nginx (#5179)
* fix(binary): detect multi-arch ingress-nginx

Add v1.9.6 ARM64, ARMv7, and s390x fixtures from the published controller image and match their version marker against the nearby Go build version.

Assisted-by: OpenAI Codex
Signed-off-by: Shurong Cao <170531907+CAOShurong@users.noreply.github.com>

* fix(binary): match ingress-nginx release marker on all arches

The release is injected with `-ldflags -X`, so it lands in its own aligned,
NUL-padded data symbol. Only that padding is portable -- the surrounding bytes
are an arch-specific float constant pool, which is why the existing matchers all
anchor on incidental junk like `$a` and `S=v<y5` and only ever worked on amd64.

Matching `v<version>` followed by two NULs finds exactly one hit in every v1.9.6
binary (amd64, arm, arm64, s390x) and in all nine amd64 releases already under
test. Two NULs matter -- with one, s390x matches a vendored `v1.19.0` earlier in
the file and reports the wrong version.

This also drops the ~8KB wildcard window the marker previously needed, so the new
fixtures are ordinary 369 byte snippets instead of 8.4KB ones.

Two things in the fixture tooling had to move for that to work:

- `config.yaml` pinned the multi-arch *index* digest for all four platforms, but
  extraction runs `docker create <ref>` with no `--platform`, so every entry
  resolved to whichever image was pulled last. `make download` failed outright on
  `linux/arm/v7`, since the registry labels that platform `armv7` and docker
  normalizes it to `arm`. Now pinned per-platform, matching the redis entry.

- `write-snippet` only recognized EM_X86_64 and EM_AARCH64, so anything else
  landed in a `linux-unknown-<hex>` directory and had to be placed by hand.

Verified against the real binaries with `-must-use-original-binaries`.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(binary): put non-amd64 snippets where the tests look for them

`write-snippet` used to fall back to `unknown-<hex>` for any ELF machine it
didn't recognize, so three snippets were sitting in directories nothing reads:

    helm/3.12.0/linux-unknown-454d5f53333930      (hex of "EM_S390")
    helm/4.1.4/linux-unknown-454d5f53333930
    redis-server/7.2.5/linux-unknown-454d5f333836 (hex of "EM_386")

The test table asks for `linux-s390x` and `linux-386`, so those three cases were
quietly falling through to downloading the full binaries instead. Renamed to
match, and taught the tool about EM_386 and EM_RISCV so it stops producing dead
directories (there is an existing `linux-riscv64` snippet it also couldn't have
written).

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Shurong Cao <170531907+CAOShurong@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Shurong Cao <170531907+CAOShurong@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-19 18:15:15 +00:00
Hamody WeandAlex Goodman 8bb3b5eed1 fix(javascript): read every document of a pnpm-lock.yaml stream (#5188)
* fix(javascript): read every document of a pnpm-lock.yaml stream

pnpm-lock.yaml can be a multi-document YAML stream. pnpm keeps config
dependencies and the pinned package-manager version in a leading document
and the project's dependency graph in the next one. That layout has
shipped since pnpm 11 for projects using config dependencies, and pnpm 12
records the package-manager pin by default, so two documents is now the
common case.

The cataloger called yaml.Unmarshal, which reads the first document only.
On such a lockfile the SBOM contains pnpm's own release binaries and none
of the project's dependencies. Both documents declare the same
lockfileVersion and the result is well-formed and non-empty, so nothing
signals that it is wrong.

Split the stream and parse each document, merging the results. A
single-document lockfile yields exactly one document, so its behaviour is
unchanged. A document that omits lockfileVersion falls back to the
version from the first one rather than being dropped.

Fixes #5168

Signed-off-by: hamodywe <iosapk.org@gmail.com>

* refactor(javascript): stream pnpm-lock.yaml documents instead of buffering

The multi-document fix read the whole lockfile, decoded it to a `yaml.Node`
tree, re-marshaled every document back to `[]byte` and then re-parsed each one
twice. `yaml.Node` already has a `Decode` method, so the decoder can walk the
stream straight off the reader and hand each document's node to the parser.

On a 4.8MB single-document lockfile that takes total allocations from 1069MB to
250MB, which is also below where things sat before multi-document support. It
drops the `io.ReadAll` and its `noUnboundedReads` suppression, and puts pnpm in
line with every other YAML cataloger in the tree, all of which already stream.

`pnpmLockfileParser.Parse` now takes a `*yaml.Node` rather than `[]byte`. It is
package-private with two implementations and one production call site.

Documents accumulate into a single map keyed by `name@version`, so the stream
follows one collision rule rather than two: the last entry to appear wins,
which is what already happens within a document. That replaces the separate
first-wins-with-fill-in merge pass. A conflicting integrity between documents
is now traced rather than resolved silently.

Three behaviour fixes come with it:

- an empty or comment-only leading document no longer takes down the whole
  file. It decodes to a null node, not a zero-kind one, so the old guard never
  fired and the stream failed with `invalid lockfile version ""`.
- a malformed document no longer discards the documents that already parsed.
  It is reported through `unknown` instead, matching how the rest of the
  cataloger reports partial results.
- parse errors carry the document index, and their line numbers again point at
  the user's file rather than into a re-marshaled buffer.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* chore(javascript): trim the duplicated pnpm multi-document comment

The six-line explanation of why a pnpm lockfile can hold two documents sat
verbatim in both the parser and its test. Keep it next to the code, where
someone debugging will be, and leave the test pointing at it.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: hamodywe <iosapk.org@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-19 18:09:51 +00:00
Oleksandr Vodotiiets 21cca30e5b perf: make orderedIDSet membership checks constant time (#5178)
orderedIDSet.add scanned the entire existing slice for every inserted ID,
so building a Collection was quadratic in the number of packages sharing an
index key. This is most visible in idsByType: package types are few, so that
index accumulates nearly every package in the SBOM into a single set, and
each Add rescans it.

Keep the ordered slice (ordering and dedup semantics are unchanged) and add
a hash index for membership checks. The index is only built once a set grows
beyond a small threshold, so the many single-element sets held by idsByName
and idsByPath do not pay for a map, while the large idsByType sets get
constant-time lookups.

BenchmarkCollectionAdd (Apple M2 Pro), packages sharing one type:

    packages   before        after       speedup
    1,000        1.37 ms      0.76 ms      1.8x
    10,000     100.31 ms      9.80 ms     10.2x
    50,000   2,423.79 ms     34.46 ms     70.3x

The tradeoff is memory: sets past the threshold allocate an index map,
about 14% more bytes at 50,000 packages, with allocation counts unchanged.

Signed-off-by: Oleksandr Vodotiiets <61548316+avodotiiets@users.noreply.github.com>
2026-08-19 18:01:20 +00:00
Keith Zantow ed76e96749 fix: panic when scanning squashfs symlinks (#5119)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-08-19 13:13:42 -04:00
Luan Taraschi ab508169e6 fix(golang): skip remote license lookup for standard library module paths (#5192)
With search-remote-licenses enabled, every module name went to the proxy,
including toolchain binaries whose main module is synthesized from the
package path, such as cmd/cgo. The proxy answers 404, and the direct
fallback then treats the path as a repository host, producing requests
like https://cmd/cgo/info/refs?service=git-upload-pack.

Reuse isStandardImportPath, already in this package, to skip the remote
search for paths whose first element carries no dot. Those are never
publishable module paths, so neither a proxy nor a repository can resolve
them.

Fixes #3149

Signed-off-by: Luan Taraschi <130802253+luantaraschi@users.noreply.github.com>
2026-08-19 10:11:10 -04:00
anchore-oss-update-botandanchore-oss-update-bot 360dbc04aa chore(deps): update CPE dictionary index (#5189)
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>
2026-08-18 11:44:00 -04:00
Alex Goodman 58a033f924 Prevent unnecessary allocations when parsing compressed ELF sections (#5187)
* fix(elf): bound compressed ELF section reads

`debug/elf` takes a section's decompressed size from that section's own
compression header, and a highly compressible stream really does deliver the
bytes that header promises, so `internal/saferio` does not help: it faithfully
allocates every one of them. A 2MB input file drives `elf.NewFile` to allocate
over 10GB and return no error, which is a fatal OOM rather than a recoverable
panic.

Which sections get read is not up to the caller. `elf.NewFile` always reads the
section-name string table, and `File.Symbols` reads `.symtab` plus whatever
section its `Link` field points at, so being selective about sections is not
enough to avoid it.

New `elfutil.NewFile` is a drop-in for `elf.NewFile` that rejects a declared
decompressed size over 128MB. Every production call site goes through it, and a
ruleguard rule keeps the next one from going direct.

The check runs in two parts, since `debug/elf` expands sections at two different
times. The section-name string table is the only one `elf.NewFile` expands
itself, so it is checked against the raw bytes before the call; everything else
is expanded lazily by `(*Section).Open` and is checked after the parse, where
names, types and decompressed sizes are already resolved.

Only the sections syft can actually reach are bounded, which keeps the guard
from costing real binaries. DWARF is excluded since nothing calls `File.DWARF`,
so a large compressed `.debug_info` no longer skips the whole file, and sections
`debug/elf` will not decompress anyway (`SHF_ALLOC`, `SHT_NOBITS`) are left
alone. The legacy `.zdebug` form is matched on the section name the way
`debug/elf` gates it rather than on the `ZLIB` magic, so an ordinary section
starting with those four bytes is not mistaken for a compressed one.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(elf): gate debug/buildinfo behind the compressed-section check

`debug/buildinfo.Read` opens ELF files with `debug/elf` itself, and `elf.NewFile`
expands the section-name string table as it parses, so the golang cataloger was
still reachable by the same bomb `elfutil` exists to stop. A 261KB fixture drove
1.4GB of allocation through `buildinfo.Read` and returned no error.

`elfutil.CheckSectionNameTable` is now exported for that case: callers that cannot
use `NewFile` because the `debug/elf` call is made for them inside another package.
Both `buildinfo.Read` call sites go through it, including the UPX-decompressed one.

Also corrects claims that did not hold up:

- the package doc's 2MB-to-10GB figure is not reachable with zlib (~1000:1), so it
  now carries the measured 510KB-to-2.6GB, and names zstd's 32767:1 since that is
  what makes the small inputs possible

- `.go.buildinfo` was listed as a hot-path section elfutil covers, but it is read
  through `debug/buildinfo` and never touches `Section.Data`

- the graalvm comment claimed routing size rejections away from `*elf.FormatError`
  improved reporting; both branches are skipped by the caller and only the
  FormatError branch logs, so it did the opposite

- `sharedLibraries` logged short and truncated files as real ELF failures, since
  `debug/elf` returns a bare `io.EOF` rather than an `*elf.FormatError` for those

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* added test comments around the negative cases

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* additional tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* better decomposition and comments

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use a less brittle constant for error detection

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-14 20:08:58 +00:00
Alex Goodman 04a6fa1b41 fix(unionreader): honor the io.ReaderAt contract in readerAtAdapter (#5186)
`readerAtAdapter.ReadAt` seeks and then issues a single `Read`, which breaks the
`io.ReaderAt` contract in both directions against the squashfs reader it exists
to wrap:

- `squashfs.File.Read` copies against the decompressed block length but advances
  its block cursor by the nominal block size, so a block that decompresses short
  silently stops copying and returns fewer bytes with a nil error. `ReadAt`
  forbids that, and callers rely on it: anything decoding a fixed-size structure
  off the result gets zero padding it has no way to detect and parses it as real
  data. The GraalVM PE export table and the UPX block reader both size a buffer
  from a header field and then ignore `n` entirely, so a crafted image drives
  them straight through the padding.

- a read landing exactly on the end of the file returns a *full* buffer paired
  with `io.EOF`. `bytes.Reader.ReadAt` returns nil there, and the callers that
  treat any error as fatal were written against that, so squashfs-resident
  binaries sized near a read boundary were being skipped outright.

`io.ReadFull` normalizes both: it fills the buffer across short reads, and it
clears the error once the buffer is full. A genuinely short tail is reported as
`io.EOF`, which is what `ReadAt` implementations return at the end of a file, and
what the buffering branch of `GetUnionReader` already returns.

Affects squashfs-backed sources (snaps), so in practice the binary catalogers
reading structure out of executables.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-14 14:09:10 -04:00
dependabot[bot] d63c774fa9 chore(deps): bump modernc.org/sqlite from 1.55.0 to 1.56.0 (#5183)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.55.0 to 1.56.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.55.0...v1.56.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.56.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>
2026-08-14 13:34:28 +00:00
dependabot[bot] 3eee102f63 chore(deps): bump github.com/google/go-containerregistry (#5184)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.21.7 to 0.21.9.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.21.7...v0.21.9)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.21.9
  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>
2026-08-14 13:34:24 +00:00
dependabot[bot] 20cc3df387 chore(deps): bump zizmorcore/zizmor-action from 0.6.1 to 0.6.2 (#5181)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.6.1 to 0.6.2.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/6fc4b006235f201fdab3722e17240ab420d580e5...3dc1ecc9bcb9e94e9b2c709687979e1298497054)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.2
  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>
2026-08-14 13:33:58 +00:00
dependabot[bot] 4782a94029 chore(deps): bump github.com/klauspost/compress from 1.19.1 to 1.19.2 (#5182)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.19.1 to 1.19.2.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.19.1...v1.19.2)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.19.2
  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>
2026-08-14 13:33:38 +00:00
Weston Steimel 2293641e3b fix: add correct CPE vendor/product candidates for Git for Windows PE binary (#5156)
Signed-off-by: Weston Steimel <author@code.w.steimel.me.uk>
2026-08-10 10:26:29 -04:00
anchore-oss-update-botandanchore-oss-update-bot 9dd9ce0bdd chore(deps): update CPE dictionary index (#5166)
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>
2026-08-10 10:24:51 -04:00
Ankit 949ac70369 fix(java): map legacy Jackson 1.x (-asl) artifacts to org.codehaus.jackson (#5146)
jackson-mapper-asl, jackson-core-asl, and their sibling artifacts
(jackson-jaxrs, jackson-xc, jackson-smile) predate the convention of
embedding META-INF/maven/.../pom.properties in the jar (they were
built with Ant before ~2014). With no POM metadata to read, syft's
groupIDFromJavaMetadata falls through to using the artifact name
itself as the group ID, e.g.

  pkg:maven/jackson-mapper-asl/jackson-mapper-asl@1.9.13

instead of the correct

  pkg:maven/org.codehaus.jackson/jackson-mapper-asl@1.9.13

(confirmed against the published POM on Maven Central for all five
artifacts). Because the generated purl's namespace doesn't match the
vulnerability database's namespace for these packages, this causes
false negatives in downstream scanning (e.g. Grype cannot match known
CVEs such as CVE-2019-10202 against jackson-mapper-asl).

Add the five artifacts to DefaultArtifactIDToGroupID, the same known-
package-list fallback already used for other jars with incomplete
metadata (e.g. the existing ant-*, spring-ldap* entries).

Fixes #4598

Signed-off-by: ankit090701 <ankitanku090701@gmail.com>
2026-08-07 14:38:14 +00:00
Gunny Patel 07fb23487f feat(golang): detect native Go FIPS 140 mode in binaries (#5155)
Signed-off-by: Gunny Patel <zip159@gmail.com>
2026-08-07 14:36:09 +00:00
Timo f45586b457 Remove Go Report Card badge from README (#5130)
Signed-off-by: Timo <57227498+EchterTimo@users.noreply.github.com>
2026-08-07 10:31:27 -04:00
dependabot[bot] 0fe98ed1f2 chore(deps): bump github.com/go-git/go-git/v5 from 5.19.1 to 5.19.2 (#5159)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.19.1 to 5.19.2.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Changelog](https://github.com/go-git/go-git/blob/main/HISTORY.md)
- [Commits](https://github.com/go-git/go-git/compare/v5.19.1...v5.19.2)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.19.2
  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>
2026-08-07 13:32:31 +00:00
dependabot[bot] 5f67883fe8 chore(deps): bump go.yaml.in/yaml/v3 from 3.0.4 to 3.0.5 (#5158)
Bumps [go.yaml.in/yaml/v3](https://github.com/yaml/go-yaml) from 3.0.4 to 3.0.5.
- [Commits](https://github.com/yaml/go-yaml/compare/v3.0.4...v3.0.5)

---
updated-dependencies:
- dependency-name: go.yaml.in/yaml/v3
  dependency-version: 3.0.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>
2026-08-07 13:31:31 +00:00
dependabot[bot] d288dd67ae chore(deps): bump docker/login-action from 4.5.1 to 4.6.0 (#5157)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.5.1 to 4.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/abd2ef45e78c5afb21d64d4ca52ee8550d9572c7...dbcb813823bdd20940b903addbd779551569679f)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.6.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>
2026-08-07 13:30:56 +00:00
Alex Goodman da745b13e8 feat(golang): add extended-stdlib scope and module patterns for symbol capture (#5154)
* feat(golang): add extended-stdlib scope and include patterns for symbol capture

`golang.capture-symbols` decides how much symbol data lands in the SBOM for grype's reachability analysis. It's `none`, `stdlib`, or `all` today, and the useful middle is missing: `stdlib` stops at the standard library, `all` multiplies SBOM size.

A new `extended-stdlib` configurable covers stdlib plus everything under `golang.org/x/`:

```yaml
golang:
  capture-symbols: extended-stdlib
```

Also, a new `capture-symbols-include` configurable for modules that are noisy in your binaries but not everyone's. It's unioned with whatever the scope selects, so it only ever widens:

```yaml
golang:
  capture-symbols: extended-stdlib
  capture-symbols-include:
    - github.com/klauspost/**
```

Patterns are standard doublestar globs, which matters because module paths carry `/v2`-style suffixes:

```yaml
golang:
  capture-symbols-include:
    - github.com/klauspost/*     # compress, but not compress/v2
    - github.com/klauspost/**    # both
    - k8s.io/client-go           # exact match only
```

Ordering is `none` < `stdlib` < `extended-stdlib` < `all`. The existing three values
and the `none` default are unchanged, and the include list is inert under `none`.
Presets compile into glob lists internally, so a single matcher answers "does this
module get symbols" instead of a preset branch sitting next to a separate glob branch.

An unrecognized `capture-symbols` value still falls back to `none`, but warns now
instead of doing it silently. A malformed include pattern warns and gets skipped.

One thing worth a look beyond the feature: the `Symbols` field description in the JSON
schema was wrong after this (it claimed only `all` and `stdlib` populate anything), and
that description lives in the already-published `16.1.10`. Rather than bump a version for
a sentence, `16.1.10` is amended in place and `schema/json/README.md` grows an explicit
exception for description-only changes: descriptions only, no shape change of any kind,
`$id` unchanged. Anything else still needs a bump. Happy to split that into its own PR if
you'd rather review the policy separately.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor(golang): rename capture-symbols-include to capture-symbols-modules

The key's entries are go module paths, and `-include` sitting next to `capture-symbols` reads as plausibly taking symbol or package names instead. Those spellings parse and match nothing, which is quieter than the confusion `-include` was picked to avoid, so the name now says what the list holds.

`golang.CatalogerConfig.CaptureSymbolsModules` and `WithCaptureSymbolsModules` rename with it. Nothing behavioral changes; the key is new in this PR so there is no compatibility surface.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* feat(golang): match capture-symbols-modules across major version suffixes

`github.com/anchore/*` covered `github.com/anchore/syft` and silently stopped covering it the day it became `github.com/anchore/syft/v2`. The config keeps parsing, nothing warns, and symbols quietly go missing from the SBOM. Exact paths had the same hole: `github.com/klauspost/compress` did not cover `compress/v2` either, so no spelling short of `**` survived a major bump.

A major version suffix is part of a module's path but not part of its identity, so patterns are now matched against the module path both with and without it, using `module.SplitPathVersion` from `golang.org/x/mod` (already a direct dep, already used in this package for `PseudoVersion`).

```yaml
golang:
  capture-symbols-modules:
    - github.com/klauspost/*          # compress and compress/v2
    - github.com/klauspost/compress   # same module at every major version
    - github.com/klauspost/compress/v2  # v2 alone
```

Only a trailing suffix is a version, which is Go's own rule. In `github.com/anchore/syft/v2/thing` the `v2` is an ordinary path element naming a major subdirectory a nested module lives in, so it stays literal and `github.com/anchore/**/thing` is how you reach it. `/v0` and `/v1` are not valid suffixes and are left alone.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-07 12:44:42 +00:00
Alex Goodman 68da404bd7 fix(make): don't let ambient RACE leak into raceEnabled test (#5152)
The two "RACE is unset" cases only skipped the t.Setenv call, so they
inherited whatever RACE was in the environment. Running `make test` with
RACE=false exported job-wide flipped the CI-default case and failed.

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-05 10:00:17 -04:00
Alex Goodman 35bf33bcf0 fix(make): explicit integration test timeout + single race switch (#5151)
with a cold fixture cache the integration suite builds and saves 18 docker
images across 36 sequential tests, which walks past `go test`'s default 10m
timeout and takes the fixture cache rebuild down with it. that suite now runs
`go test` directly with `-timeout=30m` (gotest.Tasks() has no timeout option),
plus `-count=1` since the built fixtures are the side effect we're actually
after and a test cache hit would skip producing them.

also adds `RACE` as one switch for the race detector across every suite:

- `RACE=false make test` drops `-race` from unit + integration and skips the
  race smoke, worth doing on a cache rebuild where the wall clock is all
  docker builds anyway
- `RACE=true` forces it on locally
- unset behaves as before: on in CI, off locally and on windows

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-04 10:46:35 -04:00
anchore-oss-update-botandanchore-oss-update-bot 7f73d6a603 chore(deps): update CPE dictionary index (#5148)
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>
2026-08-03 13:20:36 -04:00
dependabot[bot] ef40eec38d chore(deps): bump github.com/klauspost/compress from 1.19.0 to 1.19.1 (#5140)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.19.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>
2026-07-31 13:50:21 +00:00
dependabot[bot] 80212d87c2 chore(deps): bump github.com/go-git/go-billy/v5 from 5.9.0 to 5.9.1 (#5139)
Bumps [github.com/go-git/go-billy/v5](https://github.com/go-git/go-billy) from 5.9.0 to 5.9.1.
- [Release notes](https://github.com/go-git/go-billy/releases)
- [Commits](https://github.com/go-git/go-billy/compare/v5.9.0...v5.9.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-billy/v5
  dependency-version: 5.9.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>
2026-07-31 13:35:46 +00:00
dependabot[bot] b14123ecc1 chore(deps): bump github.com/magiconair/properties (#5141)
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.10 to 1.18.11.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.10...v1.18.11)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-version: 1.18.11
  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>
2026-07-31 13:34:20 +00:00
dependabot[bot] e00aeb16e4 chore(deps): bump github.com/ulikunitz/xz from 0.5.15 to 0.5.16 (#5142)
Bumps [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) from 0.5.15 to 0.5.16.
- [Commits](https://github.com/ulikunitz/xz/compare/v0.5.15...v0.5.16)

---
updated-dependencies:
- dependency-name: github.com/ulikunitz/xz
  dependency-version: 0.5.16
  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>
2026-07-31 13:32:58 +00:00
dependabot[bot] fa6f6d70f0 chore(deps): bump modernc.org/sqlite from 1.54.0 to 1.55.0 (#5138)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.54.0 to 1.55.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.54.0...v1.55.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.55.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>
2026-07-31 13:29:04 +00:00
dependabot[bot] 0975d80dc0 chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.8.2 to 6.8.3 (#5137)
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.8.2 to 6.8.3.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.8.2...v6.8.3)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.8.3
  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>
2026-07-31 13:28:49 +00:00
dependabot[bot] 4d823eaaa7 chore(deps): bump github.com/diskfs/go-diskfs from 1.9.3 to 1.9.4 (#5136)
Bumps [github.com/diskfs/go-diskfs](https://github.com/diskfs/go-diskfs) from 1.9.3 to 1.9.4.
- [Commits](https://github.com/diskfs/go-diskfs/compare/v1.9.3...v1.9.4)

---
updated-dependencies:
- dependency-name: github.com/diskfs/go-diskfs
  dependency-version: 1.9.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>
2026-07-31 13:27:56 +00:00
dependabot[bot] 138e42b428 chore(deps): bump zizmorcore/zizmor-action from 0.6.0 to 0.6.1 (#5134)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/6599ee8b7a49aef6a770f63d261d214911a7ce02...6fc4b006235f201fdab3722e17240ab420d580e5)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.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>
2026-07-31 13:27:32 +00:00
dependabot[bot] ecd8fb7740 chore(deps): bump docker/login-action from 4.4.0 to 4.5.1 (#5135)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.4.0 to 4.5.1.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/af1e73f918a031802d376d3c8bbc3fe56130a9b0...abd2ef45e78c5afb21d64d4ca52ee8550d9572c7)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.5.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>
2026-07-31 13:27:08 +00:00
anchore-oss-update-botandanchore-oss-update-bot dd639c09b2 chore(deps): update tool versions (#5124)
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>
2026-07-30 10:59:36 +00:00
Christopher Angelo PhillipsandAlex Goodman 31a352d030 fix(snap): release temp directories on every snap failure path
---------
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>
2026-07-29 14:50:20 -04:00
Alex Goodman 08e913a2ce Report each hardlink as its own file when scanning images (#5029)
* report each hardlink as its own file when scanning images

image scans previously collapsed a set of hardlinks onto a single file, so only
one path per inode showed up in results. dir scans report every hardlink path,
which made image vs dir SBOMs of the same filesystem diverge (and produce
different SPDX `packageVerificationCode` values for packages that own hardlinked
files).

now both image resolvers (squash and all-layers) surface each hardlink at its
own path as a regular file bound to the target's content, matching dir scans.

user-facing impact:
- SBOMs for images containing hardlinks will list more `file` entries
- SPDX `packageVerificationCode` values change for affected packages, now
  matching the equivalent `dir:` scan
- adds `file.NewVirtualLocationFromImage` to the public API

fixes #5019

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix busybox test assertion

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-29 13:50:43 -04:00
dependabot[bot]andChristopher Phillips 5ef5b1badc chore(deps): bump anchore/workflows/.github/workflows/check-version-available.yaml (#5033)
Bumps [anchore/workflows/.github/workflows/check-version-available.yaml](https://github.com/anchore/workflows) from 0.7.2 to 0.8.0.
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b0c30a80409130d329aaa356fd64a34d8c0b3375...7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/check-version-available.yaml
  dependency-version: 0.8.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>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-07-29 17:33:37 +00:00
dependabot[bot]andChristopher Phillips b236f8c1f7 chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.8.1 to 6.8.2 (#5051)
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.8.1 to 6.8.2.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.8.1...v6.8.2)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.8.2
  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>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-07-29 17:30:07 +00:00
Yashas Gunderiaandychampion 476ce60768 fix(binary): detect Deno 1.11 and 2.9 versions
Signed-off-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: ychampion <ychampion@users.noreply.github.com>
2026-07-29 16:33:46 +00:00
Keith Zantow 16223e6dd7 fix: consider vendored golang packages in module attribution (#5093)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-07-27 15:20:49 -04:00
Eljees 1286689419 Fix missing nested packages in package-lock.json v1 (#5108)
* fix(javascript): catalog nested package-lock v1 dependencies

Signed-off-by: Eljees <57435526+Eljees@users.noreply.github.com>

* test(javascript): cover nested package-lock v1 dependencies

Signed-off-by: Eljees <57435526+Eljees@users.noreply.github.com>

---------

Signed-off-by: Eljees <57435526+Eljees@users.noreply.github.com>
2026-07-27 15:18:48 -04:00
Eljees 295454945c fix: strip publisher URL from RPM CPE vendor (#5081)
Signed-off-by: Eljees <yurytumanov.r@yandex.ru>
2026-07-27 15:18:27 -04:00
Enes Deniz 86baeeb481 fix(rust): omit Cargo PURLs for local packages (#5105)
Signed-off-by: Enes Deniz <142517728+3nesdeniz@users.noreply.github.com>
2026-07-27 14:50:13 +00:00
Rayan Salhabandcyphercodes 2dcf5163b8 fix(apk): allow large installed db fields (#5100)
Signed-off-by: cyphercodes <cyphercodes@users.noreply.github.com>
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
2026-07-27 14:36:56 +00:00
Arpit Jain 12b8ba47fb Fix inverted bounds check dropping every Erlang string with a backslash (#5110)
parseErlangString advances past a backslash escape and then checks
len(data) >= *i before reading the escaped byte. That condition is
almost always true (it only turns false once *i runs off the end),
so the intended out-of-range guard fires on the very first escape
character it sees instead of only at EOF. Any rebar.lock or OTP
resource file containing a backslash in a quoted string (a Windows
git path, an escaped quote, anything) fails to parse and the whole
file, and every package in it, gets dropped.

Flip the comparison to *i >= len(data) so the guard only trips when
the escape is genuinely truncated, and add a regression test for a
string with an escaped quote.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
2026-07-27 14:22:41 +00:00
Rez Moss 138d9ce2a0 added bun binary classifier (#5103)
* added bun binary classifier

Signed-off-by: Rez Moss <hi@rezmoss.com>

* added bun binary classifier

Signed-off-by: Rez Moss <hi@rezmoss.com>

---------

Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-07-27 10:18:50 -04:00
anchore-oss-update-botandanchore-oss-update-bot 8a229b16c4 chore(deps): update CPE dictionary index (#5109)
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>
2026-07-27 10:04:27 -04:00
dependabot[bot] 9af0098a68 chore(deps): bump google.golang.org/grpc from 1.80.0 to 1.82.1 (#5099)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.80.0 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.80.0...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 13:48:24 +00:00
dependabot[bot] 2840ea6653 chore(deps): bump modernc.org/sqlite from 1.53.0 to 1.54.0 (#5098)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.53.0 to 1.54.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.53.0...v1.54.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.54.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>
2026-07-24 13:29:49 +00:00
dependabot[bot] b38e40e3e6 chore(deps): bump zizmorcore/zizmor-action from 0.5.7 to 0.6.0 (#5096)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.7 to 0.6.0.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/192e21d79ab29983730a13d1382995c2307fbcaa...6599ee8b7a49aef6a770f63d261d214911a7ce02)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.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>
2026-07-24 13:29:21 +00:00
dependabot[bot] a6eedecd31 chore(deps): bump github.com/gpustack/gguf-parser-go (#5097)
Bumps [github.com/gpustack/gguf-parser-go](https://github.com/gpustack/gguf-parser-go) from 0.24.1 to 0.25.0.
- [Release notes](https://github.com/gpustack/gguf-parser-go/releases)
- [Commits](https://github.com/gpustack/gguf-parser-go/compare/v0.24.1...v0.25.0)

---
updated-dependencies:
- dependency-name: github.com/gpustack/gguf-parser-go
  dependency-version: 0.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>
2026-07-24 13:29:04 +00:00
dependabot[bot] 691b1357b2 chore(deps): bump actions/checkout from 7.0.0 to 7.0.1 (#5095)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.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>
2026-07-24 13:27:48 +00:00
Alex Goodman c890e7f17f decode golang symbols (#5089)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-23 15:03:36 -04:00
anchore-oss-update-botandanchore-oss-update-bot 29fd7d0dec chore(deps): update anchore dependencies (#5022)
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>
2026-07-20 18:04:57 +00:00
Yashas Gunderiaandychampion 289137ff3c Avoid duplicate packages for replaced Go modules (#5069)
* Prevent duplicate Go packages after source replacement resolution

Source analysis already applies module replacements through go/packages. Avoid synthesizing the same replacement again from go.mod while retaining fallback synthesis for modules that source analysis did not resolve.

Constraint: Preserve unimported and local-path replacement cataloging.

Rejected: Deduplicate only during final assembly | That retains redundant license lookup and ambiguous metadata ownership.

Confidence: high

Scope-risk: narrow

Directive: Keep go.mod fallback packages limited to modules absent from source analysis.

Tested: Focused replacement regression, related Go module parser tests, go vet, gofmt, and diff checks.

Not-tested: Docker-backed full cataloger fixtures; local root storage was exhausted by image generation.
Signed-off-by: ychampion <ychampion@users.noreply.github.com>

* Keep replacement fixtures with the Go module test data

Constraint: The maintainer reserves internal/gotestdata for fixtures that need special Go tooling discovery.
Rejected: Leave this fixture in gotestdata | The regression opens its module explicitly and does not need the special location.
Confidence: high
Scope-risk: narrow
Directive: Use internal/gotestdata only when a fixture must avoid Go testdata discovery rules.
Tested: replacement regression repeated 10 times; Go module parser table; go vet for the Go cataloger; gofmt; diff checks.
Not-tested: Full cataloger package; three unrelated parser fixtures fail identically on exact prior head in this environment.
Signed-off-by: ychampion <ychampion@users.noreply.github.com>

---------

Signed-off-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: ychampion <ychampion@users.noreply.github.com>
2026-07-20 12:30:56 -04:00
dependabot[bot] 22ffd5209d chore(deps): bump github.com/gkampitakis/go-snaps from 0.5.22 to 0.5.23 (#5079)
Bumps [github.com/gkampitakis/go-snaps](https://github.com/gkampitakis/go-snaps) from 0.5.22 to 0.5.23.
- [Release notes](https://github.com/gkampitakis/go-snaps/releases)
- [Commits](https://github.com/gkampitakis/go-snaps/compare/v0.5.22...v0.5.23)

---
updated-dependencies:
- dependency-name: github.com/gkampitakis/go-snaps
  dependency-version: 0.5.23
  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>
2026-07-20 15:28:53 +00:00
Jason PaulosandAlex Goodman a0852ebf98 Add multi-platform OCI image support (#5074)
* Add multi-platform OCI image support

Signed-off-by: Jason Paulos <jasonpaulos@users.noreply.github.com>

* Reduce calls to PrepareMultiplatformFixtureImage in TestMultiPlatformOCIImageSelection

Signed-off-by: Jason Paulos <jasonpaulos@users.noreply.github.com>

* Use smaller image for testing & update stereoscope fork

Signed-off-by: Jason Paulos <jasonpaulos@users.noreply.github.com>

* bump to stereoscope@main after 548 merge

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>
2026-07-20 15:28:29 +00:00
anchore-oss-update-botandanchore-oss-update-bot d380ad08b5 chore(deps): update CPE dictionary index (#5082)
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>
2026-07-20 15:21:24 +00:00
dependabot[bot] ae9534203d chore(deps): bump golang.org/x/mod from 0.37.0 to 0.38.0 (#5078)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.37.0 to 0.38.0.
- [Commits](https://github.com/golang/mod/compare/v0.37.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.38.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>
2026-07-17 13:52:25 +00:00
dependabot[bot] dc1ef98237 chore(deps): bump golang.org/x/tools from 0.47.0 to 0.48.0 (#5077)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.47.0 to 0.48.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.47.0...v0.48.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.48.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>
2026-07-17 13:52:22 +00:00
dependabot[bot] 36a3af41d9 chore(deps): bump golang.org/x/net from 0.56.0 to 0.57.0 (#5076)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.56.0 to 0.57.0.
- [Commits](https://github.com/golang/net/compare/v0.56.0...v0.57.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.57.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>
2026-07-17 13:32:07 +00:00
Christopher Angelo Phillips 3e2bc6ed09 ci: add docker-container driver (#5071)
* ci: add docker-container driver

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

* fix: update to v4.2.0

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

---------

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-07-16 10:21:22 -04:00
Yashas Gunderia 5e11775c8b feat: detect unpackaged Firefox in software inventories (#5068)
Signed-off-by: ychampion <ychampion@users.noreply.github.com>
2026-07-15 22:09:58 -04:00
Rez Moss 8ed534c32b fix: NPM redis.js CPE (#5012)
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-07-15 20:55:42 -04:00
Christopher Angelo PhillipsandAlex Goodman aff2c247db feat: add safe tensor model type to SBOM output (#4844)
---------
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>
2026-07-15 17:14:03 -04:00
Christopher Angelo Phillips ed499fd2d0 chore: migrate .goreleaser config to use docker_v2 (#4608)
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-07-15 16:52:46 -04:00
Rez Moss ad828d659f fix: conan libxml2 CPE (#5016)
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-07-15 15:29:03 -04:00
ChrisJr404andChris 2805655ab0 feat(cmd): log total scan duration at info level (#4858)
Closes #4587

Adds a single info-level log line at the end of every `syft scan` run that reports the total scan wall-clock time, e.g.:

    [0003]  INFO scan completed in 2.017s

Per maintainer guidance on the issue, the line is logged at INFO so it surfaces with `-v` (alongside the existing per-cataloger timing) and stays out of the default TUI / stdout. Useful for users who leave long scans running and want the overall time at a glance, without having to wrap the invocation in `time`.

Signed-off-by: Chris (ChrisJr404) <11917633+ChrisJr404@users.noreply.github.com>
Co-authored-by: Chris (ChrisJr404) <11917633+ChrisJr404@users.noreply.github.com>
2026-07-14 12:49:53 +00:00
Monibee-FudgekinsandAlex Goodman 552ddbfadb fix: map NuGet PURLs to dotnet package type (#4861)
* test: use unique NuGet PURL subtest name

Signed-off-by: Monibee Fudgekins <menoisback@proton.me>

* fix: emit canonical NuGet PURL type

Signed-off-by: Monibee Fudgekins <menoisback@proton.me>

* chore: regenerate dotnet capabilities for nuget PURL type

regenerated after emitting canonical nuget PURL type for dotnet packages.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Monibee Fudgekins <menoisback@proton.me>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-13 16:43:34 -04:00
Christopher Angelo PhillipsandAlex Goodman f6b5d3e736 perf: group golang symbols by package path to reduce the sbom size (#5064)
---------
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>
2026-07-13 16:40:16 -04:00
ChrisJr404andChris 4312699dd6 cyclonedx: import firmware-typed components as packages (#4855)
The CycloneDX decoder was filtering on a hard-coded set of component
types (application/framework/library/machine-learning-model). Anything
else — including "firmware" — fell through silently, so a BOM
describing u-boot or other firmware ended up with no packages and
grype reported no matches (anchore/grype#2537).

@kzantow confirmed in the issue thread that firmware should be
included and gave this section as the spot to update.

Add ComponentTypeFirmware to the case list and a regression test that
constructs a single-firmware-component BOM and asserts the decoded
SBOM contains the package.

Closes anchore/grype#2537

Signed-off-by: Chris (ChrisJr404) <11917633+ChrisJr404@users.noreply.github.com>
Co-authored-by: Chris (ChrisJr404) <11917633+ChrisJr404@users.noreply.github.com>
2026-07-13 19:33:51 +00:00
fabd760914 fix: correct Spring LDAP group ID in purl generation (#4908)
* fix: correct Spring LDAP group ID in purl generation

The Spring LDAP artifacts use the group ID org.springframework.ldap
(not org.springframework). This caused syft to generate incorrect purls
like pkg:maven/spring-ldap-core/spring-ldap-core@3.1.4 instead of the
correct pkg:maven/org.springframework.ldap/spring-ldap-core@3.1.4.

Add all Spring LDAP artifacts to the DefaultArtifactIDToGroupID map:
- spring-ldap
- spring-ldap-core
- spring-ldap-core-tiger
- spring-ldap-ldif-core
- spring-ldap-odm
- spring-ldap-test

Closes #4030

Signed-off-by: Jonas Boos <jonasboos@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix formatting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Jonas Boos <jonasboos@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Jonas Boos <jonasboos@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-13 19:31:35 +00:00
M_C_J e854078fb0 fix: use printf instead of echo to fix ANSI color output (#4978)
Signed-off-by: Jouini Mohamed Chaker <JouiniMohamedChaker@proton.me>
2026-07-13 19:29:30 +00:00
Mike ZolotarovandAlex Goodman 987ae7f26a fix: PE case-insensitive extensions (Win32/ISO 9660 compatibility) (#4996)
* fix: PE case-insensitive extensions (Win32/ISO 9660 compatibility)

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* expand cases to bpl files and surrounding catalogers

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-13 19:25:31 +00:00
Keith Zantow 76ede661db fix: allow more PEP440-compliant characters in python versions (#4964)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-07-13 15:10:12 -04:00
Ali Asghar da77ea388a docs: fix stale commands in binary cataloger README (#5047)
Update the binary classifier cataloger developer README to match the
current workflow:

- Testing section used the non-existent flag -must-use-full-binaries
  against .../testdata/...; the real flag is -must-use-original-binaries
  and Test_Cataloger_PositiveCases lives in the binary package. Fix the
  command and add a single-fixture variant.
- Point add-snippet references at the make target and note the (Y/n/q)
  prompt requires y to write the snippet.
- Fix the test file name (classifier_cataloger_test.go) and document the
  go generate ./internal/capabilities step for new classifiers.

Fixes #4510

Signed-off-by: alliasgher <alliasgher123@gmail.com>
2026-07-13 15:15:01 +00:00
SynvoyaandSynvoya 3cc3ecb003 Fix panic on cabal.project.freeze 'installed' boot-lib constraints (#5040)
GHC boot-lib constraints (any.base installed) have no ' ==', so fields[1] panicked with index out of range and the recover dropped all Haskell packages. Skip constraint lines without a version.

Signed-off-by: Synvoya <16019863+Synvoya@users.noreply.github.com>
Co-authored-by: Synvoya <16019863+Synvoya@users.noreply.github.com>
2026-07-13 10:57:32 -04:00
Arpit Jain 7745e450fa fix panic parsing a rockspec comment that ends at EOF (#5053)
The hand-written rockspec parser reads a byte past the end of the buffer
in two spots when a comment runs right up to the end of the file.

In parseRockspecBlock, when a block starts with a leading comment that
consumes the rest of the file, the SkipWhitespace afterward leaves the
index at len(data) and the following `c = data[*i]` reads out of range.
In parseComment, `data[*i]` is read after the index is advanced to check
for a CR/LF pair, so a bare carriage return as the last byte reads past
the end.

Both cases show up with a rockspec whose final line is a comment ending
in a lone \r with no trailing newline. That is malformed but harmless
input, and the panic aborts the whole Lua cataloger, so every valid Lua
package in the same scan gets dropped. Guard both reads with a length
check and return cleanly at EOF. Added table cases covering a
comment-only file and a trailing comment, both ending in a bare CR.

Signed-off-by: arpitjain099 <arpitjain099@gmail.com>
2026-07-13 14:45:12 +00:00
SynvoyaandSynvoya bd0917fe93 fix(javascript): strip peer-dep suffix from deno.lock npm keys (#5055)
deno.lock npm keys append resolved peer dependencies after the version,
separated by "_" (e.g. "typedoc@0.28.19_typescript@6.0.3" or the scoped
"@scope/name@1.2.3_@scope+peer@4.5.6"). parseDenoNpmNameVersion split on the
last "@", capturing a peer's "@" and producing a garbage package name plus
the peer's version (and a corrupt PURL that breaks vulnerability matching).

Strip the peer suffix (the first "_" after the version "@") before splitting
name@version; a semver version never contains "_".

Signed-off-by: Synvoya <16019863+Synvoya@users.noreply.github.com>
Co-authored-by: Synvoya <16019863+Synvoya@users.noreply.github.com>
2026-07-13 14:38:32 +00:00
Christopher Angelo PhillipsandAlex Goodman 1dcac54b0e feat: optionally capture golang binary symbols (#4988)
---------
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>
2026-07-13 10:30:23 -04:00
anchore-oss-update-botandanchore-oss-update-bot 3252b6f806 chore(deps): update CPE dictionary index (#5063)
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>
2026-07-13 10:07:00 -04:00
dependabot[bot] c4a0b6f647 chore(deps): bump github.com/klauspost/compress from 1.18.6 to 1.19.0 (#5050)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.18.6 to 1.19.0.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.18.6...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.19.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>
2026-07-10 13:31:42 +00:00
dependabot[bot] 5b630ec61c chore(deps): bump docker/login-action from 4.2.0 to 4.4.0 (#5049)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/650006c6eb7dba73a995cc03b0b2d7f5ca915bee...af1e73f918a031802d376d3c8bbc3fe56130a9b0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.4.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>
2026-07-10 13:29:34 +00:00
Sai Asish YandAlex Goodman e8abab2e77 swift: emit canonical purls without .git suffix or repeated name (#4785)
* swift: emit canonical purls without .git suffix or repeated name

Reported in anchore/syft#3961: the purl syft emits for Swift packages
parsed out of Package.resolved keeps the repository URLs .git suffix
and then re-appends the package name, producing

    pkg:swift/github.com/apple/swift-nio-ssl.git/swift-nio-ssl@2.0.0

NVD / Grype cannot match that purl against the known
swift-nio-ssl@2.0.0 CVE (GHSA-frg3-gpcx-968f), so every Swift SBOM
produced by syft silently loses vulnerability coverage. cdxgen and
the wider purl ecosystem use the shorter form

    pkg:swift/github.com/apple/swift-nio-ssl@2.0.0

which Grype does match.

Replace the ad-hoc strings.Replace with swiftNamespaceFromSourceURL,
which trims the common URL schemes, strips the ".git" suffix, and
drops a trailing /<name> segment so the namespace is only the
organisation path (e.g. github.com/apple). Existing test expectations
are updated to the new purl shape.

Fixes #3961

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* Revert unrelated consul binary classifier changes

The swift purl commit (ff5ffc37) accidentally bundled removal of the
consul GitDescribe and NUL-wrapped version matchers plus the 1.12.9 and
1.7.14 fixtures/test cases. That drops version detection for consul
binaries not carrying the CONSUL_VERSION string. Restore the binary
cataloger to its pre-commit state so this branch is swift-only.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* swift: strip repeated name segment case-insensitively

Package.resolved identities are lowercased while the repo URL path may be
mixed-case (e.g. github.com/Apple/Swift-NIO vs identity swift-nio). The
case-sensitive TrimSuffix left the repo segment in the namespace for such
repos, reintroducing the duplicated-name purl. Compare the trailing segment
with EqualFold and add unit coverage for swiftPackageManagerPackageURL.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-08 12:55:13 -04:00
Alex Goodman 002a326c41 detect Ubuntu Pro/ESM extended support (#5028)
Mirrors the existing RHEL `ExtendedSupport` detector for Ubuntu so grype can
auto-enable its ESM distro channel on Pro/ESM-enabled scans.

`findUbuntuFeatures` sets `linux.Release.ExtendedSupport` when the scanned OS is
Ubuntu and any of these hold:

- an uncommented apt source (classic `.list` or DEB822 `.sources`, or the
  ubuntu-advantage auth entry) referencing `esm.ubuntu.com`
- an enabled `esm-infra`/`esm-apps` service in the ubuntu-advantage
  `status.json` cache
- an installed dpkg package whose version carries an `[~+]esm` pocket suffix
  (the usual in-image signal, since containers are rarely Pro-attached)

The package fallback reads `s.Artifacts.Packages` from the `DetectFeatures`
SBOM callback, which runs after package cataloging, so no new plumbing was
needed. Conservative by design: plain Ubuntu and disabled/commented ESM
evidence stay false.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-07 10:11:15 -04:00
OsamaSE 3e399256b3 fix(debian): supplement point release from /etc/debian_version (#4997)
Debian's /etc/os-release declares only the major version in VERSION_ID (e.g. "13"), while /etc/debian_version carries the point release (e.g. "13.5"). The cataloger previously skipped debian_version whenever os-release already had a Version/VersionID, so point releases were lost.

Signed-off-by: Osama Elhindawy <25825931+OsamaSE@users.noreply.github.com>
2026-07-06 15:18:37 +00:00
Marcus ebac92a86e fix: conanfile comments in [requires] no longer stop dependency detection (#5020)
The [requires] section parser set inRequirements=false when it
encountered a comment line (#) inside the section, because the comment
check was OR'd with the section-change check. Any dependency listed
after a comment was silently skipped.

Skip blank lines and comments before evaluating section state so they
never toggle inRequirements. Add a regression test fixture with a
comment inside [requires].

Fixes #5017

Signed-off-by: jeff <jfjrh2014@gmail.com>
2026-07-06 15:17:42 +00:00
SynvoyaandSynvoya aebdf4bff4 Fix mix.lock git/path deps mislabeled as hex.pm with bogus PURL (#5041)
parseMixLock read the version from a hex-only token position and hardcoded a pkg:hex PURL, so git/path deps got a URL-fragment version and a false hex.pm PURL (false CVE matches). Branch on the source atom: git uses the commit SHA, path an empty version, and pkg:hex PURLs are emitted only for hex deps.

Signed-off-by: Synvoya <16019863+Synvoya@users.noreply.github.com>
Co-authored-by: Synvoya <16019863+Synvoya@users.noreply.github.com>
2026-07-06 15:13:20 +00:00
Alex Goodman f1457f2554 pin php apache fixture to debian snapshot (#5043)
the plain version pin drifted out from under us: debian mirrors only keep
the latest point release, so 8.2.31 became uninstallable once 8.2.32 shipped
and apt pulled the unpinned transitive php8.2-cli at 8.2.32. pointing apt at
snapshot.debian.org keeps the exact point release installable so the fixture
(and its test expectations) stay reproducible.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-06 11:01:44 -04:00
dependabot[bot] e4f5df936c chore(deps): bump anchore/workflows/.github/workflows/release-install-script.yaml (#5031)
Bumps [anchore/workflows/.github/workflows/release-install-script.yaml](https://github.com/anchore/workflows) from 0.7.2 to 0.8.0.
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b0c30a80409130d329aaa356fd64a34d8c0b3375...7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/release-install-script.yaml
  dependency-version: 0.8.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>
2026-07-06 10:59:47 -04:00
anchore-oss-update-botandanchore-oss-update-bot 8c0d21e147 chore(deps): update CPE dictionary index (#5042)
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>
2026-07-06 10:56:45 -04:00
dependabot[bot] 6f5c83a558 chore(deps): bump actions/checkout from 6.0.3 to 7.0.0 (#5024)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 13:42:48 +00:00
dependabot[bot] ee48c01c06 chore(deps): bump actions/cache/restore in /.github/actions/bootstrap (#5037)
Bumps [actions/cache/restore](https://github.com/actions/cache) from 5.0.5 to 6.1.0.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)

---
updated-dependencies:
- dependency-name: actions/cache/restore
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 13:31:51 +00:00
dependabot[bot] f6bf15bb3c chore(deps): bump golang.org/x/tools from 0.46.0 to 0.47.0 (#5038)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.46.0 to 0.47.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.46.0...v0.47.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.47.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>
2026-07-03 13:31:03 +00:00
dependabot[bot] 312f2532d0 chore(deps): bump modernc.org/sqlite from 1.51.0 to 1.53.0 (#5039)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.51.0 to 1.53.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.51.0...v1.53.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.53.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>
2026-07-03 13:30:45 +00:00
dependabot[bot] f2c9463e76 chore(deps): bump anchore/go-make/.github/actions/setup (#5036)
Bumps [anchore/go-make/.github/actions/setup](https://github.com/anchore/go-make) from 0.6.0 to 0.8.0.
- [Release notes](https://github.com/anchore/go-make/releases)
- [Commits](https://github.com/anchore/go-make/compare/39fe5f71112d4dceb3ff0a92a40f272f067fc457...430e2175bb166bfd138ef75466adbcc8ddab875f)

---
updated-dependencies:
- dependency-name: anchore/go-make/.github/actions/setup
  dependency-version: 0.8.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>
2026-07-03 13:30:15 +00:00
dependabot[bot] 4eb61fc104 chore(deps): bump anchore/workflows/.github/workflows/check-gate.yaml (#5034)
Bumps [anchore/workflows/.github/workflows/check-gate.yaml](https://github.com/anchore/workflows) from 0.7.2 to 0.8.0.
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b0c30a80409130d329aaa356fd64a34d8c0b3375...7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/check-gate.yaml
  dependency-version: 0.8.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>
2026-07-03 13:26:50 +00:00
dependabot[bot] 832d96c1dd chore(deps): bump actions/cache in /.github/actions/bootstrap (#5035)
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.5 to 6.1.0.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 13:26:37 +00:00
dependabot[bot] 5a7a72b53e chore(deps): bump zizmorcore/zizmor-action from 0.5.6 to 0.5.7 (#5030)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/5f14fd08f7cf1cb1609c1e344975f152c7ee938d...192e21d79ab29983730a13d1382995c2307fbcaa)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.7
  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>
2026-07-03 13:26:27 +00:00
dependabot[bot] df1fc98e7a chore(deps): bump anchore/workflows/.github/workflows/codeql.yaml (#5032)
Bumps [anchore/workflows/.github/workflows/codeql.yaml](https://github.com/anchore/workflows) from 0.7.2 to 0.8.0.
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b0c30a80409130d329aaa356fd64a34d8c0b3375...7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/codeql.yaml
  dependency-version: 0.8.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>
2026-07-03 13:26:04 +00:00
gabandAlex Goodman 656a4d46d7 Vcpkg Cataloger (#4081)
* Vcpkg cataloger for vcpkg "Manifest Mode"

Find and parse vcpkg-lock.json to get HEAD commit hash

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* just use local vcpkg git repo if it exists, clone it if it doesn't

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* Config opt for git remote clones for vcpkg and README update

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* Look in vcpkg cache git repo for custom git repos

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add triplet to metadata and support overlay-ports from config file

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* Add PURL to packages (not sure if this is correct)

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* flatten structs in pkg module and move vcpkg structs to resolver

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* account for overriden versions in toplevel manifest

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* generate json schema for vcpkg metadata

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* test for basic vcpkg project

dependencies for vcpkg registry to be pulled in

add tree hashes and use correct git hash in builtin-baseline for helloworld test

vcpkg-registry for testing that uses object hashes from syft repo

fix broken tests

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* formatting

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* fix static-analysis violations

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix integration test failure

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* remove uneeded files from vcpkg test fixture and use custom registry

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* change vcpkg registry to anchore one

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* purl spec based on open PR

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* generate-json-schema

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rebased and generate json schema 16.0.40

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>

* address low hanging fruit

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* handle additional comments

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* migrate to testdata

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* improve docs and testing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix static analysis

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove license from pkg metadata

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix capabilities claim

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Gabriel Rau <gabetrau@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-07-01 09:11:33 -04:00
Rez MossandAlex Goodman 148fe572bc added macOS .app cataloger (#4490)
* added macOS .app cataloger, fixed #4010

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* added macOS .app cataloger, fixed #4010

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* added macOS .app cataloger, fixed #4010

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* address review comments

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump schema to 16.1.7

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* address static analysis failures

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* migrate to testdata

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* expand fields and improve test coverage

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-30 10:32:57 -04:00
Keith Zantow deee79411a fix: composite action version parsing (#4616)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-06-29 15:23:47 -04:00
e7f1a803e7 fixed dotnet cataloger can't find packages from deps.json in linux el… (#4517)
* fixed dotnet cataloger can't find packages from deps.json in linux elf, fixed #4514

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* split bundle and PE concerns

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* limit resource usage of readall call

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* removed duplicat

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* make sure the first 4 bytes in elf arent lostt

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* revert readelfbundle func, check size of readdeps json

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* revert readelfbundle func, check size of readdeps json, fixed #4514

Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* move dotnet net8 linux fixture to testdata convention

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* address malformed elf size claims + add tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* dont key off of cataloger name in testing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-06-29 13:52:55 -04:00
sputnik-macandAlex Goodman a34f86fba1 fix(template): expose sprig date/time functions in Go templates (#4644)
* fix(template): expose sprig date functions in Go templates

Replace HermeticTxtFuncMap with TxtFuncMap to expose date/time
functions (now, date, dateInZone, etc.) while still excluding
security-sensitive env/expandenv functions.

Users can now use date functions in templates, e.g.:
  {{ now | unixEpoch }}
  {{ now | date "2006-01-02" }}

Fixes #2372

Signed-off-by: Sputnik-MAC <sputnik.mac.001@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* invert to add functions to the hermetic set, not the other way around

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Sputnik-MAC <sputnik.mac.001@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-29 11:55:51 -04:00
e388b5249d Add support for MIT and Heimdal Kerberos 5 library detection (#4781)
* Add support for MIT and Heimdal Kerberos 5 library detection

Signed-off-by: Nadim Zubidat <nadimz@users.noreply.github.com>

* support 2-component case

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Nadim Zubidat <nadimz@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Nadim Zubidat <nadimz@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-29 11:54:44 -04:00
Archy 1746e96ad3 fix: correct typos and update examples README (#4703)
Signed-off-by: Artem Muterko <artem@sopho.tech>
2026-06-29 11:16:50 -04:00
Sai Asish YandAlex Goodman 956858fc11 ruby/gemspec: resolve simple #{s.name}/#{s.version} interpolation (#4782)
* ruby/gemspec: resolve simple #{s.name}/#{s.version} interpolation

Reported in anchore/syft#4720: scanning projects that depend on gems
like formatador leaks literal Ruby interpolation into the emitted
SBOM, e.g.

    "externalReferences": [
      { "url": "https://github.com/geemus/#{s.name}", "type": "website" }
    ]

because formatador.gemspec uses

    s.homepage = "https://github.com/geemus/#{s.name}"

and parseGemSpecEntries reads the file as plain text instead of
evaluating it. The interpolation leaks through the captured homepage
field and on into any externalReferences entry the cataloger produces.
Dependency Track then rejects the whole BOM because '{' and '}' are
not valid IRI-reference characters (RFC 3987).

Add a post-parse pass that substitutes the common interpolation forms
(#{s.name}, #{gem.name}, #{name}, and the matching #{*.version}
variants) in captured string fields using values already parsed from
the same gemspec. Anything still containing '#{' after best-effort
substitution is an unresolvable Ruby expression, and for URL-like
fields (currently just homepage) we drop the field entirely so the
SBOM is always schema-valid; callers would rather miss a homepage URL
than emit one that breaks downstream tools.

Adds testdata/formatador.gemspec, a minimal real-world gemspec using
the #{s.name} pattern, plus a new parser test asserting that the
homepage field comes out fully resolved.

Fixes #4720

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* improve test cases

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-29 11:00:48 -04:00
Chris Greeno 37fee88b5c feat(elixir): emit dependency relationships from mix.lock (#4985)
adds dependency-of relationships between elixir locked packages, matching how other
ecosystem catalogers (alpine, arch, debian, redhat, python) express the
dependency graph via the shared dependency.Processor/Specifier mechanism.

Signed-off-by: Chris Greeno <cgreeno@gmail.com>
2026-06-29 10:22:38 -04:00
sputnik-mac 1143c12a97 fix: add .bpl file extension support to PE/DLL cataloger (closes #4664) (#4688)
Borland Package Library (.bpl) files are standard Windows PE/DLL files
used in Delphi and C++Builder ecosystems. This adds the .bpl glob
pattern to the PE file discovery so these files are cataloged alongside
.dll and .exe files.
2026-06-29 10:17:52 -04:00
b15c5dbfe2 chore(deps): update anchore dependencies (#4960)
* chore(deps): update anchore dependencies

Signed-off-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com>

* update snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: anchore-oss-update-bot <anchore-oss-update-bot@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-25 15:26:14 -04:00
Alex Goodman 35d56bfb99 Update go-make to v0.8.0 (#5010)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-25 15:01:40 -04:00
Alex Goodman abf6d78dfc fixes the wrapped taskfile-tasks (#5013)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-24 11:22:16 -04:00
Will Murphy fe42bcec38 fix(purl-backfill): respect arch qualifier (#4987)
* fix(purl-backfill): respect arch qualifier

Previously, when constructing rpm, alpm, and apk metadata struct from a
PURL, Syft would ignore the arch qualifier. Start respecting that
qualifier.

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* chore: fix static analysis

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* Clean up control flow in PURL backfill code

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

---------

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2026-06-23 15:23:46 -04:00
Rez MossandChristopher Phillips fea4a50124 feat: deno cataloger #4417 (#4523)
Signed-off-by: Rez Moss <hi@rezmoss.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-06-23 10:58:22 -04:00
dependabot[bot] 5eefd73ac7 chore(deps): bump golang.org/x/tools from 0.45.0 to 0.46.0 (#5008)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.45.0 to 0.46.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.46.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>
2026-06-22 18:50:24 +00:00
dependabot[bot] 684c7018be chore(deps): bump golang.org/x/net from 0.55.0 to 0.56.0 (#5004)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.55.0 to 0.56.0.
- [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.56.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>
2026-06-22 18:34:47 +00:00
dependabot[bot] f827f91ec1 chore(deps): bump golang.org/x/mod from 0.36.0 to 0.37.0 (#5007)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.36.0 to 0.37.0.
- [Commits](https://github.com/golang/mod/compare/v0.36.0...v0.37.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.37.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>
2026-06-22 18:19:47 +00:00
dependabot[bot] e9af7d218c chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.7.10 to 6.8.1 (#5006)
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.7.10 to 6.8.1.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.7.10...v6.8.1)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.8.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>
2026-06-22 18:18:54 +00:00
Alex Goodman 506ad5d6a7 refactor release pipeline: TAG_TOKEN, skip-checks gate, dependabot/zizmor cleanup (#5003)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-22 14:04:48 -04:00
Rez Moss 1f4f9332c5 feat: support envoy bin classifier
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-06-22 13:16:33 -04:00
Rez Moss 52a4c3b594 feat: elastic beats bin classifier (#4969)
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-06-22 11:49:44 -04:00
Keith Zantow 9c321691d4 feat: SPDX 3 (#4269)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-06-22 10:59:34 -04:00
Alex Goodman 0e8d6deabe require tmpdir to exist for fingerprints (#5002)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-22 10:54:26 -04:00
dependabot[bot] deb2fd92ef chore(deps): bump github.com/containerd/containerd/v2 (#5001)
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.3.1 to 2.3.2.
- [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.1...v2.3.2)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 14:12:11 +00:00
Alex Goodman 80d3b62de4 bump go-make to v0.7.0 (#4999)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-22 09:47:58 -04:00
anchore-oss-update-botandanchore-oss-update-bot b71afc87fc chore(deps): update tool versions (#4994)
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>
2026-06-19 11:51:05 +00:00
Alex Goodman efe3174b5f Preserve dependency edges when a compliance stub changes a package ID (#4993)
* fix relationship rewrites for isolated nodes

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* cover dangling pointers

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-18 19:50:30 -04:00
Rez Moss 58e4dbbf01 feat: added bin classifier elastic-agent (#4968)
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-06-17 15:29:07 +00:00
Sebastiaan van Stijn b70fa899cb golangci-lint: enable gci formatter (#4828)
This allows linting the imports to be grouped correctly, and provides
an auto-fix (`golangci-lint run --fix`).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-06-17 10:34:22 -04:00
Alex Goodman 951fbd454a add purl types to cataloger info cmd (#4984)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-16 12:13:34 -04:00
Rez Moss 92ae4d44c5 fix: .net deps.json cataloger no longer shows phantom pkgs (#4971)
Signed-off-by: Rez Moss <hi@rezmoss.com>
2026-06-16 12:02:42 -04:00
Alex Goodman 8d48a8b8c2 ensure we have a snapshot build for cli tests (#4981)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-16 10:41:33 -04:00
David DashtiandAlex Goodman cff5a05681 fix(dpkg): extract License field for opkg/ipkg entries (#4963)
* fix(dpkg): extract License field for opkg/ipkg entries

opkg and ipkg use the dpkg cataloger but declare the package License
inline in the status DB (unlike Debian dpkg, where licenses live in
copyright files). The cataloger silently dropped the License field at
mapstructure decode time, so all opkg-managed packages reported empty
licenses.

This adds the field to the intermediate decode struct and the public
DpkgDBEntry, and populates licenses in newDpkgPackage using the alpine
cataloger's pattern: try license.ParseExpression first to keep valid
SPDX expressions whole, fall back to whitespace splitting for
space-separated lists.

Standard Debian dpkg status files never carry a License field per
Debian policy, so the new path is a no-op for them; the existing
copyright-file lookup in addLicenses is unaffected.

Closes #4940

Signed-off-by: David Dashti <47575784+Dashtid@users.noreply.github.com>

* remove license from dpkg metadata struct

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* restore format snapshot files

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add additional tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: David Dashti <47575784+Dashtid@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-06-15 16:15:32 -04:00
Kursat TopcuogluandKursat Topcuoglu 00ca43d24a fix: catalog uv PEP 723 script lockfiles (*.py.lock) (#4950)
Signed-off-by: Kursat Topcuoglu <7313835+ktopcuoglu@users.noreply.github.com>
Co-authored-by: Kursat Topcuoglu <7313835+ktopcuoglu@users.noreply.github.com>
2026-06-15 11:34:02 -04:00
dependabot[bot] 6a27678036 chore(deps): bump the actions-minor-patch group across 2 directories with 6 updates (#4975)
Bumps the actions-minor-patch group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anchore/workflows/.github/workflows/codeql.yaml](https://github.com/anchore/workflows) | `0.7.0` | `0.7.2` |
| [anchore/workflows/.github/workflows/check-version-available.yaml](https://github.com/anchore/workflows) | `0.7.0` | `0.7.2` |
| [anchore/workflows/.github/workflows/check-gate.yaml](https://github.com/anchore/workflows) | `0.7.0` | `0.7.2` |
| [actions/checkout](https://github.com/actions/checkout) | `6.0.2` | `6.0.3` |
| [anchore/workflows/.github/workflows/release-install-script.yaml](https://github.com/anchore/workflows) | `0.7.0` | `0.7.2` |

Bumps the actions-minor-patch group with 1 update in the /.github/actions/bootstrap directory: [anchore/go-make](https://github.com/anchore/go-make).


Updates `anchore/workflows/.github/workflows/codeql.yaml` from 0.7.0 to 0.7.2
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5...b0c30a80409130d329aaa356fd64a34d8c0b3375)

Updates `anchore/workflows/.github/workflows/check-version-available.yaml` from 0.7.0 to 0.7.2
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5...b0c30a80409130d329aaa356fd64a34d8c0b3375)

Updates `anchore/workflows/.github/workflows/check-gate.yaml` from 0.7.0 to 0.7.2
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5...b0c30a80409130d329aaa356fd64a34d8c0b3375)

Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

Updates `anchore/workflows/.github/workflows/release-install-script.yaml` from 0.7.0 to 0.7.2
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5...b0c30a80409130d329aaa356fd64a34d8c0b3375)

Updates `anchore/go-make` from 0.5.0 to 0.6.0
- [Release notes](https://github.com/anchore/go-make/releases)
- [Commits](https://github.com/anchore/go-make/compare/9de27be11ed73e2f9d5406a836a492b7d8aa1225...39fe5f71112d4dceb3ff0a92a40f272f067fc457)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/codeql.yaml
  dependency-version: 0.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/check-version-available.yaml
  dependency-version: 0.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/check-gate.yaml
  dependency-version: 0.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/release-install-script.yaml
  dependency-version: 0.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: anchore/go-make
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 13:29:23 +00:00
Keith Zantow 89773c0a12 fix: support CycloneDX 1.7 (#4967)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-06-11 09:40:42 -04:00
Yoonho HannandChristopher Phillips b08d3c2970 feat: add support for Bun lockfile (#4625)
---------
Signed-off-by: Yoonho Hann <hnnynh125@gmail.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-06-09 13:22:43 -04:00
Keith Zantow 63232bf725 fix: local version identifiers in python requirements parsing (#4959)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-06-08 11:12:47 -04:00
MarcusandChristopher Phillips 908eb57890 feat: add .bpl extension to PE cataloger (#4954)
BPL (Borland Package Library) files are standard PE/DLL format used by
Delphi and C++Builder. Adding the extension to the glob list so syft
picks them up during directory scans without users needing to rename
to .dll first.
---------
Signed-off-by: jfjrh2014 <jfjrh2014@gmail.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-06-08 10:07:15 -04:00
Arpit Jain c5c423ab37 fix: detect mariadb version from RHEL build path (#4952)
Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
2026-06-07 13:28:18 -04:00
anchore-oss-update-botandanchore-oss-update-bot d4496b05aa chore(deps): update anchore dependencies (#4934)
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>
2026-06-05 13:55:57 +00:00
dependabot[bot] adc55cdb3a chore(deps): bump the go-minor-patch group across 1 directory with 3 updates (#4957)
Bumps the go-minor-patch group with 3 updates in the / directory: [github.com/gkampitakis/go-snaps](https://github.com/gkampitakis/go-snaps), [github.com/gpustack/gguf-parser-go](https://github.com/gpustack/gguf-parser-go) and [modernc.org/sqlite](https://gitlab.com/cznic/sqlite).


Updates `github.com/gkampitakis/go-snaps` from 0.5.21 to 0.5.22
- [Release notes](https://github.com/gkampitakis/go-snaps/releases)
- [Commits](https://github.com/gkampitakis/go-snaps/compare/v0.5.21...v0.5.22)

Updates `github.com/gpustack/gguf-parser-go` from 0.24.0 to 0.24.1
- [Release notes](https://github.com/gpustack/gguf-parser-go/releases)
- [Commits](https://github.com/gpustack/gguf-parser-go/compare/v0.24.0...v0.24.1)

Updates `modernc.org/sqlite` from 1.50.1 to 1.51.0
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.50.1...v1.51.0)

---
updated-dependencies:
- dependency-name: github.com/gkampitakis/go-snaps
  dependency-version: 0.5.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-minor-patch
- dependency-name: github.com/gpustack/gguf-parser-go
  dependency-version: 0.24.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-minor-patch
- dependency-name: modernc.org/sqlite
  dependency-version: 1.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 13:28:05 +00:00
anchore-oss-update-botandanchore-oss-update-bot 00d0bb59cc chore(deps): update tool versions (#4724)
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>
2026-06-05 11:22:28 +00:00
dependabot[bot]andChristopher Phillips f474308783 chore(deps): bump the go-minor-patch group across 2 directories with 14 updates (#4947)
* chore(deps): bump the go-minor-patch group across 2 directories with 14 updates

Bumps the go-minor-patch group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/CycloneDX/cyclonedx-go](https://github.com/CycloneDX/cyclonedx-go) | `0.10.0` | `0.11.0` |
| [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) | `3.4.0` | `3.5.0` |
| [github.com/diskfs/go-diskfs](https://github.com/diskfs/go-diskfs) | `1.7.0` | `1.9.3` |
| [github.com/github/go-spdx/v2](https://github.com/github/go-spdx) | `2.4.0` | `2.7.0` |
| [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) | `0.21.5` | `0.21.6` |
| [github.com/gookit/color](https://github.com/gookit/color) | `1.6.0` | `1.6.1` |
| [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) | `0.13.0` | `0.14.0` |
| [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) | `6.7.8` | `6.7.10` |
| [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) | `1.46.2` | `1.50.1` |

Bumps the go-minor-patch group with 1 update in the /.make directory: [github.com/anchore/go-make](https://github.com/anchore/go-make).

Updates `github.com/CycloneDX/cyclonedx-go` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/CycloneDX/cyclonedx-go/releases)
- [Commits](https://github.com/CycloneDX/cyclonedx-go/compare/v0.10.0...v0.11.0)

Updates `github.com/Masterminds/semver/v3` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/Masterminds/semver/releases)
- [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/semver/compare/v3.4.0...v3.5.0)

Updates `github.com/diskfs/go-diskfs` from 1.7.0 to 1.9.3
- [Commits](https://github.com/diskfs/go-diskfs/compare/v1.7.0...v1.9.3)

Updates `github.com/github/go-spdx/v2` from 2.4.0 to 2.7.0
- [Release notes](https://github.com/github/go-spdx/releases)
- [Commits](https://github.com/github/go-spdx/compare/v2.4.0...v2.7.0)

Updates `github.com/google/go-containerregistry` from 0.21.5 to 0.21.6
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.21.5...v0.21.6)

Updates `github.com/gookit/color` from 1.6.0 to 1.6.1
- [Release notes](https://github.com/gookit/color/releases)
- [Commits](https://github.com/gookit/color/compare/v1.6.0...v1.6.1)

Updates `github.com/invopop/jsonschema` from 0.13.0 to 0.14.0
- [Release notes](https://github.com/invopop/jsonschema/releases)
- [Commits](https://github.com/invopop/jsonschema/compare/v0.13.0...v0.14.0)

Updates `github.com/jedib0t/go-pretty/v6` from 6.7.8 to 6.7.10
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.7.8...v6.7.10)

Updates `github.com/klauspost/compress` from 1.18.5 to 1.18.6
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.18.5...v1.18.6)

Updates `golang.org/x/mod` from 0.35.0 to 0.36.0
- [Commits](https://github.com/golang/mod/compare/v0.35.0...v0.36.0)

Updates `golang.org/x/net` from 0.53.0 to 0.54.0
- [Commits](https://github.com/golang/net/compare/v0.53.0...v0.54.0)

Updates `golang.org/x/tools` from 0.44.0 to 0.45.0
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.44.0...v0.45.0)

Updates `modernc.org/sqlite` from 1.46.2 to 1.50.1
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.46.2...v1.50.1)

Updates `github.com/anchore/go-make` from 0.4.0 to 0.5.0
- [Release notes](https://github.com/anchore/go-make/releases)
- [Commits](https://github.com/anchore/go-make/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: github.com/CycloneDX/cyclonedx-go
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/Masterminds/semver/v3
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/diskfs/go-diskfs
  dependency-version: 1.9.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/github/go-spdx/v2
  dependency-version: 2.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.21.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-minor-patch
- dependency-name: github.com/gookit/color
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-minor-patch
- dependency-name: github.com/invopop/jsonschema
  dependency-version: 0.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.7.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-minor-patch
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.18.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-minor-patch
- dependency-name: golang.org/x/mod
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: golang.org/x/net
  dependency-version: 0.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: golang.org/x/tools
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: modernc.org/sqlite
  dependency-version: 1.50.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/anchore/go-make
  dependency-version: 0.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

* fix: update signatures to return fs.FileInfo after breaking changes

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

* fix: lint-fix

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-06-04 17:06:25 -04:00
Will Murphy bf67072246 chore: bump golang.org/x/crypto (#4955)
* chore: bump golang.org/x/crypto

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* also bump golang.org/x/net

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

---------

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2026-06-04 16:06:08 -04:00
Will Murphy 9673f867e5 Pass contents: read to check-gate (#4951)
Otherwise check-gate doesn't have enough permissions to do its job and
fails.

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2026-06-02 16:05:42 -04:00
Matias Insaurralde a4fb2c0396 perf(python): hoist name normalization regexp to package level (#4926)
Avoid recompiling the separator pattern on every normalize() call during cataloging.

Signed-off-by: Matías Insaurralde <matias@insaurral.de>
2026-06-01 21:17:43 -04:00
witchcraze cf2ce643c3 update helm classifier (#4922)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2026-06-01 10:21:57 -04:00
dependabot[bot] 524a44b70d chore(deps): bump the actions-minor-patch group across 1 directory with 6 updates (#4946)
Bumps the actions-minor-patch group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anchore/workflows/.github/workflows/codeql.yaml](https://github.com/anchore/workflows) | `0.6.0` | `0.7.0` |
| [anchore/workflows/.github/workflows/check-version-available.yaml](https://github.com/anchore/workflows) | `0.6.0` | `0.7.0` |
| [anchore/workflows/.github/workflows/check-gate.yaml](https://github.com/anchore/workflows) | `0.6.0` | `0.7.0` |
| [docker/login-action](https://github.com/docker/login-action) | `4.1.0` | `4.2.0` |
| [anchore/workflows/.github/workflows/release-install-script.yaml](https://github.com/anchore/workflows) | `0.6.0` | `0.7.0` |
| [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) | `0.5.5` | `0.5.6` |



Updates `anchore/workflows/.github/workflows/codeql.yaml` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/15122524ced7906bfa9685eeae12e22647773ea6...b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5)

Updates `anchore/workflows/.github/workflows/check-version-available.yaml` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/15122524ced7906bfa9685eeae12e22647773ea6...b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5)

Updates `anchore/workflows/.github/workflows/check-gate.yaml` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/15122524ced7906bfa9685eeae12e22647773ea6...b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5)

Updates `docker/login-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/4907a6ddec9925e35a0a9e82d7399ccc52663121...650006c6eb7dba73a995cc03b0b2d7f5ca915bee)

Updates `anchore/workflows/.github/workflows/release-install-script.yaml` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/anchore/workflows/releases)
- [Commits](https://github.com/anchore/workflows/compare/15122524ced7906bfa9685eeae12e22647773ea6...b3e328b5ae31ba96297e2ed9a6124e5e6352a4c5)

Updates `zizmorcore/zizmor-action` from 0.5.5 to 0.5.6
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/a16621b09c6db4281f81a93cb393b05dcd7b7165...5f14fd08f7cf1cb1609c1e344975f152c7ee938d)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/codeql.yaml
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/check-version-available.yaml
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/check-gate.yaml
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/release-install-script.yaml
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 16:35:04 +00:00
witchcraze 4e86715c1a fix: improve julia classifier to find shared libs and beta versions (#4945)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2026-05-29 12:05:46 -04:00
Alex Goodman e8c6b7151e swap postgres signature check for rocky linux baseline rpm (#4941)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-05-26 10:11:38 -04:00
witchcraze 0fb8762f41 fix: improve deno classifier (#4939)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2026-05-25 11:58:10 -04:00
dependabot[bot] 58ddf74140 chore(deps): bump the actions-minor-patch group across 2 directories with 2 updates (#4936)
Bumps the actions-minor-patch group with 1 update in the / directory: [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).
Bumps the actions-minor-patch group with 1 update in the /.github/actions/bootstrap directory: [anchore/go-make](https://github.com/anchore/go-make).


Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.5
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/b1d7e1fb5de872772f31590499237e7cce841e8e...a16621b09c6db4281f81a93cb393b05dcd7b7165)

Updates `anchore/go-make` from 0.4.0 to 0.5.0
- [Release notes](https://github.com/anchore/go-make/releases)
- [Commits](https://github.com/anchore/go-make/compare/88c36505984649108439f13fb35dcaea4ce61d94...9de27be11ed73e2f9d5406a836a492b7d8aa1225)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: anchore/go-make
  dependency-version: 0.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 13:42:36 +00:00
dependabot[bot] b5d828ee14 chore(deps): bump github.com/containerd/containerd/v2 (#4935)
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.3.0 to 2.3.1.
- [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.0...v2.3.1)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.3.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-21 22:17:36 +00:00
Doug Clarke 1c4394fed0 fix: enhancement to java cataloger to consider .zap files as jar files (#4932)
* Enhancements to java cataloger to consider .zap files as jar files - Issue #4654
Signed-off-by: Doug Clarke <douglas.clarke@oracle.com>
2026-05-21 15:24:38 -04:00
anchore-oss-update-botandanchore-oss-update-bot f5c1a0befc chore(deps): update anchore dependencies (#4821)
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>
2026-05-20 19:50:47 +00:00
dependabot[bot] b1287d45d8 chore(deps): bump github.com/go-git/go-git/v5 from 5.19.0 to 5.19.1 (#4930)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.19.0 to 5.19.1.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Changelog](https://github.com/go-git/go-git/blob/main/HISTORY.md)
- [Commits](https://github.com/go-git/go-git/compare/v5.19.0...v5.19.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.19.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 16:11:18 +00:00
Alex Goodman d97216ff70 Remediate audit (#4929)
* remove slack notification on release

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* restrict cache usage

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-05-18 15:01:37 -04:00
dependabot[bot] c09a009bda chore(deps): bump the actions-minor-patch group across 1 directory with 4 updates (#4927)
Bumps the actions-minor-patch group with 4 updates in the / directory: [anchore/workflows/.github/workflows/codeql.yaml](https://github.com/anchore/workflows), [anchore/workflows/.github/workflows/check-version-available.yaml](https://github.com/anchore/workflows), [anchore/workflows/.github/workflows/check-gate.yaml](https://github.com/anchore/workflows) and [anchore/workflows/.github/workflows/release-install-script.yaml](https://github.com/anchore/workflows).


Updates `anchore/workflows/.github/workflows/codeql.yaml` from 0.5.0 to 0.6.0
- [Commits](https://github.com/anchore/workflows/compare/e8cee3a5916cebb68cda68b54c180f43394c1910...15122524ced7906bfa9685eeae12e22647773ea6)

Updates `anchore/workflows/.github/workflows/check-version-available.yaml` from 0.4.0 to 0.6.0
- [Commits](https://github.com/anchore/workflows/compare/8b2b1caf40e03933c6807e03b99e883e2ceb5ac8...15122524ced7906bfa9685eeae12e22647773ea6)

Updates `anchore/workflows/.github/workflows/check-gate.yaml` from 0.4.0 to 0.6.0
- [Commits](https://github.com/anchore/workflows/compare/8b2b1caf40e03933c6807e03b99e883e2ceb5ac8...15122524ced7906bfa9685eeae12e22647773ea6)

Updates `anchore/workflows/.github/workflows/release-install-script.yaml` from 0.4.0 to 0.6.0
- [Commits](https://github.com/anchore/workflows/compare/8b2b1caf40e03933c6807e03b99e883e2ceb5ac8...15122524ced7906bfa9685eeae12e22647773ea6)

---
updated-dependencies:
- dependency-name: anchore/workflows/.github/workflows/codeql.yaml
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/check-version-available.yaml
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/check-gate.yaml
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
- dependency-name: anchore/workflows/.github/workflows/release-install-script.yaml
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-18 16:13:54 +00:00
Alex Goodman d61af0abab Port to go-make (#4923)
* port to go-make

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refresh fixtures on running unit tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* address refresh cache issues with old now-gitignored files

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-05-18 11:59:55 -04:00
anchore-oss-update-botandanchore-oss-update-bot 89cda82263 chore(deps): update CPE dictionary index (#4925)
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>
2026-05-18 10:21:30 -04:00
dependabot[bot] ee6ace36d1 chore(deps): bump the actions-minor-patch group across 1 directory with 2 updates (#4920)
Bumps the actions-minor-patch group with 2 updates in the / directory: [runs-on/action](https://github.com/runs-on/action) and [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer).


Updates `runs-on/action` from 2.1.0 to 2.1.2
- [Release notes](https://github.com/runs-on/action/releases)
- [Commits](https://github.com/runs-on/action/compare/742bf56072eb4845a0f94b3394673e4903c90ff0...d141ef83eb66d096ce8afc767e09115a65c63b60)

Updates `sigstore/cosign-installer` from 4.1.1 to 4.1.2
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003...6f9f17788090df1f26f669e9d70d6ae9567deba6)

---
updated-dependencies:
- dependency-name: runs-on/action
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-15 13:34:58 +00:00
witchcraze e2e5e223ab feat: mysqld, ndbd, ndbmtd and ndb_mgmd classifier (#4907)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2026-05-14 11:29:42 -04:00
4579d11abc fix: detect compressed kernel modules (.ko.gz, .ko.xz, .ko.zst) (#4740)
* fix: detect compressed kernel modules (.ko.gz, .ko.xz, .ko.zst)

The linux-kernel-cataloger only matched plain *.ko files, missing
compressed modules produced when CONFIG_MODULE_COMPRESS is enabled
(common on Debian 13 / Ubuntu 24.04+). This resulted in near-zero
module packages being reported for such filesystems.

Changes:
- Add *.ko.gz, *.ko.xz, *.ko.zst glob patterns to both the cataloger
  and capabilities.yaml so the file resolver picks up compressed modules
- Add decompressedModuleReader() which detects the extension and
  transparently decompresses via compress/gzip, ulikunitz/xz, or
  klauspost/compress/zstd before handing the ELF bytes to the existing
  parseLinuxKernelModuleMetadata parser
- Promote github.com/klauspost/compress from indirect to direct dependency
- Add unit tests covering all three compression formats plus the
  uncompressed baseline, using a programmatically generated minimal ELF

Fixes #4721

Signed-off-by: Will Bates <william.bates11@outlook.com>

* address reading archives into memory

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Will Bates <william.bates11@outlook.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Will Bates <william.bates11@outlook.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-05-13 13:44:18 -04:00
756 changed files with 83785 additions and 6293 deletions
+7 -104
View File
@@ -1,67 +1,10 @@
# only pull in version updates that were released more than a week ago (low-pass filter for quickly-retracted releases)
cooldown: 7d
# Most tools (binny, chronicle, cosign, golangci-lint, goreleaser, gosimports,
# bouncer, quill, syft, task, gh) are inherited from anchore/go-make's embedded
# .binny.yaml — see https://github.com/anchore/go-make. Only syft-specific tools
# or version overrides should live here.
tools:
## internal tools ############################################################################
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
- name: binny
version:
want: v0.13.0
method: github-release
with:
repo: anchore/binny
# used to produce SBOMs during release
- name: syft
version:
want: v1.42.3
method: github-release
with:
repo: anchore/syft
# used to sign mac binaries at release
- name: quill
version:
want: v0.7.1
method: github-release
with:
repo: anchore/quill
# used at release to generate the changelog
- name: chronicle
version:
want: v0.8.0
method: github-release
with:
repo: anchore/chronicle
## external tools ############################################################################
# used for linting
- name: golangci-lint
version:
want: v2.11.4
method: github-release
with:
repo: golangci/golangci-lint
# used for showing the changelog at release
- name: glow
version:
want: v2.1.1
method: github-release
with:
repo: charmbracelet/glow
# used for signing the checksums file at release
- name: cosign
version:
want: v3.0.5
method: github-release
with:
repo: sigstore/cosign
# used in integration tests to verify JSON schemas
- name: yajsv
version:
@@ -70,58 +13,18 @@ tools:
with:
repo: neilpa/yajsv
# used to release all artifacts
- name: goreleaser
version:
want: v2.15.2
method: github-release
with:
repo: goreleaser/goreleaser
# used for organizing imports during static analysis
- name: gosimports
version:
want: v0.3.8
method: github-release
with:
repo: rinchsan/gosimports
# used during static analysis for license compliance
- name: bouncer
version:
want: v0.4.0
method: github-release
with:
repo: wagoodman/go-bouncer
# used for running all local and CI tasks
- name: task
version:
want: v3.49.1
method: github-release
with:
repo: go-task/task
# used for triggering a release
- name: gh
version:
want: v2.89.0
method: github-release
with:
repo: cli/cli
# used to upload test fixture cache
- name: oras
version:
want: v1.3.1
want: v1.3.4
method: github-release
with:
repo: oras-project/oras
# used to upload test fixture cache
# used to parse JSON/YAML annotations on the fixture cache image
- name: yq
version:
want: v4.52.5
want: v4.53.6
method: github-release
with:
repo: mikefarah/yq
+7
View File
@@ -0,0 +1,7 @@
# check out text files with lf on every platform (regardless of core.autocrlf), letting git detect binary files
* text=auto eol=lf
# fixtures that must keep their crlf line endings byte for byte
syft/linux/testdata/unprintable -text
syft/pkg/cataloger/bitnami/testdata/no-rel/opt/bitnami/redis/.spdx-redis.spdx -text
syft/pkg/cataloger/java/testdata/jar-metadata/**/META-INF/MANIFEST.MF -text
+46 -35
View File
@@ -1,55 +1,45 @@
name: "Bootstrap"
description: "Bootstrap all tools and dependencies"
description: "Bootstrap all syft tools and dependencies on top of go-make's setup action"
# This action is a thin wrapper around anchore/go-make/.github/actions/setup which
# already handles checkout, setup-go, restore-only build/mod cache, and tool cache.
# We add the syft-specific extras here: apt packages and the test fixture cache.
inputs:
go-version:
description: "Go version to install"
description: "Go version to install (passed to go-make/setup)"
required: true
default: "1.26.2"
go-dependencies:
description: "Download go dependencies"
cache-key-prefix:
description: "Prefix all cache keys with this value (passed to go-make/setup)"
required: true
default: "v1"
cache-enabled:
description: "Enable build/mod and tool caching (passed to go-make/setup)"
required: true
default: "true"
cache-key-prefix:
description: "Prefix all cache keys with this value"
required: true
default: "53ac821810"
download-test-fixture-cache:
description: "Download test fixture cache from OCI and github actions"
required: true
default: "false"
tools:
description: "whether to install tools"
default: "true"
bootstrap-apt-packages:
description: "Space delimited list of tools to install via apt"
default: "libxml2-utils"
runs:
using: "composite"
steps:
# note: go mod and build is automatically cached on default with v4+
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
if: inputs.go-version != ''
- name: Setup go + go-make tooling
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
with:
go-version: ${{ inputs.go-version }}
check-latest: true
- name: Restore tool cache
if: inputs.tools == 'true'
id: tool-cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ github.workspace }}/.tool
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
- name: Install project tools
cache-key-prefix: ${{ inputs.cache-key-prefix }}
cache-enabled: ${{ inputs.cache-enabled }}
- name: Install binny-managed tools
shell: bash
if: inputs.tools == 'true'
run: |
make tools
.tool/binny list
.tool/binny check
- name: Install go dependencies
if: inputs.go-dependencies == 'true'
shell: bash
run: make ci-bootstrap-go
run: make binny:install
- name: Install apt packages
if: inputs.bootstrap-apt-packages != ''
shell: bash
@@ -57,13 +47,34 @@ runs:
APT_PACKAGES: ${{ inputs.bootstrap-apt-packages }}
run: |
IFS=' ' read -ra packages <<< "$APT_PACKAGES"
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${packages[@]}"
# the AMI already ships most of these, and `apt update` costs ~40MB of index (minutes,
# when archive.ubuntu.com throttles us) to find that out. only touch apt if we must.
missing=()
for pkg in "${packages[@]}"; do
dpkg -s "$pkg" >/dev/null 2>&1 || missing+=("$pkg")
done
if [ ${#missing[@]} -eq 0 ]; then
echo "all apt packages already present: ${packages[*]}"
exit 0
fi
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${missing[@]}"
# ORAS cache: restore-only on non-default branches / forks
- name: Restore ORAS cache from github actions
if: inputs.download-test-fixture-cache == 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: ${{ inputs.download-test-fixture-cache == 'true' && (github.ref != format('refs/heads/{0}', github.event.repository.default_branch) || github.event.repository.fork == true) }}
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.tmp/oras-cache
key: ${{ inputs.cache-key-prefix }}-oras-cache
# ORAS cache: restore + save on the default branch of the canonical repo only.
- name: Restore and save ORAS cache from github actions
if: ${{ inputs.download-test-fixture-cache == 'true' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.event.repository.fork == false }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.tmp/oras-cache
key: ${{ inputs.cache-key-prefix }}-oras-cache
- name: Download test fixture cache
if: inputs.download-test-fixture-cache == 'true'
shell: bash
+6 -26
View File
@@ -1,20 +1,13 @@
# Dependabot configuration
#
# Grouping behavior (see inline comments for details):
# - Minor + patch updates: grouped into a single PR per ecosystem
# - Major version bumps: individual PR per dependency
# - Security updates: individual PR per dependency
#
# Note: "patch" refers to semver version bumps (1.2.3 -> 1.2.4), not security fixes.
# Security updates are identified separately via GitHub's Advisory Database and
# can be any version bump (patch, minor, or major) that fixes a known CVE.
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
directories:
- "/"
- "/.make"
cooldown:
default-days: 7
schedule:
@@ -28,14 +21,9 @@ updates:
- dependency-name: "github.com/aquasecurity/go-version"
- dependency-name: "github.com/knqyf263/go-apk-version"
- dependency-name: "github.com/knqyf263/go-deb-version"
groups:
go-minor-patch:
applies-to: version-updates # security updates get individual PRs
patterns:
- "*"
update-types: # major omitted, gets individual PRs
- "minor"
- "patch"
# v1 changed SlicesAsSets hashing, which changes every artifact ID syft produces
- dependency-name: "github.com/gohugoio/hashstructure"
update-types: ["version-update:semver-major"]
- package-ecosystem: "github-actions"
directories:
@@ -49,11 +37,3 @@ updates:
open-pull-requests-limit: 10
labels:
- "dependencies"
groups:
actions-minor-patch:
applies-to: version-updates # security updates get individual PRs
patterns:
- "*"
update-types: # major omitted, gets individual PRs
- "minor"
- "patch"
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
red=$(tput setaf 1)
bold=$(tput bold)
normal=$(tput sgr0)
# assert we are running in CI (or die!)
if [[ -z "$CI" ]]; then
echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}"
exit 1
fi
-36
View File
@@ -1,36 +0,0 @@
#!/usr/bin/env python3
import subprocess
import sys
import shlex
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
if len(sys.argv) < 3:
print("Usage: coverage.py [threshold] [go-coverage-report]")
sys.exit(1)
threshold = float(sys.argv[1])
report = sys.argv[2]
args = shlex.split(f"go tool cover -func {report}")
p = subprocess.run(args, capture_output=True, text=True)
percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", ""))
print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}")
if percent_coverage < threshold:
print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}")
sys.exit(1)
+51 -34
View File
@@ -1,35 +1,45 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import glob
import sys
import json
import hashlib
import json
import os
import sys
IGNORED_PREFIXES = []
def find_fingerprints_and_check_dirs(base_dir):
all_fingerprints = set(glob.glob(os.path.join(base_dir, '**', 'test*', '**', '*.fingerprint'), recursive=True))
all_fingerprints = set(
glob.glob(
os.path.join(base_dir, "**", "test*", "**", "*.fingerprint"), recursive=True
)
)
all_fingerprints = {os.path.relpath(fp) for fp in all_fingerprints
if not any(fp.startswith(prefix) for prefix in IGNORED_PREFIXES)}
all_fingerprints = {
os.path.relpath(fp)
for fp in all_fingerprints
if not any(fp.startswith(prefix) for prefix in IGNORED_PREFIXES)
}
if not all_fingerprints:
show("No .fingerprint files or cache directories found.")
exit(1)
missing_content = []
orphan_fingerprints = []
empty_content = []
valid_paths = set()
fingerprint_contents = []
for fingerprint in all_fingerprints:
path = fingerprint.replace('.fingerprint', '')
path = fingerprint.replace(".fingerprint", "")
if not os.path.exists(path):
missing_content.append(path)
# paired content path is entirely missing — the .fingerprint is likely
# leftover from a moved/deleted source (testdata trees are git-ignored,
# so they persist locally across rename refactors)
orphan_fingerprints.append(fingerprint)
continue
if not os.path.isdir(path):
@@ -39,13 +49,13 @@ def find_fingerprints_and_check_dirs(base_dir):
if os.listdir(path):
valid_paths.add(path)
else:
missing_content.append(path)
empty_content.append(path)
with open(fingerprint, 'r') as f:
with open(fingerprint, "r") as f:
content = f.read().strip()
fingerprint_contents.append((fingerprint, content))
return sorted(valid_paths), missing_content, fingerprint_contents
return sorted(valid_paths), empty_content, orphan_fingerprints, fingerprint_contents
def parse_fingerprint_contents(fingerprint_content):
@@ -59,7 +69,9 @@ def parse_fingerprint_contents(fingerprint_content):
def calculate_sha256(fingerprint_contents):
sorted_fingerprint_contents = sorted(fingerprint_contents, key=lambda x: x[0])
concatenated_contents = ''.join(content for _, content in sorted_fingerprint_contents)
concatenated_contents = "".join(
content for _, content in sorted_fingerprint_contents
)
sha256_hash = hashlib.sha256(concatenated_contents.encode()).hexdigest()
@@ -68,7 +80,7 @@ def calculate_sha256(fingerprint_contents):
def calculate_file_sha256(file_path):
sha256_hash = hashlib.sha256()
with open(file_path, 'rb') as f:
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
@@ -79,17 +91,28 @@ def show(*s: str):
def main(file_path: str | None):
base_dir = '.'
valid_paths, missing_content, fingerprint_contents = find_fingerprints_and_check_dirs(base_dir)
base_dir = "."
valid_paths, empty_content, orphan_fingerprints, fingerprint_contents = (
find_fingerprints_and_check_dirs(base_dir)
)
if missing_content:
show("The following paths are missing or have no content, but have corresponding .fingerprint files:")
for path in sorted(missing_content):
if empty_content:
show(
"The following paths exist but are empty, and have corresponding .fingerprint files:"
)
for path in sorted(empty_content):
show(f"- {path}")
# when adding new cache directories there is a time where it is not possible to have this directory without
# running the tests first... but this step is a prerequisite for running the tests. We should not block on this.
# show("Please ensure these paths exist and have content if they are directories.")
# exit(1)
if orphan_fingerprints:
show(
"The following .fingerprint files reference paths that no longer exist "
"(likely leftover from a moved/deleted cataloger — safe to delete, "
"or run `task prune-orphan-fingerprints`):"
)
for fp in sorted(orphan_fingerprints):
show(f"- {fp}")
sha256_hash = calculate_sha256(fingerprint_contents)
@@ -101,30 +124,24 @@ def main(file_path: str | None):
file_digest = calculate_file_sha256(fingerprint_file)
# Parse the fingerprint file to get the digest/path tuples
with open(fingerprint_file, 'r') as f:
with open(fingerprint_file, "r") as f:
fingerprint_content = f.read().strip()
input_map = parse_fingerprint_contents(fingerprint_content)
paths_with_digests.append({
"path": path,
"digest": file_digest,
"input": input_map
})
paths_with_digests.append(
{"path": path, "digest": file_digest, "input": input_map}
)
except Exception as e:
show(f"Error processing {fingerprint_file}: {e}")
raise e
output = {
"digest": sha256_hash,
"paths": paths_with_digests
}
output = {"digest": sha256_hash, "paths": paths_with_digests}
content = json.dumps(output, indent=2, sort_keys=True)
if file_path:
with open(file_path, 'w') as f:
with open(file_path, "w") as f:
f.write(content)
print(content)
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -eu
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
trap "cp -p ${ORIGINAL_STATE_DIR}/* ./ && git update-index -q --refresh && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
# capturing original state of files...
cp go.mod go.sum "${ORIGINAL_STATE_DIR}"
# capturing state of go.mod and go.sum after running go mod tidy...
go mod tidy
cp go.mod go.sum "${TIDY_STATE_DIR}"
set +e
# detect difference between the git HEAD state and the go mod tidy state
DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod")
DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum")
if [[ -n "${DIFF_MOD}" || -n "${DIFF_SUM}" ]]; then
echo "go.mod diff:"
echo "${DIFF_MOD}"
echo "go.sum diff:"
echo "${DIFF_SUM}"
echo ""
printf "FAILED! go.mod and/or go.sum are NOT tidy; please run 'go mod tidy'.\n\n"
exit 1
fi
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
Remove orphan *.fingerprint files left behind by moved/deleted catalogers.
A fingerprint is considered orphaned when:
1. its paired content path (the fingerprint path with `.fingerprint` stripped)
does not exist, AND
2. the nearest ancestor `testdata/` directory has no `Makefile` claiming
responsibility for generating that path.
The second condition is the safety check: if there is a Makefile, the
fingerprint is "live" and might just be waiting for fixtures to be built —
leave it alone. Without a Makefile, nothing in-repo will ever regenerate
the content, so the fingerprint is dead weight that triggers spurious
"missing path" warnings.
Empty parent directories are also pruned after removing the fingerprint.
Use --dry-run to preview without deleting.
"""
from __future__ import annotations
import argparse
import glob
import os
import sys
def find_ancestor_testdata(path: str) -> str | None:
d = os.path.dirname(path)
while d and d not in (".", os.sep):
if os.path.basename(d) == "testdata":
return d
d = os.path.dirname(d)
return None
def is_orphan(fingerprint: str) -> bool:
paired = fingerprint[: -len(".fingerprint")]
if os.path.exists(paired):
return False
testdata_dir = find_ancestor_testdata(fingerprint)
if testdata_dir and os.path.isfile(os.path.join(testdata_dir, "Makefile")):
# a Makefile exists that may regenerate this — not safe to prune
return False
return True
def prune_empty_parents(start: str, stop_at: str = ".") -> list[str]:
removed = []
d = os.path.dirname(start)
stop_at = os.path.abspath(stop_at)
while d and os.path.abspath(d) != stop_at:
try:
if not os.listdir(d):
os.rmdir(d)
removed.append(d)
d = os.path.dirname(d)
else:
break
except OSError:
break
return removed
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be removed without deleting anything",
)
args = parser.parse_args()
all_fingerprints = glob.glob("**/test*/**/*.fingerprint", recursive=True)
orphans = sorted(fp for fp in all_fingerprints if is_orphan(fp))
if not orphans:
print("no orphan fingerprints found")
return 0
verb = "would remove" if args.dry_run else "removing"
print(f"{verb} {len(orphans)} orphan fingerprint(s):")
for fp in orphans:
print(f"- {fp}")
if args.dry_run:
continue
try:
os.remove(fp)
except OSError as e:
print(f" ! failed to remove: {e}", file=sys.stderr)
continue
for d in prune_empty_parents(fp):
print(f" (also removed empty dir {d})")
return 0
if __name__ == "__main__":
sys.exit(main())
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
set -eu
bold=$(tput bold)
normal=$(tput sgr0)
GH_CLI=.tool/gh
if ! [ -x "$(command -v $GH_CLI)" ]; then
echo "The GitHub CLI could not be found. run: make bootstrap"
exit 1
fi
$GH_CLI auth status
# set the default repo in cases where multiple remotes are defined
$GH_CLI repo set-default anchore/syft
export GITHUB_TOKEN="${GITHUB_TOKEN-"$($GH_CLI auth token)"}"
# we need all of the git state to determine the next version. Since tagging is done by
# the release pipeline it is possible to not have all of the tags from previous releases.
git fetch --tags
# populates the CHANGELOG.md and VERSION files
echo "${bold}Generating changelog...${normal}"
make changelog 2> /dev/null
NEXT_VERSION=$(cat VERSION)
if [[ "$NEXT_VERSION" == "" || "${NEXT_VERSION}" == "(Unreleased)" ]]; then
echo "Could not determine the next version to release. Exiting..."
exit 1
fi
while true; do
read -p "${bold}Do you want to trigger a release for version '${NEXT_VERSION}'?${normal} [y/n] " yn
case $yn in
[Yy]* ) echo; break;;
[Nn]* ) echo; echo "Cancelling release..."; exit;;
* ) echo "Please answer yes or no.";;
esac
done
echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
echo
$GH_CLI workflow run release.yaml -f version=${NEXT_VERSION}
echo
echo "${bold}Waiting for release to start...${normal}"
sleep 10
set +e
echo "${bold}Head to the release workflow to monitor the release:${normal} $($GH_CLI run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
id=$($GH_CLI run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
$GH_CLI run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" $GH_CLI run view $id --log-failed)
+1 -1
View File
@@ -13,7 +13,7 @@ permissions: {}
jobs:
analyze:
name: Analyze
uses: anchore/workflows/.github/workflows/codeql.yaml@e8cee3a5916cebb68cda68b54c180f43394c1910 # v0.5.0
uses: anchore/workflows/.github/workflows/codeql.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
permissions:
security-events: write
packages: read
+48 -49
View File
@@ -21,20 +21,27 @@ on:
options:
- "all"
- "install-script-only"
skip-checks:
description: skip the check-gate (release even if checks haven't passed on main)
type: boolean
default: false
required: false
jobs:
version-available:
if: ${{ github.event.inputs.phase == 'all' }}
uses: anchore/workflows/.github/workflows/check-version-available.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
permissions:
contents: read # required for fetching tags
uses: anchore/workflows/.github/workflows/check-version-available.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
with:
version: ${{ github.event.inputs.version }}
check-gate:
if: ${{ github.event.inputs.phase == 'all' }}
if: ${{ github.event.inputs.phase == 'all' && !inputs.skip-checks }}
permissions:
checks: read # required for getting the status of specific check names
uses: anchore/workflows/.github/workflows/check-gate.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
contents: read
checks: read # required for getting the status of specific check names
uses: anchore/workflows/.github/workflows/check-gate.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
with:
# these are checks that should be run on pull-request and merges to main.
# we do NOT want to kick off a release if these have not been verified on main.
@@ -42,21 +49,33 @@ jobs:
checks: '["Acceptance tests (Linux)", "Acceptance tests (Mac)", "Build snapshot artifacts", "CLI tests (Linux)", "Integration tests", "Static analysis", "Unit tests"]'
release:
needs: [ check-gate, version-available ]
if: ${{ github.event.inputs.phase == 'all' }}
needs: [check-gate, version-available]
# run even when check-gate is skipped, but never when version-available
# failed/was skipped, nor when check-gate failed or was cancelled. note:
# always() disables the implicit success() gate on ALL needs, so the
# version-available requirement must be re-asserted explicitly here.
if: >-
${{ always()
&& needs.version-available.result == 'success'
&& !contains(fromJSON('["failure", "cancelled"]'), needs.check-gate.result) }}
environment: release
# runs-on.com: compute instances for parallel builds
# spot disabled: reliability for build workflows (used for releases too)
# goreleaser uses parallelism of 12, so we need more CPUs
# s3-cache: faster actions cache
# tmpfs: faster io-intensive workflows
runs-on: runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache+tmpfs
# volume: enlarge dist/ disk -- the default 40GB EBS root fills up during the multi-arch
# image + binary build and OOMs (runs out of disk); size it well above peak usage.
# provisioned throughput/iops above the gp3 free baseline (125mbs/3000iops) approximate
# the tmpfs IO speed on disk, for ~1c/run.
# note: tmpfs intentionally omitted -- a RAM-backed dist/ never touches the enlarged
# volume and competes with the build's memory peak, so keep dist/ on disk.
runs-on: runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache/volume=120gb:gp3:500mbs:4000iops
permissions:
contents: write
packages: write
# required for goreleaser signs section with cosign
id-token: write
contents: write # required for creating the GitHub release and pushing the version tag
packages: write # required for publishing release artifacts to GitHub packages
id-token: write # required for keyless signing (cosign/sigstore OIDC)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
fetch-depth: 0
persist-credentials: true
@@ -64,32 +83,30 @@ jobs:
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
# dockers_v2 builds multi-platform manifests in a single buildx invocation, which requires a
# docker-container driver builder (the default builder on the runner cannot build multi-platform).
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
with:
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag release
run: |
git config --global user.name "anchoreci"
git config --global user.email "anchoreci@users.noreply.github.com"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin --tags
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.event.inputs.version }}
- name: Build & publish release artifacts
run: make ci-release
env:
# for pushing tags (does not inherit workflow permissions)
TAG_TOKEN: ${{ secrets.TAG_TOKEN }}
RELEASE_VERSION: ${{ github.event.inputs.version }}
# for mac signing and notarization...
QUILL_SIGN_P12: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_CHAIN }}
QUILL_SIGN_PASSWORD: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_PASS }}
@@ -101,36 +118,18 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 #v0.24.0
- uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 #v0.24.2
continue-on-error: true
with:
file: go.mod
artifact-name: sbom.spdx.json
- name: Notify Slack of new release
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c #v3.0.3
continue-on-error: true
with:
webhook: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
text: "A new Syft release has been published: https://github.com/anchore/syft/releases/tag/${{ github.event.inputs.version }}"
blocks:
- type: section
text:
type: mrkdwn
text: |
*A new Syft release has been published* :rocket:
• Release: <https://github.com/anchore/syft/releases/tag/${{ github.event.inputs.version }}|${{ github.event.inputs.version }}>
• Repo: `${{ github.repository }}`
• Workflow: `${{ github.workflow }}`
• Event: `${{ github.event_name }}`
if: ${{ success() }}
release-install-script:
needs: [ release ]
needs: [release]
if: ${{ always() && (needs.release.result == 'success' || github.event.inputs.phase == 'install-script-only') }}
uses: "anchore/workflows/.github/workflows/release-install-script.yaml@main"
permissions:
contents: read # required for the reusable workflow to check out the repo and publish the install script
uses: anchore/workflows/.github/workflows/release-install-script.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
with:
tag: ${{ github.event.inputs.version }}
secrets:
@@ -20,12 +20,12 @@ jobs:
contents: read
security-events: write # for uploading SARIF results
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Run zizmor"
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
# there is a pass/fail gate as a repo ruleset (if there is no ruleset configured then the action will pass by default)
advanced-security: true
+64 -23
View File
@@ -21,13 +21,17 @@ jobs:
name: "Static analysis"
# runs-on.com: memory & general purpose instances for testing
# spot enabled: ok to interrupt non-production workloads
# s3-cache: faster actions cache
# tmpfs: faster io-intensive workflows
runs-on: &test-runner "runs-on=${{ github.run_id }}/cpu=4+8/ram=32+128/family=r5+r6+r7+r8+m4+m5+m6+m7+m8/spot=price-capacity-optimized/extras=s3-cache+tmpfs"
# tmpfs intentionally omitted -- it RAM-backs /home/runner, /tmp and /var/lib/docker at
# 100% of instance memory, starving `make unit` of headroom to link with -race.
# volume: the caches and docker images tmpfs used to hold. gp3 baseline throughput is
# enough; a 4 vCPU instance can't pull much more than that anyway.
# note: s3-cache intentionally omitted -- PR runs are untrusted and must not write to the
# shared cache backend that the trusted release workflow reads from (cache poisoning).
runs-on: &test-runner "runs-on=${{ github.run_id }}/cpu=4+8/ram=16+32/family=m6+m7+m8+c6+c7+c8/spot=price-capacity-optimized/volume=100gb"
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -45,11 +49,29 @@ jobs:
Unit-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Unit tests"
runs-on: ubuntu-latest
needs: [Unit-Test-Linux, Unit-Test-Windows]
# a skipped job satisfies a required check, so this job must always run and fail explicitly
# unless every dependency succeeded (otherwise a failing PR can still be auto-merged)
if: ${{ always() }}
steps:
- name: Require all unit test jobs to succeed
env:
NEEDS: ${{ toJSON(needs) }}
run: |
echo "$NEEDS" | jq -e 'length > 0 and all(.[]; .result == "success")' > /dev/null || {
echo "$NEEDS" | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
exit 1
}
echo All unit tests passed!
Unit-Test-Linux:
name: "Unit tests (Linux)"
runs-on: *test-runner
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -64,6 +86,27 @@ jobs:
- name: Check for capability drift
run: make check-capability-drift
Unit-Test-Windows:
name: "Unit tests (Windows)"
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
# go-make's setup only (not ./.github/actions/bootstrap): no apt, binny tools, or fixture cache are needed
- name: Setup go + go-make tooling
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
with:
# keep in sync with the go-version default in ./.github/actions/bootstrap
go-version: "1.26.2"
# the full unit suite leans on linux docker fixtures, so this runs the windows-relevant subset
- name: Run unit tests
run: make unit:windows
Integration-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Integration tests"
@@ -71,7 +114,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -91,16 +134,20 @@ jobs:
# runs-on.com: compute instances for parallel builds
# spot disabled: reliability for build workflows (used for releases too)
# goreleaser uses parallelism of 12, so we need more CPUs
# s3-cache: faster actions cache
# tmpfs: faster io-intensive workflows
runs-on: "runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache+tmpfs"
# tmpfs intentionally omitted -- a RAM-backed dist/ competes with the goreleaser
# build's memory peak (parallel binaries + multi-arch image assembly) and OOMs the
# instance; keep dist/ on disk.
# note: s3-cache intentionally omitted -- PR runs are untrusted and must not write to the
# shared cache backend that the trusted release workflow reads from (cache poisoning).
# volume: enlarge dist/ disk -- the default 40GB EBS root fills up during the multi-arch
# image + binary build and OOMs (runs out of disk); size it well above peak usage.
# provisioned throughput/iops above the gp3 free baseline (125mbs/3000iops) approximate
# the tmpfs IO speed on disk, for ~1c/run.
runs-on: "runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/volume=120gb:gp3:500mbs:4000iops"
permissions:
contents: read
steps:
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -130,10 +177,7 @@ jobs:
permissions:
contents: read
steps:
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -175,9 +219,9 @@ jobs:
contents: read
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -211,10 +255,7 @@ jobs:
permissions:
contents: read
steps:
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
-5
View File
@@ -1,9 +1,4 @@
rules:
unpinned-uses:
config:
policies:
# anchore/workflows is an internal repository; using @main is acceptable
anchore/*: any
dangerous-triggers:
ignore:
# Safe use of pull_request_target - only runs trusted scripts from base repo,
+5 -2
View File
@@ -3,6 +3,8 @@ go.work
go.work.sum
.tool-versions
.python-version
.mise.toml
.env
# app configuration
/.syft.yaml
@@ -18,8 +20,11 @@ bin/
/.tool
/.task
/generate
/spec
/specs
mise.toml
.make/.make
.conductor
# changelog generation
CHANGELOG.md
@@ -76,5 +81,3 @@ cosign.pub
__pycache__/
*.py[cod]
*$py.class
+26 -3
View File
@@ -7,6 +7,7 @@ linters:
- asciicheck
- bodyclose
- copyloopvar
- depguard
- dogsled
- dupl
- errcheck
@@ -29,9 +30,20 @@ linters:
- unused
- whitespace
settings:
depguard:
rules:
yaml:
deny:
# gopkg.in/yaml.v3 is unmaintained and panics on some malformed input (see #5327)
- pkg: gopkg.in/yaml.v3
desc: use go.yaml.in/yaml/v3 instead
funlen:
lines: 70
statements: 50
goconst:
# the default of 3 turns every repeated literal in a lookup table or a test fixture into a finding.
# 5 keeps the ones worth hoisting without flagging data that reads better written out.
min-occurrences: 5
gocritic:
enabled-checks:
- deferInLoop
@@ -54,6 +66,12 @@ linters:
- linters:
- revive
text: "var-naming: avoid package names that conflict"
# cpegenerate is almost entirely lookup tables mapping artifact names to vendors, products, and
# maven group ids. The repeated strings are the data itself, so hoisting them to constants would
# replace a readable table with indirection for no gain.
- linters:
- goconst
path: syft/pkg/cataloger/internal/cpegenerate/
paths:
- third_party$
- builtin$
@@ -61,7 +79,6 @@ linters:
# do not enable...
# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused".
# - depguard # We don't have a configuration for this yet
# - goprintffuncname # does not catch all cases and there are exceptions
# - nakedret # does not catch all cases and should not fail a build
# - gochecknoglobals
@@ -70,7 +87,6 @@ linters:
# - godot
# - godox
# - goerr113
# - goimports # we're using gosimports now instead to account for extra whitespaces (see https://github.com/golang/go/issues/20818)
# - golint # deprecated
# - gomnd # this is too aggressive
# - interfacer # this is a good idea, but is no longer supported and is prone to false positives
@@ -96,11 +112,18 @@ issues:
formatters:
enable:
- gci
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
settings:
gci:
# See https://golangci-lint.run/docs/formatters/configuration/#gci
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- prefix(github.com/anchore)
+79 -300
View File
@@ -73,306 +73,88 @@ brews:
description: *description
license: "Apache License 2.0"
dockers:
# production images...
- image_templates:
- anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-amd64
goarch: amd64
dockers_v2:
# production images (scratch base, root)
- id: production
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
ids: &docker-ids
- linux-build
images: &docker-images
- anchore/syft
- ghcr.io/anchore/syft
platforms: &docker-platforms
- linux/amd64
- linux/arm64
- linux/ppc64le
- linux/riscv64
- linux/s390x
labels: &docker-labels
"org.opencontainers.image.created": "{{.Date}}"
"org.opencontainers.image.title": "syft"
"org.opencontainers.image.description": "CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
"org.opencontainers.image.source": "{{.GitURL}}"
"org.opencontainers.image.revision": "{{.FullCommit}}"
"org.opencontainers.image.vendor": "Anchore, Inc."
"org.opencontainers.image.version": "{{.Version}}"
"org.opencontainers.image.licenses": "Apache-2.0"
"io.artifacthub.package.readme-url": "https://raw.githubusercontent.com/anchore/syft/main/README.md"
"io.artifacthub.package.logo-url": "https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
"io.artifacthub.package.license": "Apache-2.0"
tags:
- latest
- "{{.Tag}}"
# DEBIAN_VERSION 13 (trixie) is the first distroless release with a riscv64 base image
# and is a superset of debian 12 for the other targeted arches; docker_v2 builds all
# platforms in a single buildx invocation, so build_args cannot vary per platform.
build_args: &docker-build-args
DEBIAN_VERSION: "13"
# disable provenance attestations to keep the manifest free of unknown/unknown entries
flags: &docker-flags
- "--provenance=false"
# SBOMs are produced separately for the archives (see the sboms section)
sbom: &docker-sbom "false"
- image_templates:
- anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
goarch: arm64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
goarch: ppc64le
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
goarch: riscv64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/riscv64"
- "--build-arg=DEBIAN_VERSION=13"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-s390x
- ghcr.io/anchore/syft:{{.Tag}}-s390x
goarch: s390x
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
# nonroot images...
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
goarch: amd64
# nonroot images
- id: nonroot
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
ids: *docker-ids
images: *docker-images
platforms: *docker-platforms
labels: *docker-labels
tags:
- nonroot
- "{{.Tag}}-nonroot"
build_args: *docker-build-args
flags: *docker-flags
sbom: *docker-sbom
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
goarch: arm64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
goarch: riscv64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/riscv64"
- "--build-arg=DEBIAN_VERSION=13"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-s390x
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
goarch: s390x
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
# debug images...
- image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
goarch: amd64
# debug images (root)
- id: debug
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
ids: *docker-ids
images: *docker-images
platforms: *docker-platforms
labels: *docker-labels
tags:
- debug
- "{{.Tag}}-debug"
build_args: *docker-build-args
flags: *docker-flags
sbom: *docker-sbom
- image_templates:
- anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
goarch: arm64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
goarch: riscv64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/riscv64"
- "--build-arg=DEBIAN_VERSION=13"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
goarch: s390x
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:{{.Tag}}-amd64
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-riscv64
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}-amd64
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-riscv64
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-s390x
# nonroot images...
- name_template: anchore/syft:nonroot
image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-riscv64
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:nonroot
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: anchore/syft:{{.Tag}}-nonroot
image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-riscv64
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
# debug images...
- name_template: anchore/syft:debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-riscv64
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}-debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-riscv64
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
# debug-nonroot images
- id: debug-nonroot
dockerfile: Dockerfile.debug-nonroot
ids: *docker-ids
images: *docker-images
platforms: *docker-platforms
labels: *docker-labels
tags:
- debug-nonroot
- "{{.Tag}}-debug-nonroot"
build_args: *docker-build-args
flags: *docker-flags
sbom: *docker-sbom
sboms:
- artifacts: archive
@@ -385,16 +167,13 @@ sboms:
- "--output"
- "json=$document"
# the signature, certificate, and transparency log proof all land in a single sigstore bundle.
signs:
- cmd: .tool/cosign
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
signature: "${artifact}.sigstore.json"
args:
- "sign-blob"
- "--use-signing-config=false"
- "--oidc-issuer=https://token.actions.githubusercontent.com"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "--bundle=${signature}"
- "${artifact}"
- "--yes"
artifacts: checksum
+12
View File
@@ -0,0 +1,12 @@
module github.com/anchore/syft/.make
go 1.25.8
require github.com/anchore/go-make v0.8.1
require (
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
golang.org/x/mod v0.40.0 // indirect
golang.org/x/sys v0.47.0 // indirect
)
+10
View File
@@ -0,0 +1,10 @@
github.com/anchore/go-make v0.8.1 h1:GAhPXR+9qVQhFB1pfVlHFOzxAt3dTDTUIA7Tp7nx9tg=
github.com/anchore/go-make v0.8.1/go.mod h1:xALbVsPsDoJnJxdkjPlxOLZV7GbAdT5Pv3gd0kP1Lz0=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+213
View File
@@ -0,0 +1,213 @@
package main
import (
"os"
"path/filepath"
"runtime"
"strconv"
. "github.com/anchore/go-make"
"github.com/anchore/go-make/config"
"github.com/anchore/go-make/file"
"github.com/anchore/go-make/lang"
"github.com/anchore/go-make/run"
"github.com/anchore/go-make/tasks/golint"
"github.com/anchore/go-make/tasks/goreleaser"
"github.com/anchore/go-make/tasks/gotask"
"github.com/anchore/go-make/tasks/gotest"
)
func main() {
Makefile(
// shared anchore tasks
golint.Tasks(),
goreleaser.Tasks(),
// unit tests: exclude packages under any test/ directory (matches the syft
// Taskfile's prior `grep -v` against test paths). Coverage threshold of 62%
// preserves the prior coverage gate that used to live in scripts/coverage.py.
gotest.Tasks(
gotest.Name("unit"),
gotest.ExcludeGlob("**/test/**"),
gotest.CoverageThreshold(62),
race(),
),
// integration tests: run `go test` directly instead of via gotest.Tasks(), which
// has no way to set a timeout. The suite is a single package of ~36 sequential
// tests over 18 docker fixture images, so with a cold fixture cache (every image
// built + saved inline, under -race) it runs well past `go test`'s default 10m
// timeout -- which is exactly the case when regenerating the fixture cache from
// scratch. -count=1 keeps the go test cache from short-circuiting a run whose
// side effect (the built fixtures) is the thing we're after.
//
// The race-detector smoke against a real image stays bundled here (RunsOn
// integration) so `make integration` behaves like the Taskfile version did.
Task{
Name: "integration",
Description: "run integration tests",
RunsOn: lang.List("test"),
Run: func() {
raceFlag := ""
if raceEnabled() {
raceFlag = " -race"
}
Run(
"go test -count=1 -timeout=30m"+raceFlag+" ./cmd/syft/internal/test/integration/...",
run.Env("GODEBUG", "dontfreezetheworld=1"),
)
},
},
Task{
Name: "integration:race-smoke",
Description: "exercise the CLI with the race detector",
RunsOn: lang.List("integration"),
Run: func() {
if !raceEnabled() {
Log("race detector disabled (RACE=false); skipping race smoke")
return
}
Run("go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2")
},
},
// cli tests: native go-make Task. Runs SYFT_BINARY_LOCATION at an *absolute*
// path to the snapshot binary. Builds the snapshot only when the binary is
// missing rather than depending on the snapshot task unconditionally: in
// validations.yaml CI we download a pre-built snapshot artifact, so the binary
// already exists and rebuilding would both burn ~10m and clobber it. When
// `make test` runs cold (e.g. the release pipeline) or locally with no
// snapshot, we build a single-target snapshot (current OS/arch only) since
// that's all the CLI tests need.
Task{
Name: "cli",
Description: "Run CLI tests",
RunsOn: lang.List("test"),
Run: func() {
bin := snapshotBinPath()
if !file.Exists(bin) {
Log("snapshot binary not found at %s; building single-target snapshot", bin)
Run("make snapshot:single-target")
}
Log("testing binary: %s", bin)
Run(
"go test -count=1 -timeout=15m -v ./test/cli",
run.Env("SYFT_BINARY_LOCATION", bin),
)
},
},
// windows unit tests: deliberately not hooked into "test" (no RunsOn). The full unit suite leans on
// linux docker fixture images and posix-only assumptions, so this covers the packages that own
// host path handling (where windows bugs live) and skips the tests that need docker image fixtures.
// ponytail: curated package list, widen it as more of the suite is made windows-safe.
Task{
Name: "unit:windows",
Description: "run the windows-relevant subset of unit tests",
Run: func() {
Run("go test -count=1"+
` -skip "Image|Squash|AllLayers|MixFileTypes|GivenCoordinates"`+
" ./syft/internal/windows/... ./syft/internal/fileresolver/... ./syft/source/directorysource/... ./syft/source/filesource/..."+
" ./syft/file ./syft/file/cataloger/filedigest/... ./syft/file/cataloger/filecontent/...",
run.Stdout(os.Stderr),
)
},
},
// default validation pipeline (replaces Taskfile `default`/`pr-validations`/`validations`).
Task{
Name: "default",
Description: "Run all validation tasks",
Dependencies: Deps("static-analysis", "test", "install-test"),
},
// --- everything else is implemented in Taskfile.yaml. gotask.Tasks()
// discovers every (non-internal) Taskfile task — including the namespaced
// `generate:cpe-index:*` tasks from the included task.d file — and surfaces
// them as first-class go-make tasks (with their canonical names and `desc:`)
// that forward to `task <name>`. Descriptions live in Taskfile.yaml as the
// single source of truth; no per-task wrapping needed here.
gotask.Tasks(),
// gotask.Tasks() discovers canonical task names only, not Taskfile aliases,
// so re-expose `refresh-fixtures`'s `fixtures` alias for manual use.
Task{
Name: "fixtures",
Description: "Clear and fetch all test fixture cache (alias of refresh-fixtures)",
Dependencies: Deps("refresh-fixtures"),
},
// gotask.Tasks() can't attach RunsOn labels, so wire the syft-specific
// Taskfile tasks into go-make's native phases here. These thin hooks have
// no body and no description (hidden from `make help`); they only pull the
// discovered tasks in when the labeled phase runs.
Task{
Name: "static-analysis:syft",
RunsOn: lang.List("static-analysis"),
Dependencies: Deps("check-json-schema-drift", "check-binary-fixture-size"),
},
Task{
Name: "test:syft",
RunsOn: lang.List("test"),
Dependencies: Deps("validate-cyclonedx-schema", "test-utils", "check-docker-cache"),
},
// refresh-fixtures hooks into "unit" so `make unit` triggers the stale-cache
// detection + download just like `task unit` did on main (its
// `deps: [tmpdir, fixtures]` is what kept the fixture cache fresh).
Task{
Name: "unit:syft",
RunsOn: lang.List("unit"),
Dependencies: Deps("refresh-fixtures"),
},
Task{
Name: "clean:syft",
RunsOn: lang.List("clean"),
Dependencies: Deps(
"clean-snapshot",
"clean-docker-cache",
"clean-oras-cache",
"clean-cache",
"clean-test-observations",
),
},
)
}
// raceEnabled is the single switch for the race detector across every test suite.
// Unset it and we keep go-make's behavior (on in CI, off locally and on windows);
// set RACE=false to turn it off everywhere -- worth doing when rebuilding the test
// fixture cache from scratch, where every suite is dominated by building docker
// fixtures and the race detector only adds wall clock. RACE=true forces it on.
func raceEnabled() bool {
if enabled, err := strconv.ParseBool(config.Env("RACE", "")); err == nil {
return enabled
}
return config.CI && !config.Windows
}
// race applies raceEnabled() to a gotest suite. gotest exposes no functional option
// for the race detector, but gotest.Config.Race is exported.
func race() gotest.Option {
return func(c *gotest.Config) {
c.Race = raceEnabled()
}
}
// snapshotBinPath replicates the SNAPSHOT_BIN computation from the prior Taskfile:
// <repoRoot>/snapshot/<os>-build_<os>_<arch>/syft, where arch maps amd64->amd64_v1
// and arm64->arm64_v8.0 to match goreleaser's per-target output directory naming.
// Returns an absolute path: the cli tests' getSyftBinaryLocation contract requires
// SYFT_BINARY_LOCATION to be absolute because subtests run with cmd.Dir = t.TempDir().
func snapshotBinPath() string {
osName := runtime.GOOS
var arch string
switch runtime.GOARCH {
case "amd64":
arch = "amd64_v1"
case "arm64":
arch = "arm64_v8.0"
default:
arch = runtime.GOARCH
}
return filepath.Join(RootDir(), "snapshot", osName+"-build_"+osName+"_"+arch, "syft")
}
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"os"
"testing"
"github.com/anchore/go-make/config"
"github.com/anchore/go-make/tasks/gotest"
)
func TestRaceEnabled(t *testing.T) {
tests := []struct {
name string
env string // "" means RACE is unset
ci bool
want bool
}{
{name: "default in CI", ci: true, want: true},
{name: "default locally", ci: false, want: false},
{name: "explicitly off in CI", env: "false", ci: true, want: false},
{name: "explicitly on locally", env: "true", ci: false, want: true},
{name: "unparseable falls back to default", env: "yes-please", ci: true, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// always go through t.Setenv (it registers the restore) so an ambient
// RACE from the caller's environment can't leak into the unset cases
t.Setenv("RACE", tt.env)
if tt.env == "" {
os.Unsetenv("RACE")
}
orig := config.CI
config.CI = tt.ci
t.Cleanup(func() { config.CI = orig })
if got := raceEnabled(); got != tt.want {
t.Errorf("raceEnabled() = %v, want %v", got, tt.want)
}
// the gotest suites get the same answer through the functional option
var cfg gotest.Config
race()(&cfg)
if cfg.Race != tt.want {
t.Errorf("race() set Race = %v, want %v", cfg.Race, tt.want)
}
})
}
}
+3 -19
View File
@@ -1,4 +1,4 @@
ARG DEBIAN_VERSION=12
ARG DEBIAN_VERSION=13
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:latest AS build
FROM scratch
@@ -8,23 +8,7 @@ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certifica
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
ARG VCS_URL
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.title="syft"
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
LABEL org.opencontainers.image.source=$VCS_URL
LABEL org.opencontainers.image.revision=$VCS_REF
LABEL org.opencontainers.image.vendor="Anchore, Inc."
LABEL org.opencontainers.image.version=$BUILD_VERSION
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/syft /
ENTRYPOINT ["/syft"]
+4 -22
View File
@@ -1,28 +1,10 @@
ARG DEBIAN_VERSION=12
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug-nonroot
ARG DEBIAN_VERSION=13
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
USER nonroot
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
ARG VCS_URL
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.title="syft"
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
LABEL org.opencontainers.image.source=$VCS_URL
LABEL org.opencontainers.image.revision=$VCS_REF
LABEL org.opencontainers.image.vendor="Anchore, Inc."
LABEL org.opencontainers.image.version=$BUILD_VERSION
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/syft /
ENTRYPOINT ["/syft"]
+12
View File
@@ -0,0 +1,12 @@
ARG DEBIAN_VERSION=13
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug-nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/syft /
USER nonroot
ENTRYPOINT ["/syft"]
+3 -19
View File
@@ -1,28 +1,12 @@
ARG DEBIAN_VERSION=12
ARG DEBIAN_VERSION=13
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/syft /
USER nonroot
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
ARG VCS_URL
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.title="syft"
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
LABEL org.opencontainers.image.source=$VCS_URL
LABEL org.opencontainers.image.revision=$VCS_REF
LABEL org.opencontainers.image.vendor="Anchore, Inc."
LABEL org.opencontainers.image.version=$BUILD_VERSION
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ENTRYPOINT ["/syft"]
+13 -41
View File
@@ -1,46 +1,18 @@
OWNER = anchore
PROJECT = syft
# `test` and `snapshot` have matching directory names in this repo, so make would
# refuse to run them without an explicit .PHONY (Nothing to be done for ...).
.PHONY: test snapshot
test:
@go run -C .make . test
TOOL_DIR = .tool
BINNY = $(TOOL_DIR)/binny
TASK = $(TOOL_DIR)/task
snapshot:
@go run -C .make . snapshot
.DEFAULT_GOAL := make-default
.PHONY: *
.DEFAULT_GOAL: make-default
## Bootstrapping targets #################################
make-default:
@go run -C .make .
# note: we need to assume that binny and task have not already been installed
$(BINNY):
@mkdir -p $(TOOL_DIR)
@curl -sSfL https://get.anchore.io/binny | sh -s -- -b $(TOOL_DIR)
# note: we need to assume that binny and task have not already been installed
.PHONY: task
$(TASK) task: $(BINNY)
@$(BINNY) install task -q
.PHONY: ci-bootstrap-go
ci-bootstrap-go:
go mod download
# this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again
.DEFAULT:
%:
@make --silent $(TASK)
@$(TASK) $@
## Shim targets #################################
.PHONY: make-default
make-default: $(TASK)
@# run the default task in the taskfile
@$(TASK)
# for those of us that can't seem to kick the habit of typing `make ...` lets wrap the superior `task` tool
TASKS := $(shell bash -c "test -f $(TASK) && NO_COLOR=1 $(TASK) -l | grep '^\* ' | cut -d' ' -f2 | tr -d ':' | tr '\n' ' '" ) $(shell bash -c "test -f $(TASK) && NO_COLOR=1 $(TASK) -l | grep 'aliases:' | cut -d ':' -f 3 | tr '\n' ' ' | tr -d ','")
.PHONY: $(TASKS)
$(TASKS): $(TASK)
@$(TASK) $@
help: $(TASK)
@$(TASK) -l
@go run -C .make . $@
+1 -2
View File
@@ -8,10 +8,9 @@
<p align="center">
&nbsp;<a href="https://github.com/anchore/syft/actions/workflows/validations.yaml" target="_blank"><img alt="Validations" src="https://github.com/anchore/syft/actions/workflows/validations.yaml/badge.svg"></a>&nbsp;
&nbsp;<a href="https://goreportcard.com/report/github.com/anchore/syft" target="_blank"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/anchore/syft"></a>&nbsp;
&nbsp;<a href="https://github.com/anchore/syft/releases/latest" target="_blank"><img alt="GitHub release" src="https://img.shields.io/github/release/anchore/syft.svg"></a>&nbsp;
&nbsp;<a href="https://github.com/anchore/syft" target="_blank"><img alt="GitHub go.mod Go version" src="https://img.shields.io/github/go-mod/go-version/anchore/syft.svg"></a>&nbsp;
&nbsp;<a href="" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>&nbsp;
&nbsp;<a href="https://github.com/anchore/syft/blob/main/LICENSE" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>&nbsp;
&nbsp;<a href="https://anchore.com/discourse" target="_blank"><img alt="Join our Discourse" src="https://img.shields.io/badge/Discourse-Join-blue?logo=discourse"/></a>&nbsp;
&nbsp;<a rel="me" href="https://fosstodon.org/@syft"><img alt="Follow on Mastodon" src="https://img.shields.io/badge/Mastodon-Follow-blue?logoColor=white&logo=mastodon"/></a>&nbsp;
</p>
+55 -262
View File
@@ -1,6 +1,11 @@
version: "3"
# NOTE: most generic tasks (static-analysis, format, lint, unit, snapshot, release,
# changelog, ci-release, etc.) are now provided natively by anchore/go-make and
# defined in .make/main.go. This file holds the syft-specific tasks, which go-make
# auto-discovers via gotask.Tasks() and surfaces as `make <name>` — keep
# descriptions (`desc:`) populated so they show up in `make help`.
includes:
generate:cpe-index: ./task.d/generate/cpe-index.yaml
@@ -25,11 +30,7 @@ vars:
YQ: "{{ .TOOL_DIR }}/yq"
TASK: "{{ .TOOL_DIR }}/task"
# used for changelog generation
CHANGELOG: CHANGELOG.md
NEXT_VERSION: VERSION
# used for snapshot builds
# used for snapshot bin discovery in compare/install tasks
OS:
sh: uname -s | tr '[:upper:]' '[:lower:]'
ARCH:
@@ -38,15 +39,16 @@ vars:
PROJECT_ROOT:
sh: echo $PWD
# docker platform suffix goreleaser appends to snapshot image tags (e.g. latest-amd64).
# snapshot builds load per-arch images rather than a multi-arch manifest, so the smoke
# test must run the host-native tag to avoid emulation (and red-local/green-CI splits).
DOCKER_ARCH:
sh: go env GOARCH
# note: the snapshot dir must be a relative path starting with ./
# e.g. when installing snapshot debs from a local path, ./ forces the deb to be installed in the current working directory instead of referencing a package name
SNAPSHOT_DIR: ./snapshot
SNAPSHOT_BIN: "{{ .PROJECT_ROOT }}/{{ .SNAPSHOT_DIR }}/{{ .OS }}-build_{{ .OS }}_{{ .ARCH }}/{{ .PROJECT }}"
SNAPSHOT_CMD: "{{ .TOOL_DIR }}/goreleaser release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign"
BUILD_CMD: "{{ .TOOL_DIR }}/goreleaser build --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --single-target"
RELEASE_CMD: "{{ .TOOL_DIR }}/goreleaser release --clean --release-notes {{ .CHANGELOG }}"
VERSION:
sh: git describe --dirty --always --tags
# used for install and acceptance testing
COMPARE_DIR: ./test/compare
@@ -57,43 +59,10 @@ env:
tasks:
## High-level tasks #################################
default:
desc: Run all validation tasks
aliases:
- pr-validations
- validations
cmds:
- task: static-analysis
- task: test
- task: install-test
static-analysis:
desc: Run all static analysis tasks
cmds:
- task: check-go-mod-tidy
- task: check-licenses
- task: lint
- task: check-json-schema-drift
- task: check-binary-fixture-size
test:
desc: Run all levels of test
cmds:
- task: unit
- task: integration
- task: validate-cyclonedx-schema
- task: test-utils
- task: snapshot
- task: cli
- task: check-docker-cache
## Bootstrap tasks #################################
## Bootstrap (internal helpers used by other Taskfile tasks) ###############
binny:
internal: true
# desc: Get the binny tool
generates:
- "{{ .TOOL_DIR }}/binny"
status:
@@ -102,10 +71,8 @@ tasks:
silent: true
tools:
desc: Install all tools needed for CI and local development
internal: true
deps: [binny]
aliases:
- bootstrap
generates:
- ".binny.yaml"
- "{{ .TOOL_DIR }}/*"
@@ -114,79 +81,14 @@ tasks:
cmd: "{{ .TOOL_DIR }}/binny install -v"
silent: true
update-tools:
desc: Update pinned versions of all tools to their latest available versions
deps: [binny]
generates:
- ".binny.yaml"
- "{{ .TOOL_DIR }}/*"
cmd: "{{ .TOOL_DIR }}/binny update -v"
silent: true
list-tools:
desc: List all tools needed for CI and local development
deps: [binny]
cmd: "{{ .TOOL_DIR }}/binny list"
silent: true
list-tool-updates:
desc: List all tools that are not up to date relative to the binny config
deps: [binny]
cmd: "{{ .TOOL_DIR }}/binny list --updates"
silent: true
tmpdir:
internal: true
silent: true
generates:
- "{{ .TMP_DIR }}"
cmd: "mkdir -p {{ .TMP_DIR }}"
## Static analysis tasks #################################
format:
desc: Auto-format all source code
deps: [tools]
cmds:
- gofmt -w -s .
- "{{ .TOOL_DIR }}/gosimports -local github.com/anchore -w ."
- go mod tidy
lint-fix:
desc: Auto-format all source code + run golangci lint fixers
deps: [tools]
cmds:
- task: format
- "{{ .TOOL_DIR }}/golangci-lint run --tests=false --fix"
lint:
desc: Run gofmt + golangci lint checks
vars:
BAD_FMT_FILES:
sh: gofmt -l -s .
BAD_FILE_NAMES:
sh: "find . | grep -e ':' || true"
deps: [tools]
cmds:
# ensure there are no go fmt differences
- cmd: 'test -z "{{ .BAD_FMT_FILES }}" || (echo "files with gofmt issues: [{{ .BAD_FMT_FILES }}]"; exit 1)'
silent: true
# ensure there are no files with ":" in it (a known back case in the go ecosystem)
- cmd: 'test -z "{{ .BAD_FILE_NAMES }}" || (echo "files with bad names: [{{ .BAD_FILE_NAMES }}]"; exit 1)'
silent: true
# run linting
- "{{ .TOOL_DIR }}/golangci-lint run --tests=false"
check-licenses:
# desc: Ensure transitive dependencies are compliant with the current license policy
deps: [tools]
cmd: "{{ .TOOL_DIR }}/bouncer check ./..."
check-go-mod-tidy:
# desc: Ensure go.mod and go.sum are up to date
cmds:
- cmd: .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are tidy!"
silent: true
## Static analysis extras #################################################
check-json-schema-drift:
desc: Ensure there is no drift between the JSON schema and the code
@@ -203,8 +105,8 @@ tasks:
cmds:
- .github/scripts/check_binary_fixture_size.sh syft/pkg/cataloger/binary/testdata/classifiers/snippets
## Test extras ############################################################
## Testing tasks #################################
update-format-golden-files:
desc: "Update golden (i.e. snapshot) files used by unit tests"
cmds:
@@ -214,60 +116,35 @@ tasks:
- go test ./syft/format/cyclonedxjson -update-cyclonedx-json
- go test ./syft/format/syftjson -update-json
unit:
desc: Run unit tests
deps:
- tmpdir
- fixtures
vars:
TEST_PKGS:
sh: "go list ./... | grep -v {{ .OWNER }}/{{ .PROJECT }}/test | grep -v {{ .OWNER }}/{{ .PROJECT }}/cmd/syft/internal/test | tr '\n' ' '"
# unit test coverage threshold (in % coverage)
COVERAGE_THRESHOLD: 62
cmds:
- task: clean-test-observations
- "go test -coverprofile {{ .TMP_DIR }}/unit-coverage-details.txt {{ .TEST_PKGS }}"
- cmd: ".github/scripts/coverage.py {{ .COVERAGE_THRESHOLD }} {{ .TMP_DIR }}/unit-coverage-details.txt"
silent: true
integration:
desc: Run integration tests
cmds:
- "go test -v ./cmd/syft/internal/test/integration"
# exercise most of the CLI with the data race detector
# we use a larger image to ensure we're using multiple catalogers at a time
- "go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2"
validate-cyclonedx-schema:
desc: Validate that Syft produces valid CycloneDX documents
cmds:
- "cd schema/cyclonedx && make"
cli:
desc: Run CLI tests
deps: [tools]
cmds:
- cmd: "echo 'testing binary: {{ .SNAPSHOT_BIN }}'"
silent: true
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
silent: true
- "go test -count=1 -timeout=15m -v ./test/cli"
env:
SYFT_BINARY_LOCATION: "{{ .SNAPSHOT_BIN }}"
test-utils:
desc: Run tests for pipeline utils
cmds:
- cmd: .github/scripts/labeler_test.py
- cmd: go test -C .make ./...
snapshot-smoke-test:
desc: Run a smoke test on the snapshot builds + docker images
cmds:
- cmd: "echo 'testing snapshot binary: {{ .SNAPSHOT_BIN }}'"
silent: true
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
silent: true
- "{{ .SNAPSHOT_BIN }} version"
- "{{ .SNAPSHOT_BIN }} scan alpine:latest"
- docker run --pull=never --rm anchore/syft:latest-{{ .DOCKER_ARCH }} version
- docker run --pull=never --rm anchore/syft:latest-{{ .DOCKER_ARCH }} scan alpine:latest
## Test-fixture-related targets #################################
## Test-fixture-related targets ###########################################
fingerprints:
desc: Generate fingerprints for all non-docker test fixture
desc: Generate fingerprints for all non-docker test fixtures
silent: true
deps: [tmpdir]
# this will look for `testdata/Makefile` and invoke the `fingerprint` target to calculate all cache input fingerprint files
generates:
- '**/testdata/**/*.fingerprint'
@@ -281,16 +158,10 @@ tasks:
echo -e "${YELLOW}creating fingerprint files for non-docker fixtures...${RESET}"
for dir in $(find . -type d -name 'testdata'); do
if [ -f "$dir/Makefile" ]; then
# for debugging...
#echo -e "${YELLOW}• calculating fingerprints in $dir... ${RESET}"
(make -C "$dir" fingerprint)
fi
done
# for debugging...
# echo -e "generated all fixture fingerprints"
- .github/scripts/fingerprint_docker_fixtures.py
- |
# if DOWNLOAD_TEST_FIXTURE_CACHE is set to 'false', then we don't need to calculate the fingerprint for the cache
@@ -432,6 +303,7 @@ tasks:
eval $oras_command
show-test-image-cache:
desc: Print the on-disk + docker daemon state of the stereoscope fixture image cache
silent: true
cmds:
- "echo 'Docker daemon cache:'"
@@ -452,28 +324,36 @@ tasks:
exit 1
fi
## install.sh testing targets #################################
## install.sh testing targets #############################################
install-test:
desc: Run install.sh test suite (delegates to test/install/Makefile)
cmds:
- "cd test/install && make"
install-test-cache-save:
desc: Save the install.sh test image cache (delegates to test/install/Makefile)
cmds:
- "cd test/install && make save"
install-test-cache-load:
desc: Load the install.sh test image cache (delegates to test/install/Makefile)
cmds:
- "cd test/install && make load"
install-test-ci-mac:
desc: Run install.sh CI test suite on macOS (delegates to test/install/Makefile)
cmds:
- "cd test/install && make ci-test-mac"
## Compare-test targets ###################################################
generate-compare-file:
desc: Generate the acceptance comparison reference JSON for the current compare image
cmd: "go run ./cmd/syft {{ .COMPARE_TEST_IMAGE }} -o json > {{ .COMPARE_DIR }}/testdata/acceptance-{{ .COMPARE_TEST_IMAGE }}.json"
compare-mac:
desc: Run macOS install + acceptance comparison against the snapshot build
deps: [tmpdir]
cmd: |
{{ .COMPARE_DIR }}/mac.sh \
@@ -483,11 +363,13 @@ tasks:
{{ .TMP_DIR }}
compare-linux:
desc: Run Linux install + acceptance comparison (deb + rpm) against the snapshot build
cmds:
- task: compare-test-deb-package-install
- task: compare-test-rpm-package-install
compare-test-deb-package-install:
desc: Run Linux .deb install + acceptance comparison against the snapshot build
deps: [tmpdir]
cmd: |
{{ .COMPARE_DIR }}/deb.sh \
@@ -497,6 +379,7 @@ tasks:
{{ .TMP_DIR }}
compare-test-rpm-package-install:
desc: Run Linux .rpm install + acceptance comparison against the snapshot build
deps: [tmpdir]
cmd: |
{{ .COMPARE_DIR }}/rpm.sh \
@@ -506,7 +389,7 @@ tasks:
{{ .TMP_DIR }}
## Code and data generation targets #################################
## Code and data generation targets ######################################
generate:
desc: Add data generation tasks
@@ -556,104 +439,7 @@ tasks:
- "SYFT_ENABLE_COMPLETENESS_TESTS=true go test -p 1 ./internal/capabilities/... -count=1"
## Build-related targets #################################
build:
desc: Build the project
deps: [tools, tmpdir]
generates:
- "{{ .PROJECT }}"
cmds:
- silent: true
cmd: |
echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml
cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml
- "{{ .BUILD_CMD }}"
snapshot:
desc: Create a snapshot release
aliases:
- build
deps: [tools, tmpdir]
sources:
- cmd/**/*.go
- syft/**/*.go
- internal/**/*.go
method: checksum
generates:
- "{{ .SNAPSHOT_BIN }}"
cmds:
- silent: true
cmd: |
echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml
cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml
- "{{ .SNAPSHOT_CMD }}"
snapshot-smoke-test:
desc: Run a smoke test on the snapshot builds + docker images
cmds:
- cmd: "echo 'testing snapshot binary: {{ .SNAPSHOT_BIN }}'"
silent: true
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
silent: true
- "{{ .SNAPSHOT_BIN }} version"
- "{{ .SNAPSHOT_BIN }} scan alpine:latest"
- docker run --rm anchore/syft:latest version
- docker run --rm anchore/syft:latest scan alpine:latest
changelog:
desc: Generate a changelog
deps: [tools]
generates:
- "{{ .CHANGELOG }}"
- "{{ .NEXT_VERSION }}"
cmds:
- "{{ .TOOL_DIR }}/chronicle -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}"
- "{{ .TOOL_DIR }}/glow -w 0 {{ .CHANGELOG }}"
## Release targets #################################
release:
desc: Create a release
interactive: true
deps: [tools]
cmds:
- cmd: .github/scripts/trigger-release.sh
silent: true
## CI-only targets #################################
ci-check:
# desc: "[CI only] Are you in CI?"
cmds:
- cmd: .github/scripts/ci-check.sh
silent: true
ci-release:
# desc: "[CI only] Create a release"
deps: [tools]
cmds:
- task: ci-check
- "{{ .TOOL_DIR }}/chronicle -vvv > CHANGELOG.md"
- cmd: "cat CHANGELOG.md"
silent: true
- "{{ .RELEASE_CMD }}"
## Cleanup targets #################################
clean:
desc: Remove all cache files and old builds
cmds:
- task: clean-snapshot
- task: clean-cache
- task: clean-test-observations
- task: clean-docker-cache
- task: clean-oras-cache
## Cleanup targets ########################################################
clean-snapshot:
desc: Remove any snapshot builds
@@ -675,6 +461,7 @@ tasks:
desc: Remove all image docker tar cache, images from the docker daemon, and ephemeral test fixtures
cmds:
- task: clean-docker-cache
- task: prune-orphan-fingerprints
- |
BOLD='\033[1m'
YELLOW='\033[0;33m'
@@ -690,6 +477,12 @@ tasks:
echo -e "${BOLD}Deleted all ephemeral test fixtures${RESET}"
- rm -f {{ .LAST_CACHE_PULL_FILE }} {{ .CACHE_PATHS_FILE }}
prune-orphan-fingerprints:
desc: Remove *.fingerprint files left behind by moved/deleted catalogers
silent: true
cmds:
- .github/scripts/prune_orphan_fingerprints.py
clean-test-observations:
desc: Remove all test observations (i.e. testdata/test-observations.json)
cmds:
@@ -1,6 +1,6 @@
[TestHandler_handleAttestationStarted/attesting_in_progress/task_line - 1]
⠋ Creating a thing running a thing
⠋ Creating a thing running a thing
---
[TestHandler_handleAttestationStarted/attesting_in_progress/log - 1]
@@ -11,7 +11,7 @@
---
[TestHandler_handleAttestationStarted/attesting_complete/task_line - 1]
✔ Created a thing running a thing
✔ Created a thing running a thing
---
[TestHandler_handleAttestationStarted/attesting_complete/log - 1]
@@ -1,8 +1,8 @@
[TestHandler_handleFetchImage/fetch_image_in_progress - 1]
⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image
⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image
---
[TestHandler_handleFetchImage/fetch_image_complete - 1]
✔ Loaded image the-image
✔ Loaded image the-image
---
@@ -1,8 +1,8 @@
[TestHandler_handleFileIndexingStarted/cataloging_in_progress - 1]
⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path
⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path
---
[TestHandler_handleFileIndexingStarted/cataloging_complete - 1]
✔ Indexed file system /some/path
✔ Indexed file system /some/path
---
@@ -1,8 +1,8 @@
[TestHandler_handlePullSourceStarted/snap_download_in_progress - 1]
⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap
⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap
---
[TestHandler_handlePullSourceStarted/snap_download_complete - 1]
✔ Snap downloaded successfully example-app_1.0_amd64.snap
✔ Snap downloaded successfully example-app_1.0_amd64.snap
---
@@ -1,8 +1,8 @@
[TestHandler_handleReadImage/read_image_in_progress - 1]
⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id
⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id
---
[TestHandler_handleReadImage/read_image_complete - 1]
✔ Parsed image id
✔ Parsed image id
---
+5
View File
@@ -158,6 +158,11 @@ func (l *backgroundLineReader) read(reader io.Reader, stage *progress.Stage) {
l.lock.Unlock()
}
if err := s.Err(); err != nil {
log.WithFields("error", err).Debug("unable to read attestation output")
// keep draining so the writer never blocks on a full pipe
_, _ = io.Copy(io.Discard, reader)
}
}
func (l backgroundLineReader) Lines() []string {
+20 -8
View File
@@ -60,6 +60,7 @@ type (
Packages []detectorPackageInfo `json:"packages,omitempty"`
Comment string `json:"comment,omitempty"`
PackageTypes []string `json:"package_types,omitempty"`
PURLTypes []string `json:"purl_types,omitempty"`
JSONSchemaTypes []string `json:"json_schema_types,omitempty"`
Capabilities capabilities.CapabilitySet `json:"capabilities,omitempty"`
}
@@ -235,7 +236,7 @@ func renderCatalogerInfoJSON(doc *capabilities.Document, catalogers []capabiliti
// if no parsers, use detectors instead
if len(info.Patterns) == 0 {
info.Capabilities = cat.Capabilities
info.Patterns = convertDetectorsToPatterns(cat.Detectors, cat.PackageTypes, cat.JSONSchemaTypes)
info.Patterns = convertDetectorsToPatterns(cat.Detectors, cat.PackageTypes, cat.PURLTypes, cat.JSONSchemaTypes)
}
info.Config = getConfigInfoFromDocument(doc, cat.Config)
@@ -278,6 +279,7 @@ func convertParsersToPatterns(parsers []capabilities.Parser) []patternInfo {
Packages: convertDetectorPackages(parser.Detector.Packages),
Comment: parser.Detector.Comment,
PackageTypes: parser.PackageTypes,
PURLTypes: parser.PURLTypes,
JSONSchemaTypes: parser.JSONSchemaTypes,
Capabilities: parser.Capabilities,
})
@@ -286,7 +288,7 @@ func convertParsersToPatterns(parsers []capabilities.Parser) []patternInfo {
}
// convertDetectorsToPatterns converts detector entries to pattern info for JSON output (for non-parser catalogers)
func convertDetectorsToPatterns(detectors []capabilities.Detector, packageTypes, jsonSchemaTypes []string) []patternInfo {
func convertDetectorsToPatterns(detectors []capabilities.Detector, packageTypes, purlTypes, jsonSchemaTypes []string) []patternInfo {
var patterns []patternInfo
for _, det := range detectors {
patterns = append(patterns, patternInfo{
@@ -296,6 +298,7 @@ func convertDetectorsToPatterns(detectors []capabilities.Detector, packageTypes,
Packages: convertDetectorPackages(det.Packages),
Comment: det.Comment,
PackageTypes: packageTypes,
PURLTypes: purlTypes,
JSONSchemaTypes: jsonSchemaTypes,
})
}
@@ -346,14 +349,14 @@ func renderCatalogerInfoTable(_ *capabilities.Document, catalogers []capabilitie
})),
tablewriter.WithConfig(tablewriter.Config{
Row: tw.CellConfig{
Formatting: tw.CellFormatting{MergeMode: tw.MergeHierarchical},
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
Merging: tw.CellMerging{Mode: tw.MergeHierarchical},
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
},
}),
)
// set headers
table.Header("ECOSYSTEM", "CATALOGER", "CRITERIA", "LICENSE", "NODES", "EDGES", "KINDS", "LISTING", "DIGESTS", "HASH")
table.Header("ECOSYSTEM", "CATALOGER", "CRITERIA", "PURL", "LICENSE", "NODES", "EDGES", "KINDS", "LISTING", "DIGESTS", "HASH")
// build rows for each cataloger
var data [][]string
@@ -367,13 +370,13 @@ func renderCatalogerInfoTable(_ *capabilities.Document, catalogers []capabilitie
// generic catalogers: one row per parser
for _, parser := range cat.Parsers {
criteria := formatCriteria([]capabilities.Detector{parser.Detector})
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, parser.Capabilities)
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, parser.PURLTypes, parser.Capabilities)
data = append(data, row)
}
} else {
// custom catalogers: one row with all detectors
criteria := formatCriteria(cat.Detectors)
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, cat.Capabilities)
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, cat.PURLTypes, cat.Capabilities)
data = append(data, row)
}
}
@@ -385,7 +388,7 @@ func renderCatalogerInfoTable(_ *capabilities.Document, catalogers []capabilitie
}
// buildTableRowFromCapabilities builds a table row from capability values
func buildTableRowFromCapabilities(ecosystem, name, criteria string, caps capabilities.CapabilitySet) []string {
func buildTableRowFromCapabilities(ecosystem, name, criteria string, purlTypes []string, caps capabilities.CapabilitySet) []string {
// extract capability default values
license := extractBoolCapability(caps, "license")
nodes := extractNodesCapability(caps)
@@ -399,6 +402,7 @@ func buildTableRowFromCapabilities(ecosystem, name, criteria string, caps capabi
ecosystem,
name,
criteria,
formatPURLTypes(purlTypes),
license,
nodes,
edges,
@@ -409,6 +413,14 @@ func buildTableRowFromCapabilities(ecosystem, name, criteria string, caps capabi
}
}
// formatPURLTypes renders the PURL types as a comma-separated list, or a placeholder when empty
func formatPURLTypes(purlTypes []string) string {
if len(purlTypes) == 0 {
return noStyle.Render("·")
}
return strings.Join(purlTypes, ", ")
}
// extractBoolCapability extracts a boolean capability value and formats it
func extractBoolCapability(caps capabilities.CapabilitySet, name string) string {
for _, cap := range caps {
@@ -280,12 +280,15 @@ func Test_catalogerInfoReport(t *testing.T) {
Detectors: []capabilities.Detector{
{Method: capabilities.GlobDetection, Criteria: []string{"**/*.test"}},
},
PURLTypes: []string{"npm"},
},
},
assertions: func(t *testing.T, got string) {
assert.Contains(t, got, "test-cataloger")
assert.Contains(t, got, "ECOSYSTEM")
assert.Contains(t, got, "CATALOGER")
assert.Contains(t, got, "PURL")
assert.Contains(t, got, "npm")
},
},
{
@@ -298,12 +301,17 @@ func Test_catalogerInfoReport(t *testing.T) {
Ecosystem: "test",
Type: "custom",
Selectors: []string{"test", "custom"},
Detectors: []capabilities.Detector{
{Method: capabilities.GlobDetection, Criteria: []string{"**/*.test"}},
},
PURLTypes: []string{"npm"},
},
},
assertions: func(t *testing.T, got string) {
assert.Contains(t, got, `"name":"test-cataloger"`)
assert.Contains(t, got, `"ecosystem":"test"`)
assert.Contains(t, got, `"type":"custom"`)
assert.Contains(t, got, `"purl_types":["npm"]`)
},
},
{
+230 -17
View File
@@ -1,24 +1,34 @@
package commands
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/go-homedir"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/spillbuf"
"github.com/anchore/syft/internal/tmpdir"
"github.com/anchore/syft/syft/format"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
)
const (
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json"
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
SYFT_CONVERT_PASSTHROUGH_EXACT_FORMAT=true {{.appName}} {{.command}} img.syft.json -o syft-json=out.syft.json copy the input to "out.syft.json" unchanged if it is already syft-json at the current schema version
`
)
@@ -26,6 +36,7 @@ type ConvertOptions struct {
options.Config `yaml:",inline" mapstructure:",squash"`
options.Output `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
Convert options.Convert `yaml:"convert" json:"convert" mapstructure:"convert"`
}
func Convert(app clio.Application) *cobra.Command {
@@ -34,6 +45,7 @@ func Convert(app clio.Application) *cobra.Command {
opts := &ConvertOptions{
UpdateCheck: options.DefaultUpdateCheck(),
Output: options.DefaultOutput(),
Convert: options.DefaultConvert(),
}
return app.SetupCommand(&cobra.Command{
@@ -46,11 +58,11 @@ func Convert(app clio.Application) *cobra.Command {
}),
Args: validateConvertArgs,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
restoreStdout := ui.CaptureStdoutToTraceLog()
defer restoreStdout()
return RunConvert(opts, args[0])
return RunConvert(cmd.Context(), opts, args[0])
},
}, opts)
}
@@ -59,30 +71,63 @@ func validateConvertArgs(cmd *cobra.Command, args []string) error {
return validateArgs(cmd, args, "an SBOM argument is required")
}
func RunConvert(opts *ConvertOptions, userInput string) error {
func RunConvert(ctx context.Context, opts *ConvertOptions, userInput string) error {
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
writer, err := opts.SBOMWriter()
// a temp dir is only needed when STDIN is a pipe and has to be spilled to disk; it is created lazily on
// first use (under the user's TMPDIR), so a file input never touches it. Mirrors syft.CreateSBOM, which
// also honors a TempDir already attached to the context.
td := tmpdir.FromContext(ctx)
if td == nil {
_, td = tmpdir.Root(ctx, "syft-convert")
defer func() {
_ = td.Cleanup()
}()
}
reader, inputInfo, err := openConvertInput(userInput, td)
if err != nil {
return err
}
defer func() {
_ = reader.Close()
}()
var reader io.ReadSeekCloser
outputs := opts.Outputs
var unchanged []string
if opts.Convert.PassthroughExactFormat {
log.Warn("convert.passthrough-exact-format is enabled: syft-json input that already exactly matches a requested output format and schema version will be copied to that output unchanged (this does not apply to any other input format)")
if userInput == "-" {
// though os.Stdin is an os.File, it does not support seeking
// you will get errors such as "seek /dev/stdin: illegal seek".
// We need to buffer what we read.
reader = internal.NewBufferedSeeker(os.Stdin)
} else {
f, err := os.Open(userInput)
unchanged, outputs, err = partitionOutputsBySourceFormat(opts.Output, reader)
if err != nil {
return fmt.Errorf("failed to open SBOM file: %w", err)
return err
}
defer func() {
_ = f.Close()
}()
reader = f
}
// resolve (and validate) every output that needs converting before writing anything, so that
// a bad -o value fails the command without leaving partial results behind
var writer sbom.Writer
if len(outputs) > 0 {
outputOpts := opts.Output
outputOpts.Outputs = outputs
writer, err = outputOpts.SBOMWriter()
if err != nil {
return err
}
}
for _, output := range unchanged {
if err := writeUnchanged(output, opts.LegacyFile, inputInfo, reader); err != nil {
return err
}
}
if writer == nil {
return nil
}
if _, err := reader.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("unable to seek to start of SBOM: %w", err)
}
s, _, _, err := format.Decode(reader)
@@ -100,3 +145,171 @@ func RunConvert(opts *ConvertOptions, userInput string) error {
return nil
}
// openConvertInput opens the SBOM document at the given path, or STDIN when "-". The returned reader is seekable so
// the document can be identified, copied, and decoded in turn without being loaded into memory. The returned
// os.FileInfo describes the file the document is being read from, so that writing back over it can be recognized;
// it is nil when the input is not a file (a pipe or a terminal on STDIN).
func openConvertInput(userInput string, td *tmpdir.TempDir) (io.ReadSeekCloser, os.FileInfo, error) {
if userInput == "-" {
return openStdin(td)
}
f, err := os.Open(userInput)
if err != nil {
return nil, nil, fmt.Errorf("failed to open SBOM file: %w", err)
}
info, err := f.Stat()
if err != nil {
// the document is readable, we just cannot recognize it as an output destination
log.WithFields("error", err, "path", userInput).Debug("unable to stat SBOM file")
}
return f, info, nil
}
// openStdin returns a seekable view of STDIN. When STDIN is a regular file (such as a shell redirect) it is read in
// place. A pipe or terminal cannot seek (you will get errors such as "seek /dev/stdin: illegal seek"), so its
// contents are drained into a spill buffer: a small head stays in memory and everything beyond it goes to a temp
// file under td, so an arbitrarily large piped SBOM never has to be held in memory to be made seekable.
func openStdin(td *tmpdir.TempDir) (io.ReadSeekCloser, os.FileInfo, error) {
if info, err := os.Stdin.Stat(); err == nil && info.Mode().IsRegular() {
if start, err := os.Stdin.Seek(0, io.SeekCurrent); err == nil {
// a redirect reads the file in place, so it is just as much an input file as a path argument is
return readSeekNopCloser{io.NewSectionReader(os.Stdin, start, info.Size()-start)}, info, nil
}
}
buf := spillbuf.New(td)
if _, err := io.Copy(io.NewOffsetWriter(buf, 0), os.Stdin); err != nil {
_ = buf.Close()
return nil, nil, fmt.Errorf("failed to read SBOM from STDIN: %w", err)
}
return &spilledReader{
SectionReader: io.NewSectionReader(buf, 0, buf.Size()),
buf: buf,
}, nil, nil
}
type readSeekNopCloser struct {
io.ReadSeeker
}
func (readSeekNopCloser) Close() error {
return nil
}
// spilledReader is a seekable view over a spill buffer; closing it releases the buffer's memory and spill file.
type spilledReader struct {
*io.SectionReader
buf *spillbuf.Buffer
}
func (r *spilledReader) Close() error {
return r.buf.Close()
}
// partitionOutputsBySourceFormat splits the requested outputs (in "<format>[@<version>][=<path>]" form) into those
// that exactly match the format and schema version of a syft-json input document (which can be written out
// unchanged) and those that still require conversion. Input in any other format is always converted. Outputs that
// cannot be resolved to an encoder are left for the writer to report on.
func partitionOutputsBySourceFormat(output options.Output, reader io.ReadSeeker) (unchanged []string, toConvert []string, err error) {
if _, err := reader.Seek(0, io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("unable to seek to start of SBOM: %w", err)
}
id, version := format.Identify(reader)
if id == "" || version == "" {
// let decoding surface the appropriate error
return nil, output.Outputs, nil
}
if id != syftjson.ID {
log.WithFields("format", id, "version", version).Debug("input is not syft-json, passthrough does not apply")
return nil, output.Outputs, nil
}
encoders, err := output.Encoders()
if err != nil {
return nil, nil, err
}
collection := format.NewEncoderCollection(encoders...)
for _, out := range output.Outputs {
name, _, _ := strings.Cut(strings.TrimSpace(out), "=")
enc := collection.GetByString(name)
if enc != nil && enc.ID() == id && enc.Version() == version {
log.WithFields("format", id, "version", version, "output", out).Warn("input already exactly matches the requested output format and schema version, copying it unchanged instead of converting it")
unchanged = append(unchanged, out)
continue
}
toConvert = append(toConvert, out)
}
return unchanged, toConvert, nil
}
// writeUnchanged copies the SBOM document as-is to the destination described by the given "<format>[=<path>]"
// output value, falling back to the (deprecated) --file path, and finally to the report bus (STDOUT). When the
// destination is the input file itself there is nothing to do.
func writeUnchanged(output string, defaultFile string, inputInfo os.FileInfo, reader io.ReadSeeker) error {
if _, err := reader.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("unable to seek to start of SBOM: %w", err)
}
_, path, _ := strings.Cut(strings.TrimSpace(output), "=")
if path == "" {
path = defaultFile
}
if path == "" {
// the report bus only carries strings, so STDOUT output must be materialized in memory
content, err := io.ReadAll(reader) //nolint:gocritic // the whole SBOM is the report; there is no meaningful bound
if err != nil {
return fmt.Errorf("unable to read SBOM: %w", err)
}
bus.Report(string(content))
return nil
}
expandedPath, err := homedir.Expand(path)
if err != nil {
log.Warnf("could not expand given writer output path=%q: %w", path, err)
expandedPath = path
}
if isSameFile(inputInfo, expandedPath) {
log.WithFields("path", expandedPath).Info("output is the input file, leaving it as-is")
return nil
}
if err := os.MkdirAll(filepath.Dir(expandedPath), 0755); err != nil {
return fmt.Errorf("unable to create report directory: %w", err)
}
f, err := os.OpenFile(expandedPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("unable to create report file: %w", err)
}
if _, err := io.Copy(f, reader); err != nil {
_ = f.Close()
return fmt.Errorf("unable to write report file: %w", err)
}
return f.Close()
}
// isSameFile reports whether the given path names the file the input is being read from. Comparing the input's
// os.FileInfo rather than its path covers a shell redirect onto STDIN, which has no path to compare.
func isSameFile(inputInfo os.FileInfo, path string) bool {
if inputInfo == nil {
return false
}
pathInfo, err := os.Stat(path)
if err != nil {
return false
}
return os.SameFile(inputInfo, pathInfo)
}
+448
View File
@@ -0,0 +1,448 @@
package commands
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/spillbuf"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format"
"github.com/anchore/syft/syft/format/cyclonedxjson"
"github.com/anchore/syft/syft/format/spdxjson"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func convertTestSBOM() sbom.SBOM {
catalog := pkg.NewCollection()
catalog.Add(pkg.Package{
Name: "musl-utils",
Version: "1.2.3-r4",
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Locations: file.NewLocationSet(
file.NewLocation("/lib/apk/db/installed"),
),
PURL: "pkg:apk/alpine/musl-utils@1.2.3-r4",
})
return sbom.SBOM{
Artifacts: sbom.Artifacts{
Packages: catalog,
},
Source: source.Description{
ID: "some-id",
Name: "some-dir",
Version: "latest",
Metadata: source.DirectoryMetadata{
Path: "/some/path",
},
},
Descriptor: sbom.Descriptor{
Name: "syft",
Version: "v0.0.0-test",
},
}
}
func mustConvertEncoder(enc sbom.FormatEncoder, err error) sbom.FormatEncoder {
if err != nil {
panic(err)
}
return enc
}
func encodeConvertTestSBOM(t *testing.T, enc sbom.FormatEncoder) []byte {
buf := &bytes.Buffer{}
require.NoError(t, enc.Encode(buf, convertTestSBOM()))
return buf.Bytes()
}
// prettySyftJSON returns the test SBOM as pretty-printed syft-json. Pretty input makes a verbatim copy
// distinguishable from a re-encode, since re-encoding always produces compact output here.
func prettySyftJSON(t *testing.T) []byte {
cfg := syftjson.DefaultEncoderConfig()
cfg.Pretty = true
return encodeConvertTestSBOM(t, mustConvertEncoder(syftjson.NewFormatEncoderWithConfig(cfg)))
}
func Test_partitionOutputsBySourceFormat(t *testing.T) {
syftEncoder := mustConvertEncoder(syftjson.NewFormatEncoderWithConfig(syftjson.DefaultEncoderConfig()))
syftVersion := syftEncoder.Version()
syftContent := encodeConvertTestSBOM(t, syftEncoder)
// the same document claiming an older schema version
olderSyftContent := bytes.ReplaceAll(syftContent, []byte(`"version":"`+syftVersion+`"`), []byte(`"version":"16.0.0"`))
require.NotEqual(t, syftContent, olderSyftContent)
cdxContent := encodeConvertTestSBOM(t, mustConvertEncoder(cyclonedxjson.NewFormatEncoderWithConfig(cyclonedxjson.DefaultEncoderConfig())))
tests := []struct {
name string
content []byte
outputs []string
wantUnchanged []string
wantToConvert []string
}{
{
name: "syft-json matches the bare format name",
content: syftContent,
outputs: []string{"syft-json"},
wantUnchanged: []string{"syft-json"},
},
{
name: "syft-json matches its alias",
content: syftContent,
outputs: []string{"json"},
wantUnchanged: []string{"json"},
},
{
name: "syft-json matches an explicit version and file path",
content: syftContent,
outputs: []string{"syft-json@" + syftVersion + "=out/some.syft.json"},
wantUnchanged: []string{"syft-json@" + syftVersion + "=out/some.syft.json"},
},
{
name: "syft-json at an older schema version requires conversion",
content: olderSyftContent,
outputs: []string{"syft-json"},
wantToConvert: []string{"syft-json"},
},
{
name: "a different output format requires conversion",
content: syftContent,
outputs: []string{"spdx-json"},
wantToConvert: []string{"spdx-json"},
},
{
name: "mixed outputs are partitioned",
content: syftContent,
outputs: []string{"syft-json=a.json", "spdx-json=b.json", "cyclonedx-xml"},
wantUnchanged: []string{"syft-json=a.json"},
wantToConvert: []string{"spdx-json=b.json", "cyclonedx-xml"},
},
{
name: "unknown format is left for the writer to report",
content: syftContent,
outputs: []string{"bogus"},
wantToConvert: []string{"bogus"},
},
{
name: "non syft-json input is always converted, even when it matches the output",
content: cdxContent,
outputs: []string{"cyclonedx-json"},
wantToConvert: []string{"cyclonedx-json"},
},
{
name: "unidentifiable content is left for the decoder to report",
content: []byte("definitely not an sbom"),
outputs: []string{"syft-json"},
wantToConvert: []string{"syft-json"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := options.DefaultOutput()
output.Outputs = tt.outputs
unchanged, toConvert, err := partitionOutputsBySourceFormat(output, bytes.NewReader(tt.content))
require.NoError(t, err)
assert.Equal(t, tt.wantUnchanged, unchanged)
assert.Equal(t, tt.wantToConvert, toConvert)
})
}
}
func Test_RunConvert_passthroughExactFormat(t *testing.T) {
syftContent := prettySyftJSON(t)
newOpts := func(passthrough bool, outputs ...string) *ConvertOptions {
opts := &ConvertOptions{
Output: options.DefaultOutput(),
Convert: options.Convert{
PassthroughExactFormat: passthrough,
},
}
opts.Outputs = outputs
return opts
}
writeInput := func(t *testing.T, content []byte) string {
input := filepath.Join(t.TempDir(), "input.json")
require.NoError(t, os.WriteFile(input, content, 0600))
return input
}
t.Run("matching output is copied unchanged while others are converted", func(t *testing.T) {
dir := t.TempDir()
syftOut := filepath.Join(dir, "out.syft.json")
spdxOut := filepath.Join(dir, "out.spdx.json")
opts := newOpts(true, "syft-json="+syftOut, "spdx-json="+spdxOut)
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
got, err := os.ReadFile(syftOut)
require.NoError(t, err)
assert.Equal(t, syftContent, got)
spdx, err := os.ReadFile(spdxOut)
require.NoError(t, err)
id, _ := format.Identify(bytes.NewReader(spdx))
assert.Equal(t, spdxjson.ID, id)
})
t.Run("STDIN input is copied unchanged while others are converted", func(t *testing.T) {
dir := t.TempDir()
syftOut := filepath.Join(dir, "out.syft.json")
spdxOut := filepath.Join(dir, "out.spdx.json")
stdin, err := os.Open(writeInput(t, syftContent))
require.NoError(t, err)
defer stdin.Close()
originalStdin := os.Stdin
os.Stdin = stdin
t.Cleanup(func() { os.Stdin = originalStdin })
opts := newOpts(true, "syft-json="+syftOut, "spdx-json="+spdxOut)
require.NoError(t, RunConvert(context.Background(), opts, "-"))
got, err := os.ReadFile(syftOut)
require.NoError(t, err)
assert.Equal(t, syftContent, got)
spdx, err := os.ReadFile(spdxOut)
require.NoError(t, err)
id, _ := format.Identify(bytes.NewReader(spdx))
assert.Equal(t, spdxjson.ID, id)
})
t.Run("piped STDIN input is copied unchanged while others are converted", func(t *testing.T) {
dir := t.TempDir()
syftOut := filepath.Join(dir, "out.syft.json")
spdxOut := filepath.Join(dir, "out.spdx.json")
pipeReader, pipeWriter, err := os.Pipe()
require.NoError(t, err)
defer pipeReader.Close()
go func() {
defer pipeWriter.Close()
_, _ = pipeWriter.Write(syftContent)
}()
originalStdin := os.Stdin
os.Stdin = pipeReader
t.Cleanup(func() { os.Stdin = originalStdin })
opts := newOpts(true, "syft-json="+syftOut, "spdx-json="+spdxOut)
require.NoError(t, RunConvert(context.Background(), opts, "-"))
got, err := os.ReadFile(syftOut)
require.NoError(t, err)
assert.Equal(t, syftContent, got)
spdx, err := os.ReadFile(spdxOut)
require.NoError(t, err)
id, _ := format.Identify(bytes.NewReader(spdx))
assert.Equal(t, spdxjson.ID, id)
})
t.Run("without the option the matching output is re-encoded", func(t *testing.T) {
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
opts := newOpts(false, "syft-json="+syftOut)
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
got, err := os.ReadFile(syftOut)
require.NoError(t, err)
id, _ := format.Identify(bytes.NewReader(got))
assert.Equal(t, syftjson.ID, id)
// the input was pretty-printed and the encoder is not configured to be, so a re-encode cannot reproduce it
assert.NotEqual(t, syftContent, got)
})
t.Run("non syft-json input is re-encoded even when it matches the output", func(t *testing.T) {
cdxContent := encodeConvertTestSBOM(t, mustConvertEncoder(cyclonedxjson.NewFormatEncoderWithConfig(cyclonedxjson.DefaultEncoderConfig())))
cdxOut := filepath.Join(t.TempDir(), "out.cdx.json")
opts := newOpts(true, "cyclonedx-json="+cdxOut)
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, cdxContent)))
got, err := os.ReadFile(cdxOut)
require.NoError(t, err)
id, _ := format.Identify(bytes.NewReader(got))
assert.Equal(t, cyclonedxjson.ID, id)
// a fresh serial number is minted on every encode, so a round trip never reproduces the input
assert.NotEqual(t, cdxContent, got)
})
t.Run("the deprecated --file path is honored", func(t *testing.T) {
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
opts := newOpts(true, "syft-json")
opts.LegacyFile = syftOut
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
got, err := os.ReadFile(syftOut)
require.NoError(t, err)
assert.Equal(t, syftContent, got)
})
t.Run("writing over the input file leaves it intact", func(t *testing.T) {
input := writeInput(t, syftContent)
opts := newOpts(true, "syft-json="+input)
require.NoError(t, RunConvert(context.Background(), opts, input))
got, err := os.ReadFile(input)
require.NoError(t, err)
assert.Equal(t, syftContent, got)
})
t.Run("writing over a redirected STDIN input leaves it intact", func(t *testing.T) {
input := writeInput(t, syftContent)
stdin, err := os.Open(input)
require.NoError(t, err)
defer stdin.Close()
originalStdin := os.Stdin
os.Stdin = stdin
t.Cleanup(func() { os.Stdin = originalStdin })
opts := newOpts(true, "syft-json="+input)
require.NoError(t, RunConvert(context.Background(), opts, "-"))
got, err := os.ReadFile(input)
require.NoError(t, err)
assert.Equal(t, syftContent, got)
})
t.Run("an invalid output fails before anything is written", func(t *testing.T) {
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
opts := newOpts(true, "syft-json="+syftOut, "bogus")
require.Error(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
assert.NoFileExists(t, syftOut)
})
t.Run("a missing input file is reported", func(t *testing.T) {
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
opts := newOpts(true, "syft-json="+syftOut)
err := RunConvert(context.Background(), opts, filepath.Join(t.TempDir(), "does-not-exist.json"))
require.ErrorContains(t, err, "failed to open SBOM file")
assert.NoFileExists(t, syftOut)
})
t.Run("unrecognized input is left to the decoder to reject", func(t *testing.T) {
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
opts := newOpts(true, "syft-json="+syftOut)
err := RunConvert(context.Background(), opts, writeInput(t, []byte("definitely not an sbom")))
require.ErrorContains(t, err, "failed to decode SBOM")
})
}
// largeSyftJSON returns a pretty-printed syft-json document of at least minBytes, built from many small packages.
func largeSyftJSON(t *testing.T, minBytes int64) []byte {
catalog := pkg.NewCollection()
for i := 0; i < int(minBytes/100); i++ {
catalog.Add(pkg.Package{
Name: fmt.Sprintf("pkg-%d", i),
Version: "1.0.0",
Type: pkg.ApkPkg,
PURL: fmt.Sprintf("pkg:apk/alpine/pkg-%d@1.0.0", i),
})
}
s := convertTestSBOM()
s.Artifacts.Packages = catalog
cfg := syftjson.DefaultEncoderConfig()
cfg.Pretty = true
enc := mustConvertEncoder(syftjson.NewFormatEncoderWithConfig(cfg))
buf := &bytes.Buffer{}
require.NoError(t, enc.Encode(buf, s))
require.Greater(t, int64(buf.Len()), minBytes)
return buf.Bytes()
}
func Test_RunConvert_pipedInputLargerThanMemoryLimitSpillsToDisk(t *testing.T) {
// a piped document bigger than the spill buffer's in-memory tier is written to a temp file under TMPDIR;
// the conversion must identify, copy and clean up from there rather than holding the document in memory
dir := t.TempDir()
syftOut := filepath.Join(dir, "out.syft.json")
spillRoot := filepath.Join(dir, "spill")
require.NoError(t, os.Mkdir(spillRoot, 0o755))
t.Setenv("TMPDIR", spillRoot)
content := largeSyftJSON(t, 2*spillbuf.DefaultMemLimit)
pipeReader, pipeWriter, err := os.Pipe()
require.NoError(t, err)
defer pipeReader.Close()
go func() {
defer pipeWriter.Close()
_, _ = pipeWriter.Write(content)
}()
originalStdin := os.Stdin
os.Stdin = pipeReader
t.Cleanup(func() { os.Stdin = originalStdin })
opts := &ConvertOptions{
Output: options.DefaultOutput(),
Convert: options.Convert{
PassthroughExactFormat: true,
},
}
opts.Outputs = []string{"syft-json=" + syftOut}
require.NoError(t, RunConvert(context.Background(), opts, "-"))
got, err := os.ReadFile(syftOut)
require.NoError(t, err)
assert.Equal(t, content, got)
entries, err := os.ReadDir(spillRoot)
require.NoError(t, err)
assert.Empty(t, entries, "the spill directory must be removed once the conversion finishes")
}
func Test_writeUnchanged_stdout(t *testing.T) {
// with no output path the document is published on the report bus, which is how the CLI writes to STDOUT
b := partybus.NewBus()
subscription := b.Subscribe()
bus.Set(b)
t.Cleanup(func() {
bus.Set(nil)
})
content := []byte(`{"some":"document"}`)
require.NoError(t, writeUnchanged("syft-json", "", nil, bytes.NewReader(content)))
select {
case e := <-subscription.Events():
assert.Equal(t, event.CLIReport, e.Type)
assert.Equal(t, string(content), e.Value)
case <-time.After(time.Second):
t.Fatal("no report was published")
}
}
+6
View File
@@ -7,6 +7,7 @@ import (
"os"
"reflect"
"strings"
"time"
"github.com/hashicorp/go-multierror"
"github.com/spf13/cobra"
@@ -171,6 +172,11 @@ func validateArgs(cmd *cobra.Command, args []string, err string) error {
}
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
scanStart := time.Now()
defer func() {
log.Infof("scan completed in %s", time.Since(scanStart).Round(time.Millisecond))
}()
writer, err := opts.SBOMWriter()
if err != nil {
return err
+12 -2
View File
@@ -20,6 +20,7 @@ import (
"github.com/anchore/syft/syft/file/cataloger/executable"
"github.com/anchore/syft/syft/file/cataloger/filecontent"
"github.com/anchore/syft/syft/pkg/cataloger/binary"
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
"github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/java"
@@ -44,6 +45,7 @@ type Catalog struct {
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
// ecosystem-specific cataloger configuration
Cpp cppConfig `yaml:"cpp" json:"cpp" mapstructure:"cpp"`
Dotnet dotnetConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
Java javaConfig `yaml:"java" json:"java" mapstructure:"java"`
@@ -80,6 +82,7 @@ func DefaultCatalog() Catalog {
JavaScript: defaultJavaScriptConfig(),
Python: defaultPythonConfig(),
Nix: defaultNixConfig(),
Cpp: defaultCppConfig(),
Dotnet: defaultDotnetConfig(),
Golang: defaultGolangConfig(),
Java: defaultJavaConfig(),
@@ -172,11 +175,15 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
}
return pkgcataloging.Config{
Binary: binary.DefaultClassifierCatalogerConfig(),
Cpp: cpp.DefaultCatalogerConfig().
WithVcpkgAllowGitClone(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Cpp, task.Vcpkg), cfg.Cpp.VcpkgAllowGitClone)),
Dotnet: dotnet.DefaultCatalogerConfig().
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
WithPropagateDLLClaimsToParents(cfg.Dotnet.PropagateDLLClaimsToParents).
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected),
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected).
WithExcludeProjectReferences(cfg.Dotnet.ExcludeProjectReferences),
Golang: golang.DefaultCatalogerConfig().
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
@@ -191,7 +198,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
).
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)),
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)).
WithCaptureSymbols(cfg.Golang.CaptureSymbols).
WithCaptureSymbolsModules(cfg.Golang.CaptureSymbolsModules),
JavaScript: javascript.DefaultCatalogerConfig().
WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)).
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
@@ -299,6 +308,7 @@ var publicisedEnrichmentOptions = []string{
task.Java,
task.JavaScript,
task.Python,
task.Vcpkg,
}
func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
+27
View File
@@ -0,0 +1,27 @@
package options
import (
"github.com/anchore/clio"
)
var _ clio.FieldDescriber = (*Convert)(nil)
// Convert holds the options specific to the convert command. These are deliberately configuration-only (config file
// or environment) rather than flags: they change how the command behaves for every invocation in a pipeline and are
// not expected to vary from one call to the next.
type Convert struct {
PassthroughExactFormat bool `yaml:"passthrough-exact-format" json:"passthrough-exact-format" mapstructure:"passthrough-exact-format"`
}
func DefaultConvert() Convert {
return Convert{
PassthroughExactFormat: false,
}
}
func (o *Convert) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.PassthroughExactFormat, `when a syft-json input SBOM already exactly matches the requested output format and schema version, copy it to that output unchanged instead of decoding and re-encoding it.
Note: this only applies to syft-json input, and only when the document's schema version is identical to the schema version this version of syft produces. Any other input is always converted.
Note: output written this way is a byte-for-byte copy of the input, so format options (such as 'format.pretty' or 'format.json.legacy') are not applied to it.
Note: to keep memory use minimal on large documents, give the input as a file path (or a shell redirect) and write the output with '-o <format>=<path>'. Piped input, or output to STDOUT, requires the whole document to be held in memory.`)
}
+30
View File
@@ -0,0 +1,30 @@
package options
import (
"github.com/anchore/clio"
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
)
type cppConfig struct {
VcpkgAllowGitClone *bool `yaml:"vcpkg-allow-git-clone" json:"vcpkg-allow-git-clone" mapstructure:"vcpkg-allow-git-clone"`
}
func defaultCppConfig() cppConfig {
// reference the cataloger default so capability generation can associate this config with the cpp
// ecosystem (it discovers ecosystem configs by their cataloger import). the value itself stays nil:
// nil defaults to false (no network), and leaving it unset lets --enrich opt in. cloning requires a
// network connection, which must be opt-in.
_ = cpp.DefaultCatalogerConfig()
return cppConfig{
VcpkgAllowGitClone: nil,
}
}
var _ interface {
clio.FieldDescriber
} = (*cppConfig)(nil)
func (o *cppConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.VcpkgAllowGitClone, `enables Syft to use clone remote repositories for vcpkg custom git registries.
(also useful if the builtin vcpkg registry is not cloned locally)`)
}
@@ -13,12 +13,10 @@ func DefaultFormatSPDXJSON() FormatSPDXJSON {
}
func (o FormatSPDXJSON) config(v string) spdxjson.EncoderConfig {
var pretty bool
c := spdxjson.DefaultEncoderConfig()
c.Version = v
if o.Pretty != nil {
pretty = *o.Pretty
}
return spdxjson.EncoderConfig{
Version: v,
Pretty: pretty,
c.Pretty = *o.Pretty
}
return c
}
@@ -2,6 +2,8 @@ package options
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestFormatSPDXJSON_buildConfig(t *testing.T) {
@@ -11,5 +13,7 @@ func TestFormatSPDXJSON_buildConfig(t *testing.T) {
ft = setAllToNonZero(t, ft).(*FormatSPDXJSON)
subject := ft.config("Version")
assertExpectedValue(t, subject)
require.Equal(t, "Version", subject.Version)
require.Equal(t, "2.3", subject.DefaultVersion)
require.True(t, subject.Pretty)
}
+41 -2
View File
@@ -4,6 +4,8 @@ import (
"strings"
"github.com/anchore/clio"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/pkg/cataloger/golang"
)
@@ -17,10 +19,13 @@ type golangConfig struct {
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
UsePackagesLib *bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"`
CaptureSymbols cataloging.SymbolScope `json:"capture-symbols" yaml:"capture-symbols" mapstructure:"capture-symbols"`
CaptureSymbolsModules []string `json:"capture-symbols-modules" yaml:"capture-symbols-modules" mapstructure:"capture-symbols-modules"`
}
var _ interface {
clio.FieldDescriber
clio.PostLoader
} = (*golangConfig)(nil)
func (o *golangConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
@@ -39,12 +44,44 @@ if unset this defaults to $GONOPROXY`)
always show (devel) as the version. Use these options to control heuristics to guess
a more accurate version from the binary.`)
descriptions.Add(&o.UsePackagesLib, `use the golang.org/x/tools/go/packages library, which executes golang tooling found on the path in addition to potential network access to get the most accurate results`)
// note: descriptions must be static string literals; the app config discovery that generates the
// capability docs reads them straight out of the AST
descriptions.Add(&o.CaptureSymbols, `capture function symbols from the binary symbol table (pclntab). valid values are:
"none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every
module under golang.org/x/), and "all" (all module packages plus stdlib)`)
descriptions.Add(&o.CaptureSymbolsModules, `glob patterns matched against go module paths (e.g. github.com/klauspost/**) that should have symbols
captured in addition to whatever capture-symbols selects. ** crosses path separators, * does not.
a trailing major version suffix is ignored when matching, so github.com/foo/* covers github.com/foo/bar/v2;
spelling a suffix out in the pattern selects only that major version.
this can only widen the selection, never narrow it, and is inert when capture-symbols is none`)
descriptions.Add(&o.MainModuleVersion.FromLDFlags, `look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)`)
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
(e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise`)
descriptions.Add(&o.MainModuleVersion.FromContents, `search for semver-like strings in the binary contents`)
}
func (o *golangConfig) PostLoad() error {
raw := strings.TrimSpace(string(o.CaptureSymbols))
o.CaptureSymbols = o.CaptureSymbols.Parse()
// an unrecognized value still resolves to "none", but say so rather than silently capturing nothing.
// stay quiet for unset and an explicit "none", which is the default and would otherwise warn on nearly
// every scan.
if o.CaptureSymbols == cataloging.SymbolScopeNone && raw != "" && !strings.EqualFold(raw, string(cataloging.SymbolScopeNone)) {
log.Warnf("unknown golang.capture-symbols value %q, defaulting to %q (valid values: none, stdlib, extended-stdlib, all)", raw, cataloging.SymbolScopeNone)
}
// trim only, deliberately not Flatten: viper already splits a comma-separated scalar (env var or a bare
// yaml string) into a slice before this point, and there is no CLI flag feeding this key. The one thing
// Flatten would add is splitting commas *inside* a list entry, which silently breaks doublestar brace
// alternation like github.com/{foo,bar}/**. Viper's split does not trim, so that part is still needed.
for i, pattern := range o.CaptureSymbolsModules {
o.CaptureSymbolsModules[i] = strings.TrimSpace(pattern)
}
return nil
}
type golangMainModuleVersionConfig struct {
FromLDFlags bool `json:"from-ld-flags" yaml:"from-ld-flags" mapstructure:"from-ld-flags"`
FromContents bool `json:"from-contents" yaml:"from-contents" mapstructure:"from-contents"`
@@ -66,6 +103,8 @@ func defaultGolangConfig() golangConfig {
FromContents: def.MainModuleVersion.FromContents,
FromBuildSettings: def.MainModuleVersion.FromBuildSettings,
},
UsePackagesLib: nil, // this defaults to true, which is the API default
UsePackagesLib: nil, // this defaults to true, which is the API default
CaptureSymbols: def.CaptureSymbols,
CaptureSymbolsModules: def.CaptureSymbolsModules,
}
}
+95
View File
@@ -0,0 +1,95 @@
package options
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cataloging"
)
func Test_golangConfig_PostLoad(t *testing.T) {
tests := []struct {
name string
cfg golangConfig
expected cataloging.SymbolScope
expectedModules []string
wantErr assert.ErrorAssertionFunc
}{
{
name: "normalize all",
cfg: golangConfig{CaptureSymbols: "all"},
expected: cataloging.SymbolScopeAll,
},
{
name: "normalize stdlib",
cfg: golangConfig{CaptureSymbols: "stdlib"},
expected: cataloging.SymbolScopeStdlib,
},
{
name: "normalize extended-stdlib",
cfg: golangConfig{CaptureSymbols: " Extended-Stdlib "},
expected: cataloging.SymbolScopeExtendedStdlib,
},
{
name: "module patterns keep embedded commas",
cfg: golangConfig{
CaptureSymbols: "stdlib",
// brace alternation contains a comma; splitting on it would corrupt the pattern
CaptureSymbolsModules: []string{"github.com/{foo,bar}/**", "golang.org/x/**"},
},
expected: cataloging.SymbolScopeStdlib,
expectedModules: []string{"github.com/{foo,bar}/**", "golang.org/x/**"},
},
{
// viper splits a comma-separated scalar (env var or bare yaml string) but does not trim,
// so a leading space would otherwise survive into a pattern that silently matches nothing
name: "module patterns are trimmed",
cfg: golangConfig{
CaptureSymbols: "stdlib",
CaptureSymbolsModules: []string{"golang.org/x/**", " github.com/foo/** "},
},
expected: cataloging.SymbolScopeStdlib,
expectedModules: []string{"golang.org/x/**", "github.com/foo/**"},
},
{
name: "empty defaults to none",
cfg: golangConfig{CaptureSymbols: ""},
expected: cataloging.SymbolScopeNone,
},
{
name: "invalid value defaults to none",
cfg: golangConfig{CaptureSymbols: "stdlbi"},
expected: cataloging.SymbolScopeNone,
},
{
name: "boolean spellings default to none",
cfg: golangConfig{CaptureSymbols: "true"},
expected: cataloging.SymbolScopeNone,
},
{
name: "explicit none resolves to none",
cfg: golangConfig{CaptureSymbols: "none"},
expected: cataloging.SymbolScopeNone,
},
{
name: "explicit none is not case sensitive",
cfg: golangConfig{CaptureSymbols: " NONE "},
expected: cataloging.SymbolScopeNone,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
err := tt.cfg.PostLoad()
tt.wantErr(t, err)
if err != nil {
return
}
assert.Equal(t, tt.expected, tt.cfg.CaptureSymbols)
assert.Equal(t, tt.expectedModules, tt.cfg.CaptureSymbolsModules)
})
}
}
@@ -524,4 +524,12 @@ var commonTestCases = []testCase{
"kong": "3.7.0-0",
},
},
{
name: "find cpan distribution",
pkgType: pkg.CpanPkg,
pkgLanguage: pkg.Perl,
pkgInfo: map[string]string{
"URI": "5.35",
},
},
}
@@ -89,6 +89,8 @@ func TestPkgCoverageImage(t *testing.T) {
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // we have coverage for pear instead
definedPkgs.Remove(string(pkg.CondaPkg))
definedPkgs.Remove(string(pkg.ModelPkg))
definedPkgs.Remove(string(pkg.VcpkgPkg))
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
var cases []testCase
cases = append(cases, commonTestCases...)
@@ -164,6 +166,8 @@ func TestPkgCoverageDirectory(t *testing.T) {
definedPkgs.Remove(string(pkg.CondaPkg))
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // this is covered as pear packages
definedPkgs.Remove(string(pkg.ModelPkg))
definedPkgs.Remove(string(pkg.VcpkgPkg))
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
// for directory scans we should not expect to see any of the following package types
definedPkgs.Remove(string(pkg.KbPkg))
@@ -0,0 +1,147 @@
package integration
import (
"context"
"encoding/json"
"fmt"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/source"
)
// multiplatformImage is busybox:1.38.0 pinned by index digest, since docker hub periodically rebuilds and
// re-pushes official image tags, which changes the per-platform digests asserted below.
const multiplatformImage = "docker.io/library/busybox@sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e"
// TestMultiPlatformOCIImageSelection verifies that syft can load a single platform out of a
// multi-platform OCI image on disk (both --from oci-dir and --from oci-archive) by selecting the
// image that matches the requested platform.
func TestMultiPlatformOCIImageSelection(t *testing.T) {
// Per-platform image config digests within the multi-platform index, obtained from the OCI layout
// (see anchore/stereoscope integration tests: TestPlatformSelectionWithOciLocalSources).
expectedDigest := map[string]string{
"arm64": "sha256:d2482869a6b838d3b4c9cad0c8085c06277909482771eb525d298fc3a7d07927",
"s390x": "sha256:e55a0fa55032cceac2d983f4b4a25b28e42303b46a2f1915fc0fa45051e6ca70",
"amd64": "sha256:aaef90e065235eb0b2d9938be85d1b81add2902cf89affe2cdfa19be33476ac0",
"ppc64le": "sha256:8158829b015d1c5285d63d54d37e95998ef0af4177e3f416678decbcaff3170c",
}
// syft --from source tag -> stereoscope OCI source used to prepare the local fixture
sources := map[string]image.Source{
"oci-dir": image.OciDirectorySource,
"oci-archive": image.OciTarballSource,
}
for from, imageSource := range sources {
t.Run(from, func(t *testing.T) {
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, multiplatformImage)
for _, arch := range []string{"amd64", "arm64", "s390x", "ppc64le"} {
t.Run(fmt.Sprintf("linux/%s", arch), func(t *testing.T) {
platform, err := image.NewPlatform("linux/" + arch)
require.NoError(t, err)
src, err := syft.GetSource(
context.Background(),
localPath,
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(platform),
)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, src.Close())
})
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
// The raw config of the selected image must match the requested platform...
assertConfigPlatform(t, meta.RawConfig, "linux", arch)
assert.Equal(t, "linux", meta.OS)
assert.Equal(t, arch, meta.Architecture)
// ...and it must be the exact per-platform image from the multi-platform index.
assert.Equal(t, expectedDigest[arch], meta.ID)
})
}
})
}
}
// TestMultiPlatformOCIImageSelection_UnavailablePlatform verifies that requesting a platform not present
// in the multi-platform OCI image results in an error rather than silently selecting the wrong image.
func TestMultiPlatformOCIImageSelection_UnavailablePlatform(t *testing.T) {
// windows/amd64 is not present in this linux-only multi-platform image
platform, err := image.NewPlatform("windows/amd64")
require.NoError(t, err)
sources := map[string]image.Source{
"oci-dir": image.OciDirectorySource,
"oci-archive": image.OciTarballSource,
}
for from, imageSource := range sources {
t.Run(from, func(t *testing.T) {
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, multiplatformImage)
_, err := syft.GetSource(
context.Background(),
localPath,
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(platform),
)
require.ErrorContains(t, err, "windows/amd64")
})
}
}
// TestMultiPlatformOCIImageSelection_DefaultPlatform verifies that not specifying a platform results
// in the current platform being selected.
func TestMultiPlatformOCIImageSelection_DefaultPlatform(t *testing.T) {
sources := map[string]image.Source{
"oci-dir": image.OciDirectorySource,
"oci-archive": image.OciTarballSource,
}
for from, imageSource := range sources {
t.Run(from, func(t *testing.T) {
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, multiplatformImage)
src, err := syft.GetSource(
context.Background(),
localPath,
syft.DefaultGetSourceConfig().WithSources(from),
)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, src.Close())
})
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
// The raw config of the selected image must match the current platform
assertConfigPlatform(t, meta.RawConfig, "linux", runtime.GOARCH)
assert.Equal(t, "linux", meta.OS)
assert.Equal(t, runtime.GOARCH, meta.Architecture)
})
}
}
// assertConfigPlatform asserts the os/architecture recorded in a raw OCI image config document.
func assertConfigPlatform(t *testing.T, rawConfig []byte, os, architecture string) {
t.Helper()
require.NotEmpty(t, rawConfig)
var cfg struct {
OS string `json:"os"`
Architecture string `json:"architecture"`
}
require.NoError(t, json.Unmarshal(rawConfig, &cfg))
assert.Equal(t, os, cfg.OS)
assert.Equal(t, architecture, cfg.Architecture)
}
@@ -0,0 +1,64 @@
package integration
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/source"
)
// TestSinglePlatformOCIImage covers the single-image (non-index) OCI layouts, where syft used to report
// empty os/architecture metadata and silently accept any --platform. Both now come from the image config.
func TestSinglePlatformOCIImage(t *testing.T) {
// a platform this fixture is never built for, so the mismatch is not host dependent
unavailablePlatform, err := image.NewPlatform("linux/ppc64le")
require.NoError(t, err)
sources := map[string]image.Source{
"oci-dir": image.OciDirectorySource,
"oci-archive": image.OciTarballSource,
}
for from, imageSource := range sources {
t.Run(from, func(t *testing.T) {
localPath := strings.TrimPrefix(imagetest.PrepareFixtureImage(t, imageSource, "image-distro-id"), string(imageSource)+":")
t.Run("reports the platform from the image config", func(t *testing.T) {
src, err := syft.GetSource(
context.Background(),
localPath,
syft.DefaultGetSourceConfig().WithSources(from),
)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, src.Close())
})
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
// the fixture is built for whatever the local docker daemon runs, so assert against the
// config rather than a fixed platform
assert.NotEmpty(t, meta.OS)
assert.NotEmpty(t, meta.Architecture)
assertConfigPlatform(t, meta.RawConfig, meta.OS, meta.Architecture)
})
t.Run("rejects a platform the image does not match", func(t *testing.T) {
_, err := syft.GetSource(
context.Background(),
localPath,
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(unavailablePlatform),
)
require.ErrorContains(t, err, "linux/ppc64le")
})
})
}
}
@@ -7,4 +7,6 @@
!image-pkg-coverage/pkgs/java/*.hpi
**/go.sum
!image-go-bin-arch-coverage/go.sum
!image-go-bin-arch-coverage/go.sum
cache/
@@ -8,8 +8,6 @@ RUN mkdir tmp
WORKDIR /usr/share/jenkins/tmp
RUN apt-get update 2>&1 > /dev/null && apt-get install -y less zip 2>&1 > /dev/null
RUN unzip ../jenkins.war 2>&1 > /dev/null
RUN rm -rf ./META-INF/MANIFEST.MF ./WEB-INF ./jsbundles ./scripts ./css
@@ -18,7 +16,8 @@ WORKDIR /usr/share/jenkins
RUN rm -rf jenkins.war
RUN cd ./tmp && zip -r ../jenkins.war . && cd ..
# use jar (from the JDK in the base image) to rezip -- M keeps it manifest-less, which is the point of this fixture
RUN cd ./tmp && jar cfM ../jenkins.war . && cd ..
RUN rm -rf ./tmp
@@ -16,7 +16,7 @@ RUN unxz /lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko.xz
# dotnet pkg coverage
# https://nuget.info/packages/DocuSign.eSign.dll/6.8.0
# https://github.com/docusign/docusign-esign-csharp-client/blob/master/LICENSE
RUN curl -LO https://www.nuget.org/api/v2/package/DocuSign.eSign.dll/6.8.0
RUN curl -fLO https://www.nuget.org/api/v2/package/DocuSign.eSign.dll/6.8.0
RUN unzip 6.8.0
RUN chmod 600 lib/net462/DocuSign.eSign.dll
RUN rm 6.8.0
@@ -0,0 +1,9 @@
{
"abstract": "Uniform Resource Identifiers (absolute and relative)",
"license": ["perl_5"],
"meta-spec": {"version": 2},
"name": "URI",
"prereqs": {"runtime": {"requires": {"Carp": "0", "perl": "5.008001"}}},
"release_status": "stable",
"version": "5.35"
}
@@ -0,0 +1,11 @@
{
"dist": "URI-5.35",
"name": "URI",
"pathname": "O/OA/OALDERS/URI-5.35.tar.gz",
"provides": {
"URI": {"file": "lib/URI.pm", "version": "5.35"},
"URI::Escape": {"file": "lib/URI/Escape.pm", "version": "5.35"}
},
"target": "URI",
"version": "5.35"
}
+46 -2
View File
@@ -1,6 +1,7 @@
package ui
import (
"bytes"
"fmt"
"io"
"os"
@@ -8,6 +9,7 @@ import (
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly"
@@ -52,7 +54,7 @@ func New(out io.Writer, quiet bool, handlers ...bubbly.EventHandler) *UI {
func (m *UI) Setup(subscription partybus.Unsubscribable) error {
// we still want to collect log messages, however, we also the logger shouldn't write to the screen directly
if logWrapper, ok := log.Get().(logger.Controller); ok {
logWrapper.SetOutput(m.frame.(*frame.Frame).Footer())
logWrapper.SetOutput(&logFooter{w: m.frame.(*frame.Frame).Footer()})
}
m.subscription = subscription
@@ -100,7 +102,7 @@ func (m *UI) Teardown(force bool) error {
})
// it may be tempting to use Kill() however it has been found that this can cause the terminal to be left in
// a bad state (where Ctrl+C and other control characters no longer works for future processes in that terminal).
// a bad state (where Ctrl+C and other control characters no longer work for future processes in that terminal).
m.program.Quit()
_ = runWithTimeout(250*time.Millisecond, func() error {
@@ -187,6 +189,48 @@ func (m UI) View() string {
return m.frame.View()
}
var (
logTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey
// the first log line gets a branch pointing at it from the title, the rest are indented to match
logBranch = logTitleStyle.Render("└─▶") + " "
logIndent = " "
)
// logFooter decorates log entries drawn below the TUI with a one-time title (written when the first entry arrives)
// and a branch from the title to the first log line. Writes are serialized by the logger.
type logFooter struct {
w io.Writer
started bool
}
func (l *logFooter) Write(p []byte) (int, error) {
var buf bytes.Buffer
first := !l.started
if first {
buf.WriteString(logTitleStyle.Render("Logs (non-fatal, see -v for more):") + "\n")
l.started = true
}
for i, line := range bytes.SplitAfter(p, []byte("\n")) {
if len(line) == 0 {
continue
}
if first && i == 0 {
buf.WriteString(logBranch)
} else {
buf.WriteString(logIndent)
}
buf.Write(line)
}
if _, err := l.w.Write(buf.Bytes()); err != nil {
return 0, err
}
return len(p), nil
}
// IsTerminal marks the footer as terminal-bound (it is drawn by the TUI) so the logger keeps colors. NO_COLOR is
// still honored by the formatter.
func (*logFooter) IsTerminal() bool { return true }
func runWithTimeout(timeout time.Duration, fn func() error) (err error) {
c := make(chan struct{}, 1)
go func() {
+3 -1
View File
@@ -5,8 +5,10 @@ This directory contains examples of how to use the Syft API.
- `create_simple_sbom`: Create a simple SBOM from scratch
- `create_custom_sbom`: Create an SBOM using as much custom configuration as possible, including a custom cataloger implementation
- `decode_sbom`: Take an existing SBOM file (of arbitrary format) and decode it into a Syft SBOM object
- `select_catalogers`: Example demonstrating how to select specific catalogers to run
- `source_detection`: Shows how to detect what to catalog automatically from a user string (e.g. container image vs directory)
- `source_from_image`: Construct a source from a only a container image
- `source_from_image`: Construct a source from only a container image
- `source_from_registry`: Construct a source directly from a container registry
You can run any of these examples from this directory with:
+176 -152
View File
@@ -1,72 +1,73 @@
module github.com/anchore/syft
go 1.25.8
go 1.26.8
require (
github.com/BurntSushi/toml v1.6.0
github.com/CycloneDX/cyclonedx-go v0.10.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/CycloneDX/cyclonedx-go v0.12.0
github.com/Masterminds/semver/v3 v3.5.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/OneOfOne/xxhash v1.2.8
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/acobaugh/osrelease v0.1.0
github.com/adrg/xdg v0.5.3
github.com/anchore/bubbly v0.2.0
github.com/anchore/clio v0.1.0
github.com/anchore/fangs v0.1.0
github.com/anchore/go-collections v0.1.0
github.com/anchore/go-homedir v0.1.0
github.com/anchore/go-logger v0.1.0
github.com/anchore/go-macholibre v0.1.0
github.com/anchore/go-rpmdb v0.1.0
github.com/anchore/go-sync v0.1.0
github.com/anchore/bubbly v0.2.2
github.com/anchore/clio v0.1.1
github.com/anchore/fangs v0.1.1
github.com/anchore/go-collections v0.1.1
github.com/anchore/go-homedir v0.1.1
github.com/anchore/go-logger v0.2.0
github.com/anchore/go-macholibre v0.1.1
github.com/anchore/go-rpmdb v0.2.0
github.com/anchore/go-sync v0.1.2
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.2.0
github.com/anchore/stereoscope v0.1.23
github.com/anchore/stereoscope v0.3.3
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/aquasecurity/go-pep440-version v0.0.1
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/bmatcuk/doublestar/v4 v4.10.0
github.com/bmatcuk/doublestar/v4 v4.10.2
github.com/cespare/xxhash/v2 v2.3.0
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/dave/jennifer v1.7.1
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
github.com/diskfs/go-diskfs v1.7.0
github.com/diskfs/go-diskfs v1.9.4
github.com/distribution/reference v0.6.0
github.com/dustin/go-humanize v1.0.1
github.com/dustin/go-humanize v1.1.0
github.com/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.4.0
github.com/gkampitakis/go-snaps v0.5.21
github.com/go-git/go-billy/v5 v5.9.0
github.com/go-git/go-git/v5 v5.19.0
github.com/github/go-spdx/v2 v2.7.0
github.com/gkampitakis/go-snaps v0.5.23
github.com/go-git/go-billy/v5 v5.9.1
github.com/go-git/go-git/v5 v5.19.2
github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/goccy/go-yaml v1.19.2
github.com/gohugoio/hashstructure v0.6.0
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.21.5
github.com/google/go-containerregistry v0.22.1
github.com/google/licensecheck v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.6.0
github.com/gpustack/gguf-parser-go v0.24.0
github.com/gookit/color v1.6.1
github.com/gpustack/gguf-parser-go v0.26.3
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-getter v1.8.6
github.com/hashicorp/go-getter v1.8.9
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/hcl/v2 v2.24.0
github.com/hashicorp/hcl/v2 v2.25.0
github.com/iancoleman/strcase v0.3.0
github.com/invopop/jsonschema v0.13.0
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/invopop/jsonschema v0.14.0
github.com/jedib0t/go-pretty/v6 v6.8.3
github.com/jinzhu/copier v0.4.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/magiconair/properties v1.8.10
github.com/klauspost/compress v1.20.0
github.com/magiconair/properties v1.18.12
github.com/mholt/archives v0.1.5
github.com/moby/sys/mountinfo v0.7.2
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
github.com/olekukonko/tablewriter v1.1.4
github.com/olekukonko/tablewriter v1.1.5
github.com/opencontainers/go-digest v1.0.0
github.com/pelletier/go-toml v1.9.5
github.com/quasilyte/go-ruleguard/dsl v0.3.23
@@ -78,75 +79,80 @@ require (
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
github.com/sergi/go-diff v1.4.0
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb
github.com/spdx/tools-golang v0.5.7
github.com/spdx/tools-golang v0.6.0-rc4
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/ulikunitz/xz v0.5.15
github.com/stretchr/testify v1.12.1
github.com/ulikunitz/xz v0.5.17
github.com/vbatts/go-mtree v0.7.0
github.com/vifraa/gopom v1.0.0
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
github.com/wagoodman/go-progress v0.0.0-20260303201901-10176f79b2c0
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
go.uber.org/goleak v1.3.0
go.yaml.in/yaml/v3 v3.0.4
go.yaml.in/yaml/v3 v3.0.5
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/mod v0.35.0
golang.org/x/net v0.53.0
golang.org/x/time v0.15.0
golang.org/x/tools v0.44.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.46.2
golang.org/x/mod v0.41.0
golang.org/x/net v0.59.0
golang.org/x/time v0.16.0
golang.org/x/tools v0.50.0
modernc.org/sqlite v1.59.0
)
require (
cel.dev/expr v0.25.1 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd
howett.net/plist v1.0.1
)
require (
cel.dev/expr v0.25.2 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth v0.23.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
cloud.google.com/go/storage v1.61.3 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
cloud.google.com/go/iam v1.12.0 // indirect
cloud.google.com/go/monitoring v1.30.0 // indirect
cloud.google.com/go/storage v1.65.1 // indirect
cyphar.com/go-pathrs v0.2.5 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect
github.com/ProtonMail/go-crypto v1.4.0 // indirect
github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect
github.com/Microsoft/hcsshim v0.15.0-rc.4 // indirect
github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-struct-converter v0.1.0 // indirect
github.com/anchore/go-lzo v0.1.1 // indirect
github.com/anchore/go-struct-converter v0.2.0-rc2 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.44.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.40 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.39 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.10.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.41 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.108.0 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 // indirect
github.com/aws/smithy-go v1.28.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
@@ -155,59 +161,62 @@ require (
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/containerd/cgroups/v3 v3.1.2 // indirect
github.com/containerd/containerd/api v1.10.0 // indirect
github.com/containerd/containerd/v2 v2.2.2 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
github.com/containerd/cgroups/v3 v3.1.3 // indirect
github.com/containerd/containerd/api v1.12.0 // indirect
github.com/containerd/containerd/v2 v2.4.1 // indirect
github.com/containerd/continuity v0.5.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v1.0.0-rc.4 // indirect
github.com/containerd/plugin v1.0.0 // indirect
github.com/containerd/log v0.2.0 // indirect
github.com/containerd/log/otel v0.1.0 // indirect
github.com/containerd/platforms v1.0.0-rc.5 // indirect
github.com/containerd/plugin v1.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v29.4.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/containerd/ttrpc v1.2.9 // indirect
github.com/containerd/typeurl/v2 v2.3.0 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.3.2 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
github.com/cyphar/filepath-securejoin v0.7.0 // indirect
github.com/docker/cli v29.8.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.8 // indirect
github.com/docker/go-connections v0.8.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gkampitakis/ciinfo v0.3.2 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
github.com/gkampitakis/ciinfo v0.3.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-jose/go-jose/v4 v4.1.5 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect
github.com/googleapis/gax-go/v2 v2.24.0 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/henvic/httpretty v0.1.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -215,115 +224,130 @@ require (
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maruel/natural v1.1.1 // indirect
github.com/maruel/natural v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.27 // indirect
github.com/mattn/go-sqlite3 v1.14.48 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.2 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mistifyio/go-zfs/v4 v4.0.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/moby/api v1.54.1 // indirect
github.com/moby/moby/client v0.4.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/moby/api v1.56.0 // indirect
github.com/moby/moby/client v0.6.0 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/sys/user v0.4.1 // indirect
github.com/moby/sys/userns v0.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/nwaples/rardecode/v2 v2.4.1 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.6 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/opencontainers/selinux v1.13.1 // indirect
github.com/opencontainers/selinux v1.15.1 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/piprate/json-gold v0.7.0 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/pkg/xattr v0.4.12 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/proglottis/gpgme v0.1.6 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/sigstore/fulcio v1.8.7 // indirect
github.com/sigstore/protobuf-specs v0.5.1 // indirect
github.com/sigstore/sigstore v1.10.8 // indirect
github.com/sirupsen/logrus v1.10.2 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect
github.com/smallstep/pkcs7 v0.2.1 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/sylabs/sif/v2 v2.24.0 // indirect
github.com/sylabs/sif/v2 v2.24.1 // indirect
github.com/sylabs/squashfs v1.0.6 // indirect
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/gjson v1.19.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/vbatts/tar-split v0.12.3 // indirect
github.com/vbauerster/cupwriter v0.0.4 // indirect
github.com/vbauerster/mpb/v8 v8.14.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
go.opencensus.io v0.24.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/zclconf/go-cty v1.19.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect
go.opentelemetry.io/otel v1.46.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/otel/sdk v1.46.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect
go.opentelemetry.io/otel/trace v1.46.0 // indirect
go.podman.io/image/v5 v5.41.2 // indirect
go.podman.io/storage v1.64.1 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/crypto v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/sync v0.23.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/term v0.46.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/api v0.271.0 // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
google.golang.org/api v0.294.0 // indirect
google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
google.golang.org/grpc v1.83.2 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/libc v1.75.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/memory v1.12.1 // indirect
)
retract (
+419 -317
View File
File diff suppressed because it is too large Load Diff
+57 -27
View File
@@ -8,7 +8,6 @@ REPO="${PROJECT_NAME}"
GITHUB_DOWNLOAD_PREFIX=https://github.com/${OWNER}/${REPO}/releases/download
INSTALL_SH_BASE_URL=https://get.anchore.io/${PROJECT_NAME}
LEGACY_INSTALL_SH_BASE_URL=https://raw.githubusercontent.com/${OWNER}/${PROJECT_NAME}
PROGRAM_ARGS=$@
# signature verification options
@@ -19,6 +18,8 @@ VERIFY_SIGN=false
VERIFY_SIGN_SUPPORTED_VERSION=v0.104.0
# this is the earliest tag in the repo where the -v flag was introduced to this install.sh script
VERIFY_SIGN_FLAG_VERSION=v1.6.0
# releases at or after this version are signed with a sigstore bundle (.sigstore.json); earlier releases ship a separate .sig and .pem
VERIFY_SIGN_BUNDLE_VERSION=v1.53.0
# do not change the name of this parameter (this must always be backwards compatible)
DOWNLOAD_TAG_INSTALL_SCRIPT=${DOWNLOAD_TAG_INSTALL_SCRIPT:-true}
@@ -35,7 +36,8 @@ is_command() (
)
echo_stderr() (
echo "$@" 1>&2
IFS=" "
printf "%b\n" "$*" 1>&2
)
_logp=2
@@ -75,12 +77,12 @@ init_colors
log_tag() (
case $1 in
0) echo "${RED}${BOLD}[error]${RESET}" ;;
1) echo "${RED}[warn]${RESET}" ;;
2) echo "[info]${RESET}" ;;
3) echo "${BLUE}[debug]${RESET}" ;;
4) echo "${PURPLE}[trace]${RESET}" ;;
*) echo "[$1]" ;;
0) printf "%b" "${RED}${BOLD}[error]${RESET}" ;;
1) printf "%b" "${RED}[warn]${RESET}" ;;
2) printf "%b" "[info]${RESET}" ;;
3) printf "%b" "${BLUE}[debug]${RESET}" ;;
4) printf "%b" "${PURPLE}[trace]${RESET}" ;;
*) printf "[%s]" "$1" ;;
esac
)
@@ -385,8 +387,8 @@ download_github_release_checksums_files() (
complete_url=$(github_release_asset_url "${download_url}" "${name}" "${version}" "${filename}")
output_path="${output_dir}/${complete_filename}"
http_download "${output_path}" "${complete_url}" ""
asset_file_exists "${output_path}"
http_download "${output_path}" "${complete_url}" "" || return 1
asset_file_exists "${output_path}" || return 1
log_trace "download_github_release_checksums_files() returned '${output_path}' for file '${complete_filename}'"
@@ -584,28 +586,31 @@ download_and_install_asset() (
install_asset "${asset_filepath}" "${install_path}" "${binary}"
)
# verify_sign [checksums-file-path] [certificate-reference] [signature-reference] [version]
# verify_sign [checksums-file-path] [verification-material-flags...]
#
# attempts verify the signature of the checksums file from the release workflow in Github Actions run against the main branch.
# the verification material is either "--bundle <path>" or (for older releases) "--certificate <ref> --signature <ref>".
#
verify_sign() {
checksums_file=$1
cert_reference=$2
sig_reference=$3
shift
log_trace "verifying artifact $1"
log_trace "verifying artifact ${checksums_file}"
log_file=$(mktemp)
${COSIGN_BINARY} \
verify-blob "$checksums_file" \
--certificate "$cert_reference" \
--signature "$sig_reference" \
"$@" \
--certificate-identity "https://github.com/${OWNER}/${REPO}/.github/workflows/release.yaml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" > "${log_file}" 2>&1
if [ $? -ne 0 ]; then
log_err "$(cat "${log_file}")"
# cosign older than v2.4.2 can't read the bundle's verification material and reports a missing cert
if [ "$1" = "--bundle" ] && grep -q "bundle does not contain cert" "${log_file}"; then
log_err "note: releases >= ${VERIFY_SIGN_BUNDLE_VERSION} are verified with a sigstore bundle, which requires cosign v2.5.0 or newer (check '${COSIGN_BINARY} version')"
fi
rm -f "${log_file}"
return 1
fi
@@ -629,7 +634,10 @@ download_asset() (
log_trace "download_asset(url=${download_url}, destination=${destination}, name=${name}, os=${os}, arch=${arch}, version=${version}, format=${format})"
checksums_filepath=$(download_github_release_checksums "${download_url}" "${name}" "${version}" "${destination}")
if ! checksums_filepath=$(download_github_release_checksums "${download_url}" "${name}" "${version}" "${destination}"); then
log_err "unable to download checksums file"
return 1
fi
log_trace "checksums content:\n$(cat ${checksums_filepath})"
@@ -641,13 +649,25 @@ download_asset() (
fi
if [ "$VERIFY_SIGN" = true ]; then
checksum_sig_file_url=$(github_release_checksums_sig_url "${download_url}" "${name}" "${version}")
log_trace "checksums signature url: ${checksum_sig_file_url}"
if compare_semver "${version}" "${VERIFY_SIGN_BUNDLE_VERSION}"; then
if ! checksums_bundle_filepath=$(download_github_release_checksums_files "${download_url}" "${name}" "${version}" "${destination}" "checksums.txt.sigstore.json"); then
log_err "unable to download checksums signature bundle"
return 1
fi
log_trace "checksums bundle: ${checksums_bundle_filepath}"
checksums_cert_file_url=$(github_release_checksums_cert_url "${download_url}" "${name}" "${version}")
log_trace "checksums certificate url: ${checksums_cert_file_url}"
set -- --bundle "${checksums_bundle_filepath}"
else
checksum_sig_file_url=$(github_release_checksums_sig_url "${download_url}" "${name}" "${version}")
log_trace "checksums signature url: ${checksum_sig_file_url}"
if ! verify_sign "${checksums_filepath}" "${checksums_cert_file_url}" "${checksum_sig_file_url}"; then
checksums_cert_file_url=$(github_release_checksums_cert_url "${download_url}" "${name}" "${version}")
log_trace "checksums certificate url: ${checksums_cert_file_url}"
set -- --certificate "${checksums_cert_file_url}" --signature "${checksum_sig_file_url}"
fi
if ! verify_sign "${checksums_filepath}" "$@"; then
log_err "signature verification failed"
return 1
fi
@@ -656,9 +676,15 @@ download_asset() (
asset_url="${download_url}/${asset_filename}"
asset_filepath="${destination}/${asset_filename}"
http_download "${asset_filepath}" "${asset_url}" ""
if ! http_download "${asset_filepath}" "${asset_url}" ""; then
log_err "unable to download asset '${asset_url}'"
return 1
fi
hash_sha256_verify "${asset_filepath}" "${checksums_filepath}"
# this is what ties the asset to the (optionally signature verified) checksums file, so a mismatch must stop the install
if ! hash_sha256_verify "${asset_filepath}" "${checksums_filepath}"; then
return 1
fi
log_trace "download_asset_by_checksums_file() returned '${asset_filepath}'"
@@ -764,7 +790,7 @@ prep_signature_verification() {
fi
}
main() (
parse_install_args() {
# parse arguments
# note: never change default install directory (this must always be backwards compatible)
@@ -804,6 +830,11 @@ EOF
set +u
tag=$1
set -u
}
main() (
parse_install_args "$@"
if [ -z "${tag}" ]; then
log_info "checking github for the current release tag"
@@ -811,7 +842,6 @@ EOF
else
log_info "checking github for release tag='${tag}'"
fi
set -u
if ! tag=$(get_release_tag "${OWNER}" "${REPO}" "${tag}"); then
log_err "unable to find tag='${tag}'"
@@ -842,7 +872,7 @@ EOF
log_warn "failed to fetch from ${INSTALL_SH_BASE_URL}, trying fallback URL"
install_script=$(http_copy "${LEGACY_INSTALL_SH_BASE_URL}/${tag}/install.sh" "")
fi
echo "${install_script}" | sh -s -- ${PROGRAM_ARGS}
echo "${install_script}" | sh -s -- "$@"
exit $?
fi
+6
View File
@@ -2,6 +2,8 @@
# This file is partially auto-generated. Run 'go generate ./internal/capabilities' to regenerate.
application: # AUTO-GENERATED - application-level config keys
- key: cpp.vcpkg-allow-git-clone
description: enables Syft to use clone remote repositories for vcpkg custom git registries. (also useful if the builtin vcpkg registry is not cloned locally)
- key: dotnet.dep-packages-must-claim-dll
description: only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk). The package is also included if any child package claims a DLL, even if the package itself does not claim a DLL.
- key: dotnet.dep-packages-must-have-dll
@@ -12,6 +14,10 @@ application: # AUTO-GENERATED - application-level config keys
description: treat DLL claims or on-disk evidence for child packages as DLL claims or on-disk evidence for any parent package
- key: dotnet.relax-dll-claims-when-bundling-detected
description: show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack)
- key: golang.capture-symbols
description: 'capture function symbols from the binary symbol table (pclntab). valid values are: "none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every module under golang.org/x/), and "all" (all module packages plus stdlib)'
- key: golang.capture-symbols-modules
description: glob patterns matched against go module paths (e.g. github.com/klauspost/**) that should have symbols captured in addition to whatever capture-symbols selects. ** crosses path separators, * does not. a trailing major version suffix is ignored when matching, so github.com/foo/* covers github.com/foo/bar/v2; spelling a suffix out in the pattern selects only that major version. this can only widen the selection, never narrow it, and is inert when capture-symbols is none
- key: golang.local-mod-cache-dir
description: specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
- key: golang.local-vendor-dir
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"sort"
"github.com/scylladb/go-set/strset"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
"github.com/anchore/syft/internal/task"
)
+8 -3
View File
@@ -1,4 +1,4 @@
// this file handles YAML file reading and writing with comment preservation, using gopkg.in/yaml.v3's node tree to maintain all existing comments during regeneration.
// this file handles YAML file reading and writing with comment preservation, using go.yaml.in/yaml/v3's node tree to maintain all existing comments during regeneration.
package main
import (
@@ -6,7 +6,7 @@ import (
"os"
"regexp"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
"github.com/anchore/syft/internal/capabilities"
"github.com/anchore/syft/internal/capabilities/internal"
@@ -297,6 +297,11 @@ func addCatalogerFieldComment(keyNode, valueNode *yaml.Node, catalogerName strin
if keyNode.LineComment == "" {
keyNode.LineComment = autoGeneratedComment
}
case "purl_types":
// cataloger-level purl_types (for custom catalogers) are AUTO-GENERATED
if keyNode.LineComment == "" {
keyNode.LineComment = autoGeneratedComment
}
case "json_schema_types":
// json_schema_types are AUTO-GENERATED
if keyNode.LineComment == "" {
@@ -367,7 +372,7 @@ func addParserComments(parsersNode *yaml.Node) {
valueNode := parserNode.Content[i+1]
switch keyNode.Value {
case "parser_function", "metadata_types", "package_types", "json_schema_types":
case "parser_function", "metadata_types", "package_types", "purl_types", "json_schema_types":
// add AUTO-GENERATED comment to these fields
if keyNode.LineComment == "" {
keyNode.LineComment = autoGeneratedComment
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
)
func TestFindFieldValue(t *testing.T) {
+57 -2
View File
@@ -3,6 +3,7 @@ package main
import (
"fmt"
"sort"
"strings"
"github.com/scylladb/go-set/strset"
@@ -10,6 +11,7 @@ import (
"github.com/anchore/syft/internal/capabilities"
"github.com/anchore/syft/internal/capabilities/internal"
"github.com/anchore/syft/internal/packagemetadata"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/binary"
)
@@ -192,6 +194,9 @@ func RegenerateCapabilities(catalogerDir string, repoRoot string) (*Statistics,
formatOrphans(orphans))
}
// 5a. Derive PURL types from the discovered package types (purely a function of pkg.Type)
derivePURLTypes(updated)
// 6. Write back to YAML files with comments, preserving existing node trees
fmt.Print(" → Writing updated capabilities files...")
if err := saveCapabilities(catalogerDir, repoRoot, updated, existingNodes); err != nil {
@@ -427,6 +432,39 @@ func (e *EnrichmentData) GetMetadataTypes(catalogerName string) ([]string, bool)
return types, ok
}
// derivePURLTypes populates the PURLTypes field on every cataloger and parser by mapping each
// observed package type to its canonical PURL type via pkg.Type.PackageURLType. this is a pure
// function of the already-discovered package_types, so it needs no additional test observations.
func derivePURLTypes(doc *capabilities.Document) {
for i := range doc.Catalogers {
cat := &doc.Catalogers[i]
cat.PURLTypes = purlTypesFromPackageTypes(cat.PackageTypes)
for j := range cat.Parsers {
cat.Parsers[j].PURLTypes = purlTypesFromPackageTypes(cat.Parsers[j].PackageTypes)
}
}
}
// purlTypesFromPackageTypes maps syft package type strings (e.g. "go-module") to their PURL types
// (e.g. "golang"), returning a deduplicated, sorted slice. package types with no PURL mapping are skipped.
func purlTypesFromPackageTypes(packageTypes []string) []string {
if len(packageTypes) == 0 {
return nil
}
set := strset.New()
for _, pt := range packageTypes {
if purlType := pkg.Type(pt).PackageURLType(); purlType != "" {
set.Add(purlType)
}
}
if set.IsEmpty() {
return nil
}
result := set.List()
sort.Strings(result)
return result
}
// GetPackageTypes returns the package types for the given cataloger name
func (e *EnrichmentData) GetPackageTypes(catalogerName string) ([]string, bool) {
types, ok := e.packageTypes[catalogerName]
@@ -464,6 +502,19 @@ func convertToJSONSchemaTypesFromMetadata(metadataTypes []string) []string {
return result
}
// packageTypeConstName renders a package type using its go constant name, which is what the detector
// "type" field reports (e.g. "BinaryPkg").
// a switch rather than a derivation, since pkg.Type values don't reliably map back to their constant
// names (e.g. "java-archive" is JavaPkg); add an arm when a classifier declares a new type.
func packageTypeConstName(t pkg.Type) string {
switch t {
case pkg.CpanPkg:
return "CpanPkg"
default:
return "BinaryPkg"
}
}
// EnrichWithBinaryClassifier enriches an entry with binary classifier detectors if it's the binary-classifier-cataloger
func (e *EnrichmentData) EnrichWithBinaryClassifier(catalogerName string, entry *capabilities.CatalogerEntry) {
// special handling for binary-classifier-cataloger: auto-generate one detector per classifier
@@ -485,16 +536,20 @@ func (e *EnrichmentData) EnrichWithBinaryClassifier(catalogerName string, entry
Name: classifier.Package,
PURL: purlStr,
CPEs: cpeStrings,
Type: "BinaryPkg",
Type: packageTypeConstName(classifier.PackageType()),
},
}
for _, o := range binaryClassifierOverrides[classifier.Class] {
cpeStrings := make([]string, len(o.CPEs))
for i, c := range o.CPEs {
cpeStrings[i] = c.Attributes.BindToFmtString()
}
packages = append(packages, capabilities.DetectorPackageInfo{
Class: o.Class,
Name: o.Package,
PURL: stripPURLVersion(o.PURL),
CPEs: o.CPEs,
CPEs: cpeStrings,
Type: "BinaryPkg",
})
}
@@ -552,3 +552,48 @@ func TestConvertToJSONSchemaTypesFromMetadata(t *testing.T) {
})
}
}
func TestPURLTypesFromPackageTypes(t *testing.T) {
tests := []struct {
name string
packageTypes []string
want []string
}{
{
name: "nil slice returns nil",
packageTypes: nil,
want: nil,
},
{
name: "single package type maps to purl type",
packageTypes: []string{"go-module"},
want: []string{"golang"},
},
{
name: "multiple package types are sorted",
packageTypes: []string{"npm", "go-module"},
want: []string{"golang", "npm"},
},
{
// java and jenkins-plugin both map to maven, so the result is deduplicated
name: "distinct package types collapsing to one purl type are deduplicated",
packageTypes: []string{"java-archive", "jenkins-plugin"},
want: []string{"maven"},
},
{
// unknown package types have no purl mapping and are skipped
name: "package type without a purl mapping is skipped",
packageTypes: []string{"UnknownPackage"},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := purlTypesFromPackageTypes(tt.packageTypes)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("purlTypesFromPackageTypes() mismatch (-want +got):\n%s", diff)
}
})
}
}
+38 -3
View File
@@ -10,7 +10,7 @@ type binaryClassifierOverride struct {
Class string
Package string
PURL string
CPEs []string
CPEs []cpe.CPE
}
var binaryClassifierOverrides = map[string][]binaryClassifierOverride{
@@ -110,12 +110,47 @@ var binaryClassifierOverrides = map[string][]binaryClassifierOverride{
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
},
"mysqld-binary": {
{
Class: "mysqld-mysql-cluster-legacy-binary",
Package: "mysql-cluster",
PURL: mustPURL("pkg:generic/mysql-cluster@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:mysql_cluster:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
},
{
Class: "mysqld-mysql-cluster-binary",
Package: "mysql-cluster",
PURL: mustPURL("pkg:generic/mysql-cluster@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
cpe.Must("cpe:2.3:a:oracle:mysql_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
cpe.Must("cpe:2.3:a:oracle:mysql_cluster:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
},
{
Class: "mysqld-mysql-server-binary",
Package: "mysql-server",
PURL: mustPURL("pkg:generic/mysql-server@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
cpe.Must("cpe:2.3:a:oracle:mysql_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
},
},
}
func mustPURL(purl string) string {
return purl
}
func singleCPE(cpeString string, _ ...any) []string {
return []string{cpeString}
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
src := cpe.GeneratedSource
if len(source) > 0 {
src = source[0]
}
return []cpe.CPE{
cpe.Must(cpeString, src),
}
}
@@ -5,7 +5,7 @@ import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
"github.com/anchore/syft/internal/capabilities"
)
+2
View File
@@ -82,6 +82,7 @@ type CatalogerEntry struct {
Detectors []Detector `yaml:"detectors,omitempty" json:"detectors,omitempty"` // AUTO-GENERATED - detection methods (only for type=custom)
MetadataTypes []string `yaml:"metadata_types,omitempty" json:"metadata_types,omitempty"` // AUTO-GENERATED - pkg metadata types emitted (only for type=custom)
PackageTypes []string `yaml:"package_types,omitempty" json:"package_types,omitempty"` // AUTO-GENERATED - package types emitted (only for type=custom)
PURLTypes []string `yaml:"purl_types,omitempty" json:"purl_types,omitempty"` // AUTO-GENERATED - PURL types derived from package_types (only for type=custom)
JSONSchemaTypes []string `yaml:"json_schema_types,omitempty" json:"json_schema_types,omitempty"` // AUTO-GENERATED - JSON schema type names (UpperCamelCase)
Capabilities CapabilitySet `yaml:"capabilities,omitempty" json:"capabilities,omitempty"` // MANUAL - config-driven capability definitions (only for type=custom)
}
@@ -92,6 +93,7 @@ type Parser struct {
Detector Detector `yaml:"detector" json:"detector"` // AUTO-GENERATED - how artifacts are detected
MetadataTypes []string `yaml:"metadata_types,omitempty" json:"metadata_types,omitempty"` // AUTO-GENERATED - pkg metadata types emitted by this parser
PackageTypes []string `yaml:"package_types,omitempty" json:"package_types,omitempty"` // AUTO-GENERATED - package types emitted by this parser
PURLTypes []string `yaml:"purl_types,omitempty" json:"purl_types,omitempty"` // AUTO-GENERATED - PURL types derived from package_types
JSONSchemaTypes []string `yaml:"json_schema_types,omitempty" json:"json_schema_types,omitempty"` // AUTO-GENERATED - JSON schema type names (UpperCamelCase)
Capabilities CapabilitySet `yaml:"capabilities,omitempty" json:"capabilities,omitempty"` // MANUAL - config-driven capability definitions
}
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
)
func TestConfigSchemaExtensions(t *testing.T) {
+9 -2
View File
@@ -3,12 +3,19 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.1.3"
JSONSchemaVersion = "16.1.11"
// Changelog
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
// 16.1.1 - correct elf package osCpe field according to the document of systemd (also add appCpe field)
// 16.1.2 - placeholder for 16.1.2 changelog
// 16.1.3 - add GGUFFileParts to GGUFFileHeader metadata
// 16.1.4 - add BunLockEntry metadata type for bun.lock support
// 16.1.5 - add DenoLockEntry and DenoRemoteLockEntry metadata types for deno.lock support
// 16.1.6 - add Dependencies to ElixirMixLockEntry metadata
// 16.1.7 - add AppleAppBundleEntry metadata type for the apple app bundle cataloger
// 16.1.8 - add VcpkgManifest metadata type for vcpkg manifest support
// 16.1.9 - add Symbols (grouped by owning package import path) to GolangBinaryBuildinfoEntry metadata
// 16.1.10 - add SafeTensorsModelInfo metadata type for the safetensors AI model cataloger
// 16.1.11 - add CpanDistribution and CpanUnpackedRelease metadata types for the perl catalogers
)
+66
View File
@@ -0,0 +1,66 @@
package file
import "io"
// ReaderSize reports the number of bytes behind r. The bool is false when that cannot be determined, and
// callers must branch on it rather than treating the zero as a size.
//
// Binary parsers read sizes out of the files they are parsing and allocate against them. Those sizes are
// 32- and 64-bit fields under the control of whoever produced the file, so a truncated or hostile input
// can declare gigabytes that are not there. The read would fail afterwards either way; what matters is
// refusing before the allocation, and that needs a real byte count to weigh the claim against.
//
// The bool is the whole point of the signature. An earlier version returned 0 for every failure mode, and
// a caller that forgot to special-case it got a bound of "nothing is allowed" or, more often, silently
// fell back to an unbounded path. Making the caller name the failure keeps that from happening by
// omission.
//
// Both *bytes.Reader and *io.SectionReader answer directly. A file handle only has Seek, so fall back to
// a save-and-restore seek, which leaves the caller's cursor where it found it. That fallback is not
// atomic: it moves the cursor and puts it back, so a reader being read concurrently can observe the
// intermediate position.
//
// Neither answer is taken on faith, because a reader can report a length it cannot deliver: an
// *io.SectionReader answers with the length it was constructed with, and debug/elf and friends construct
// theirs with a nominal one, so io.NewSectionReader(r, 0, 1<<63-1) would otherwise report a bound that
// permits everything as if it had been measured. The last byte is read back to confirm the count, which is
// one ReadAt whatever the size, and the read is what the caller was going to weigh anyway.
//
// A wrapper that embeds io.ReaderAt as an interface promotes only ReadAt, so it answers no size here
// however sizable the reader underneath it is. A type meant to be bounded needs to forward Size() itself.
func ReaderSize(r io.ReaderAt) (int64, bool) {
size, ok := reportedSize(r)
if !ok {
return 0, false
}
// ReadAt may return io.EOF alongside a full read, so the count is what says the byte is there
var last [1]byte
if n, _ := r.ReadAt(last[:], size-1); n != len(last) {
return 0, false
}
return size, true
}
// reportedSize is what the reader says about itself, before ReaderSize checks whether it can back it up.
func reportedSize(r io.ReaderAt) (int64, bool) {
if sr, ok := r.(interface{ Size() int64 }); ok {
size := sr.Size()
return size, size > 0
}
s, ok := r.(io.Seeker)
if !ok {
return 0, false
}
cur, err := s.Seek(0, io.SeekCurrent)
if err != nil {
return 0, false
}
size, err := s.Seek(0, io.SeekEnd)
if err != nil {
return 0, false
}
if _, err := s.Seek(cur, io.SeekStart); err != nil {
return 0, false
}
return size, size > 0
}
+140
View File
@@ -0,0 +1,140 @@
package file
import (
"bytes"
"io"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReaderSize(t *testing.T) {
tests := []struct {
name string
r io.ReaderAt
want int64
wantOK bool
}{
{
name: "bytes.Reader answers directly",
r: bytes.NewReader(make([]byte, 1234)),
want: 1234,
wantOK: true,
},
{
// an empty reader is not a size to bound against, so it reports not-ok along with everything
// else that cannot be measured
name: "empty bytes.Reader",
r: bytes.NewReader(nil),
want: 0,
},
{
name: "SectionReader over a real length",
r: io.NewSectionReader(bytes.NewReader(make([]byte, 500)), 100, 300),
want: 300,
wantOK: true,
},
{
name: "seek-only reader falls back to seeking",
r: seekOnly{bytes.NewReader(make([]byte, 4096))},
want: 4096,
wantOK: true,
},
{
name: "reader with neither Size nor Seek cannot be measured",
r: readAtOnly{bytes.NewReader(make([]byte, 4096))},
want: 0,
},
{
// the shape debug/elf, debug/pe and debug/macho build internally. Taking this at its word hands
// back a bound that permits everything, reported as if it had been measured.
name: "SectionReader over a nominal length reports what it can deliver",
r: io.NewSectionReader(bytes.NewReader(make([]byte, 500)), 0, 1<<63-1),
want: 0,
},
{
// a Size() a type simply got wrong is the same failure: the bytes are what decides
name: "a size the reader cannot back up is refused",
r: overstatedSize{ReaderAt: bytes.NewReader(make([]byte, 10)), size: 1 << 30},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
size, ok := ReaderSize(tt.r)
assert.Equal(t, tt.want, size)
assert.Equal(t, tt.wantOK, ok)
})
}
}
func TestReaderSize_FileHandle(t *testing.T) {
path := filepath.Join(t.TempDir(), "f.bin")
require.NoError(t, os.WriteFile(path, make([]byte, 7777), 0o600))
f, err := os.Open(path)
require.NoError(t, err)
defer f.Close()
// the cursor has to come back where it was found: the caller may be mid-read, and an *os.File is the
// one input here that carries a shared position
_, err = f.Seek(101, io.SeekStart)
require.NoError(t, err)
size, ok := ReaderSize(f)
assert.True(t, ok)
assert.Equal(t, int64(7777), size)
at, err := f.Seek(0, io.SeekCurrent)
require.NoError(t, err)
assert.Equal(t, int64(101), at, "measuring the file must not move the caller's cursor")
}
// TestReaderSize_WrapperMustForwardSize pins the shape that made every bound expressed against this
// helper go silently inert. A type that embeds io.ReaderAt as an interface promotes only ReadAt, so it
// answers 0 here however sizable the reader underneath is, and a caller that treats 0 as "fall back to
// unbounded" then stops bounding anything. Forwarding Size() is what fixes it, and this is the assertion
// that a new wrapper has to satisfy.
func TestReaderSize_WrapperMustForwardSize(t *testing.T) {
inner := bytes.NewReader(make([]byte, 2048))
size, ok := ReaderSize(embedsReaderAt{inner})
assert.False(t, ok, "a wrapper that only embeds the interface cannot be measured")
assert.Equal(t, int64(0), size)
size, ok = ReaderSize(forwardsSize{inner})
assert.True(t, ok, "a wrapper meant to be bounded has to forward Size itself")
assert.Equal(t, int64(2048), size)
}
type seekOnly struct{ inner *bytes.Reader }
func (s seekOnly) ReadAt(p []byte, off int64) (int, error) { return s.inner.ReadAt(p, off) }
func (s seekOnly) Seek(off int64, whence int) (int64, error) {
return s.inner.Seek(off, whence)
}
type readAtOnly struct{ inner *bytes.Reader }
func (r readAtOnly) ReadAt(p []byte, off int64) (int, error) { return r.inner.ReadAt(p, off) }
type embedsReaderAt struct{ io.ReaderAt }
type forwardsSize struct{ io.ReaderAt }
func (f forwardsSize) Size() int64 {
size, _ := ReaderSize(f.ReaderAt)
return size
}
// overstatedSize answers with a size larger than the bytes behind it, the way a wrapper that returns a
// declared length rather than a measured one would.
type overstatedSize struct {
io.ReaderAt
size int64
}
func (o overstatedSize) Size() int64 { return o.size }
+34 -4
View File
@@ -5,6 +5,7 @@ import (
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/diskfs/go-diskfs/filesystem"
)
@@ -14,8 +15,7 @@ type WalkDiskDirFunc func(fsys filesystem.FileSystem, path string, d os.FileInfo
// WalkDiskDir walks the file tree within the go-diskfs filesystem at root, calling fn for each file or directory in the tree, including root.
// This is meant to mimic the behavior of fs.WalkDir in the standard library.
func WalkDiskDir(fsys filesystem.FileSystem, root string, fn WalkDiskDirFunc) error {
infos, err := fsys.ReadDir(root)
infos, err := readDiskDir(fsys, root)
if err != nil {
return err
}
@@ -51,7 +51,7 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir
isDir := d != nil && d.IsDir()
if d == nil {
_, err := fsys.ReadDir(name)
_, err := readDiskDir(fsys, name)
if err != nil {
return nil
}
@@ -62,7 +62,7 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir
return nil
}
dirs, err := fsys.ReadDir(name)
dirs, err := readDiskDir(fsys, name)
if err != nil {
err = walkDirFn(fsys, name, d, err)
if err != nil {
@@ -87,3 +87,33 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir
}
return nil
}
// readDiskDir reads the directory entries at the given path from a go-diskfs filesystem.
// go-diskfs returns fs.DirEntry values; these are resolved to os.FileInfo so callers have
// access to the full file metadata (mode, size, modification time).
func readDiskDir(fsys filesystem.FileSystem, p string) ([]os.FileInfo, error) {
entries, err := fsys.ReadDir(ToFSPath(p))
if err != nil {
return nil, err
}
infos := make([]os.FileInfo, 0, len(entries))
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
return nil, err
}
infos = append(infos, info)
}
return infos, nil
}
// ToFSPath converts an absolute ("/"-rooted) path into an io/fs-valid path as required by
// go-diskfs, where the root is "." and other paths carry no leading slash (see io/fs.ValidPath).
func ToFSPath(p string) string {
p = strings.TrimPrefix(p, "/")
if p == "" {
return "."
}
return p
}
+2 -2
View File
@@ -43,10 +43,10 @@ func createTestFS(t *testing.T) filesystem.FileSystem {
for _, tf := range testFiles {
if tf.isDir {
err := fsys.Mkdir(tf.path)
err := fsys.Mkdir(ToFSPath(tf.path))
require.NoError(t, err)
} else {
f, err := fsys.OpenFile(tf.path, os.O_CREATE|os.O_RDWR)
f, err := fsys.OpenFile(ToFSPath(tf.path), os.O_CREATE|os.O_RDWR)
require.NoError(t, err)
_, err = f.Write([]byte(tf.content))
require.NoError(t, err)
+1 -1
View File
@@ -6,9 +6,9 @@ import (
"testing"
"github.com/invopop/jsonschema"
orderedmap "github.com/pb33f/ordered-map/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
// TestCopyAliasFieldComments verifies that field comments from source types are correctly copied to alias types.
+19
View File
@@ -0,0 +1,19 @@
package internal
import (
"bufio"
"io"
)
// maxScannedLineSize is the longest line NewLineScanner will return before Scan stops with bufio.ErrTooLong.
// The buffer only grows to this size when a line needs it.
const maxScannedLineSize = 1024 * 1024
// NewLineScanner returns a bufio.Scanner that accepts lines up to 1MB rather than the 64KB default, since
// package metadata files carry long single-line fields (descriptions, license text, dependency lists). Callers
// must still check Err() after scanning.
func NewLineScanner(r io.Reader) *bufio.Scanner {
s := bufio.NewScanner(r)
s.Buffer(nil, maxScannedLineSize)
return s
}
+24
View File
@@ -0,0 +1,24 @@
package internal
import (
"bufio"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestNewLineScanner(t *testing.T) {
long := strings.Repeat("a", 100*1024)
s := NewLineScanner(strings.NewReader(long + "\nb\n"))
require.True(t, s.Scan())
require.Equal(t, long, s.Text())
require.True(t, s.Scan())
require.Equal(t, "b", s.Text())
require.False(t, s.Scan())
require.NoError(t, s.Err())
s = NewLineScanner(strings.NewReader(strings.Repeat("a", maxScannedLineSize+1)))
require.False(t, s.Scan())
require.ErrorIs(t, s.Err(), bufio.ErrTooLong)
}
+157 -11
View File
@@ -2,14 +2,18 @@ package os
import (
"context"
"encoding/json"
"errors"
"fmt"
"regexp"
"slices"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
)
@@ -19,8 +23,11 @@ func DetectFeatures(_ context.Context, resolver file.Resolver, builder sbomsync.
return
}
err := findRhelFeatures(resolver, s.Artifacts.LinuxDistribution)
if err != nil {
if err := findRhelFeatures(resolver, s.Artifacts.LinuxDistribution); err != nil {
log.WithFields("error", err, "release", s.Artifacts.LinuxDistribution).Trace("error searching for extended support")
}
if err := findUbuntuFeatures(resolver, s.Artifacts.LinuxDistribution, s.Artifacts.Packages); err != nil {
log.WithFields("error", err, "release", s.Artifacts.LinuxDistribution).Trace("error searching for extended support")
}
})
@@ -28,6 +35,9 @@ func DetectFeatures(_ context.Context, resolver file.Resolver, builder sbomsync.
return nil
}
// rhelEUSPatterns match a RHEL content manifest referencing an extended-update-support (EUS) repo.
var rhelEUSPatterns = []*regexp.Regexp{regexp.MustCompile(`baseos-eus`), regexp.MustCompile(`baseos-eus|appstream-eus`)}
func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
if release == nil || release.ID != "rhel" {
return nil
@@ -37,9 +47,9 @@ func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
return fmt.Errorf("unable to find content manifests: %w", err)
}
for _, contentManifestFile := range contentManifestFiles {
found, err := hasRhelExtendedSupportInContentManifest(resolver, contentManifestFile)
found, err := fileMatchesAny(resolver, contentManifestFile, rhelEUSPatterns...)
if err != nil {
return fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.String(), err)
return fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.RealPath, err)
}
if found {
release.ExtendedSupport = true
@@ -49,13 +59,149 @@ func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
return nil
}
func hasRhelExtendedSupportInContentManifest(resolver file.Resolver, contentManifestFile file.Location) (bool, error) {
contents, err := resolver.FileContentsByLocation(contentManifestFile)
if err != nil {
return false, fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.String(), err)
}
defer internal.CloseAndLogError(contents, "content-manifest")
// Ubuntu Pro (formerly Ubuntu Advantage) is Canonical's subscription that unlocks Expanded Security
// Maintenance (ESM): security fixes past a package's standard support window. we detect the two base ESM
// streams, esm-infra (the `main` repo) and esm-apps (`universe`), and collapse them into one ExtendedSupport
// signal; the infra-vs-apps split is disambiguated by the downstream consumer, not by this boolean.
//
// the other Pro streams on the same host (esm.ubuntu.com/fips, /fips-updates, /realtime) are deliberately
// excluded: they are separate compliance products with their own downstream channels, and a host can run
// them with base ESM disabled, so folding them in would be unsound.
//
// detection is deliberately forgiving: apt/status evidence disappears when ESM is disabled, but an installed
// +esmN package is durable proof ESM content is on disk. so ExtendedSupport means "ESM was or is in effect
// for this host or its content", not "currently entitled" (mere eligibility, without evidence, is not enough).
patterns := []*regexp.Regexp{regexp.MustCompile(`baseos-eus`), regexp.MustCompile(`baseos-eus|appstream-eus`)}
// esmAptSourcePattern matches an uncommented apt source (classic or DEB822) or auth entry pointing at the
// plain ESM streams esm.ubuntu.com/infra or /apps. it deliberately scopes to /(infra|apps) so the other
// Pro streams served from the same host (/fips, /fips-updates, /realtime) are never folded into the base
// esm channel. the [^#\n]* before the host ensures a leading `#` (comment) can never satisfy the match.
var esmAptSourcePattern = regexp.MustCompile(`(?m)^[^#\n]*esm\.ubuntu\.com/(infra|apps)`)
// esmVersionPattern matches a dpkg version carrying a numbered ESM pocket suffix, e.g. `...+esm1` or
// `...~esm2`. the trailing \d scopes this to the ESM pocket and excludes other Pro pockets (e.g. +fips).
var esmVersionPattern = regexp.MustCompile(`[~+]esm\d`)
func findUbuntuFeatures(resolver file.Resolver, release *linux.Release, packages *pkg.Collection) error {
if release == nil || release.ID != "ubuntu" {
return nil
}
// an uncommented esm.ubuntu.com apt source or an active pro esm service both prove Pro/ESM was enabled.
// these are the durable, file-based signals present on attached hosts. a read error in one signal must
// not suppress the others, so errors are collected and detection continues to the next signal.
var errs error
for _, check := range []func() (bool, error){
func() (bool, error) { return hasUbuntuESMAptEvidence(resolver) },
func() (bool, error) { return hasActiveUbuntuESMService(resolver) },
} {
found, err := check()
if err != nil {
errs = errors.Join(errs, err)
continue
}
if found {
release.ExtendedSupport = true
return errs
}
}
// fallback for images (typically not pro-attached): an installed package whose version carries an
// +esm/~esm suffix was pulled from an ESM pocket, so ESM was in effect when it was installed.
if hasInstalledESMPackage(packages) {
release.ExtendedSupport = true
}
return errs
}
// hasUbuntuESMAptEvidence reports whether any apt source (classic .list or DEB822 .sources) or auth entry
// references esm.ubuntu.com via an uncommented line.
func hasUbuntuESMAptEvidence(resolver file.Resolver) (bool, error) {
// apt source files live in a directory (classic .list or DEB822 .sources), the credentials file is a fixed path.
sourceLocations, err := resolver.FilesByGlob("/etc/apt/sources.list.d/*")
if err != nil {
return false, fmt.Errorf("unable to find apt esm sources: %w", err)
}
authLocations, err := resolver.FilesByPath("/etc/apt/auth.conf.d/90ubuntu-advantage")
if err != nil {
return false, fmt.Errorf("unable to find apt esm auth: %w", err)
}
// ponytail: a DEB822 source with `Enabled: no` would still match; the pro client removes/comments the
// file on disable rather than toggling that key, so this is an accepted false-positive ceiling. the
// auth.conf.d entry is deleted by the pro client on disable, so it is a durable positive-only signal.
for _, location := range append(sourceLocations, authLocations...) {
match, err := fileMatchesAny(resolver, location, esmAptSourcePattern)
if err != nil {
return false, err
}
if match {
return true, nil
}
}
return false, nil
}
// hasActiveUbuntuESMService reports whether the ubuntu-advantage/pro status cache shows an enabled esm service.
func hasActiveUbuntuESMService(resolver file.Resolver) (bool, error) {
locations, err := resolver.FilesByPath("/var/lib/ubuntu-advantage/status.json")
if err != nil {
return false, fmt.Errorf("unable to find ubuntu-advantage status: %w", err)
}
for _, location := range locations {
enabled, err := hasEnabledESMServiceInStatus(resolver, location)
if err != nil {
return false, err
}
if enabled {
return true, nil
}
}
return false, nil
}
func hasEnabledESMServiceInStatus(resolver file.Resolver, location file.Location) (bool, error) {
contents, err := resolver.FileContentsByLocation(location)
if err != nil {
return false, fmt.Errorf("unable to read ubuntu-advantage status from %s: %w", location.RealPath, err)
}
defer internal.CloseAndLogError(contents, location.RealPath)
var status struct {
Services []struct {
Name string `json:"name"`
Status string `json:"status"`
} `json:"services"`
}
if err := json.NewDecoder(contents).Decode(&status); err != nil {
return false, fmt.Errorf("unable to parse ubuntu-advantage status from %s: %w", location.RealPath, err)
}
// pro status values are enabled/disabled/n-a/warning/—; only "enabled" proves the stream is active.
// warning/expired states are intentionally excluded here and left to the apt-source and installed-package signals.
for _, svc := range status.Services {
if (svc.Name == "esm-infra" || svc.Name == "esm-apps") && svc.Status == "enabled" {
return true, nil
}
}
return false, nil
}
// hasInstalledESMPackage reports whether any installed dpkg package version carries an ESM pocket suffix.
func hasInstalledESMPackage(packages *pkg.Collection) bool {
if packages == nil {
return false
}
return slices.ContainsFunc(packages.Sorted(pkg.DebPkg), func(p pkg.Package) bool {
return esmVersionPattern.MatchString(p.Version)
})
}
func fileMatchesAny(resolver file.Resolver, location file.Location, patterns ...*regexp.Regexp) (bool, error) {
contents, err := resolver.FileContentsByLocation(location)
if err != nil {
return false, fmt.Errorf("unable to read %s: %w", location.RealPath, err)
}
defer internal.CloseAndLogError(contents, location.RealPath)
return internal.MatchAnyFromReader(contents, patterns...)
}
+242
View File
@@ -2,7 +2,13 @@ package os_test
import (
"context"
"io"
stdos "os"
"path"
"path/filepath"
"slices"
"testing"
"time"
"github.com/stretchr/testify/require"
@@ -11,7 +17,9 @@ import (
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/internal/task"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
@@ -99,3 +107,237 @@ func Test_EnvironmentTask(t *testing.T) {
})
}
}
func Test_findUbuntuFeatures(t *testing.T) {
tests := []struct {
name string
dir string
releaseID string
packages []pkg.Package
expected bool
}{
{
name: "esm apt source present",
dir: "testdata/ubuntu_esm_apt",
releaseID: "ubuntu",
expected: true,
},
{
name: "esm apt source in DEB822 .sources format",
dir: "testdata/ubuntu_esm_deb822",
releaseID: "ubuntu",
expected: true,
},
{
name: "esm evidence in apt auth.conf.d",
dir: "testdata/ubuntu_esm_authconf",
releaseID: "ubuntu",
expected: true,
},
{
name: "active esm service in ubuntu-advantage status",
dir: "testdata/ubuntu_esm_status",
releaseID: "ubuntu",
expected: true,
},
{
name: "only esm-infra enabled (esm-apps disabled)",
dir: "testdata/ubuntu_esm_infra_only",
releaseID: "ubuntu",
expected: true,
},
{
name: "malformed status.json does not panic or match",
dir: "testdata/ubuntu_esm_malformed",
releaseID: "ubuntu",
expected: false,
},
{
name: "installed +esm package version",
dir: "testdata/ubuntu_plain",
releaseID: "ubuntu",
packages: []pkg.Package{
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
},
expected: true,
},
{
name: "installed ~esm package version",
dir: "testdata/ubuntu_plain",
releaseID: "ubuntu",
packages: []pkg.Package{
{Name: "libcap2", Version: "1:2.32-1ubuntu0.1~esm1", Type: pkg.DebPkg},
},
expected: true,
},
{
name: "plain ubuntu with no esm evidence",
dir: "testdata/ubuntu_plain",
releaseID: "ubuntu",
packages: []pkg.Package{
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19", Type: pkg.DebPkg},
},
expected: false,
},
{
name: "commented esm source and disabled esm service",
dir: "testdata/ubuntu_esm_disabled",
releaseID: "ubuntu",
expected: false,
},
{
name: "esm disabled but +esm package remains installed",
dir: "testdata/ubuntu_esm_disabled",
releaseID: "ubuntu",
packages: []pkg.Package{
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
},
expected: true, // durable proof: ESM content is on disk even though the channel is now off
},
{
name: "fips-only pro host is not plain esm",
dir: "testdata/ubuntu_fips_only",
releaseID: "ubuntu",
expected: false, // esm.ubuntu.com/fips must not be folded into the base esm channel
},
{
name: "non-ubuntu is unaffected by esm evidence",
dir: "testdata/ubuntu_esm_apt",
releaseID: "debian",
expected: false,
},
{
name: "non-ubuntu is unaffected by esm package version",
dir: "testdata/ubuntu_plain",
releaseID: "debian",
packages: []pkg.Package{
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
},
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resolver := fixtureResolverForDir(t, test.dir)
s := sbom.SBOM{}
s.Artifacts.LinuxDistribution = &linux.Release{ID: test.releaseID}
s.Artifacts.Packages = pkg.NewCollection(test.packages...)
err := os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s))
require.NoError(t, err)
require.Equal(t, test.expected, s.Artifacts.LinuxDistribution.ExtendedSupport)
})
}
}
// returning early from Collection.Enumerate leaks its goroutine and read lock, so any later write blocks forever.
func Test_findUbuntuFeatures_releasesPackageCollectionLock(t *testing.T) {
resolver := fixtureResolverForDir(t, "testdata/ubuntu_plain")
s := sbom.SBOM{}
s.Artifacts.LinuxDistribution = &linux.Release{ID: "ubuntu"}
s.Artifacts.Packages = pkg.NewCollection(
pkg.Package{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
pkg.Package{Name: "libcap2", Version: "1:2.32-1ubuntu0.1~esm1", Type: pkg.DebPkg},
)
require.NoError(t, os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s)))
require.True(t, s.Artifacts.LinuxDistribution.ExtendedSupport)
added := make(chan struct{})
go func() {
s.Artifacts.Packages.Add(pkg.Package{Name: "zlib1g", Version: "1:1.2.11.dfsg-2ubuntu1", Type: pkg.DebPkg})
close(added)
}()
select {
case <-added:
case <-time.After(5 * time.Second):
t.Fatal("package collection is still read-locked after feature detection")
}
}
// a read error in the apt-source signal must not suppress the ubuntu-advantage status signal; the three
// signals are meant to be independent fallbacks.
func Test_findUbuntuFeatures_signalErrorDoesNotSuppressOthers(t *testing.T) {
resolver := errOnGlobResolver{
fixtureResolver: fixtureResolverForDir(t, "testdata/ubuntu_esm_status").(fixtureResolver),
errOnGlob: "/etc/apt/sources.list.d/*",
}
s := sbom.SBOM{}
s.Artifacts.LinuxDistribution = &linux.Release{ID: "ubuntu"}
s.Artifacts.Packages = pkg.NewCollection()
require.NoError(t, os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s)))
// the apt glob errors, but the status.json signal (esm-infra enabled) should still be consulted
require.True(t, s.Artifacts.LinuxDistribution.ExtendedSupport)
}
// errOnGlobResolver injects a failure for a specific glob to simulate an unreadable apt directory.
type errOnGlobResolver struct {
fixtureResolver
errOnGlob string
}
func (r errOnGlobResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
if slices.Contains(patterns, r.errOnGlob) {
return nil, stdos.ErrPermission
}
return r.fixtureResolver.FilesByGlob(patterns...)
}
// fixtureResolver maps in-image logical paths to on-disk fixture files so DetectFeatures can be driven
// without building a container image.
type fixtureResolver struct {
file.Resolver // unused methods; nil is fine since only the three below are called
files map[string]string
}
func (r fixtureResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
var out []file.Location
for logical := range r.files {
for _, p := range patterns {
if ok, _ := path.Match(p, logical); ok {
out = append(out, file.NewLocation(logical))
break
}
}
}
return out, nil
}
func (r fixtureResolver) FilesByPath(paths ...string) ([]file.Location, error) {
var out []file.Location
for _, p := range paths {
if _, ok := r.files[p]; ok {
out = append(out, file.NewLocation(p))
}
}
return out, nil
}
func (r fixtureResolver) FileContentsByLocation(l file.Location) (io.ReadCloser, error) {
return stdos.Open(r.files[l.RealPath])
}
func fixtureResolverForDir(t *testing.T, dir string) file.Resolver {
t.Helper()
files := map[string]string{}
require.NoError(t, filepath.WalkDir(dir, func(p string, d stdos.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
rel, err := filepath.Rel(dir, p)
if err != nil {
return err
}
files["/"+filepath.ToSlash(rel)] = p
return nil
}))
return fixtureResolver{files: files}
}
@@ -0,0 +1,6 @@
# Written by ubuntu-advantage-tools
deb https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
# deb-src https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
deb https://esm.ubuntu.com/infra/ubuntu focal-infra-updates main
# deb-src https://esm.ubuntu.com/infra/ubuntu focal-infra-updates main
@@ -0,0 +1,2 @@
machine esm.ubuntu.com/infra/ubuntu/ login bearer password DUMMYTOKENREDACTED # ubuntu-pro-client
machine esm.ubuntu.com/apps/ubuntu/ login bearer password DUMMYTOKENREDACTED # ubuntu-pro-client
@@ -0,0 +1,5 @@
Types: deb
URIs: https://esm.ubuntu.com/infra/ubuntu
Suites: noble-infra-security noble-infra-updates
Components: main
Signed-By: /usr/share/keyrings/ubuntu-pro-esm-infra-keyring.gpg
@@ -0,0 +1,3 @@
# Written by ubuntu-advantage-tools
# deb https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
# deb-src https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
@@ -0,0 +1,18 @@
{
"_schema_version": "0.1",
"attached": true,
"services": [
{
"name": "esm-apps",
"available": "yes",
"entitled": "yes",
"status": "disabled"
},
{
"name": "esm-infra",
"available": "yes",
"entitled": "yes",
"status": "disabled"
}
]
}
@@ -0,0 +1,21 @@
{
"_schema_version": "0.1",
"attached": true,
"execution_status": "success",
"services": [
{
"name": "esm-apps",
"description": "Expanded Security Maintenance for Applications",
"available": "yes",
"entitled": "yes",
"status": "disabled"
},
{
"name": "esm-infra",
"description": "Expanded Security Maintenance for Infrastructure",
"available": "yes",
"entitled": "yes",
"status": "enabled"
}
]
}
@@ -0,0 +1 @@
{ this is not valid json
@@ -0,0 +1,28 @@
{
"_doc": "Content provided in json response is currently considered Experimental and may change",
"_schema_version": "0.1",
"attached": true,
"services": [
{
"name": "esm-apps",
"description": "Expanded Security Maintenance for Applications",
"available": "yes",
"entitled": "yes",
"status": "enabled"
},
{
"name": "esm-infra",
"description": "Expanded Security Maintenance for Infrastructure",
"available": "yes",
"entitled": "yes",
"status": "enabled"
},
{
"name": "fips",
"description": "NIST-certified FIPS crypto packages",
"available": "no",
"entitled": "no",
"status": "n/a"
}
]
}
@@ -0,0 +1,3 @@
# Written by ubuntu-pro-client
deb https://esm.ubuntu.com/fips/ubuntu focal main
# deb-src https://esm.ubuntu.com/fips/ubuntu focal main
@@ -0,0 +1,28 @@
{
"_schema_version": "0.1",
"attached": true,
"execution_status": "success",
"services": [
{
"name": "esm-apps",
"description": "Expanded Security Maintenance for Applications",
"available": "yes",
"entitled": "yes",
"status": "disabled"
},
{
"name": "esm-infra",
"description": "Expanded Security Maintenance for Infrastructure",
"available": "yes",
"entitled": "yes",
"status": "disabled"
},
{
"name": "fips",
"description": "NIST-certified FIPS crypto packages",
"available": "yes",
"entitled": "yes",
"status": "enabled"
}
]
}
@@ -0,0 +1,5 @@
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
@@ -27,6 +27,7 @@ var knownNonMetadataTypeNames = strset.New(
var knownMetadataTypeNames = strset.New(
"DotnetPortableExecutableEntry",
"GGUFFileHeader",
"SafeTensorsModelInfo",
)
func DiscoverTypeNames() ([]string, error) {
+8
View File
@@ -9,16 +9,22 @@ func AllTypes() []any {
return []any{
pkg.AlpmDBEntry{},
pkg.ApkDBEntry{},
pkg.AppleAppBundleEntry{},
pkg.BinarySignature{},
pkg.BitnamiSBOMEntry{},
pkg.BunLockEntry{},
pkg.CocoaPodfileLockEntry{},
pkg.ConanV1LockEntry{},
pkg.ConanV2LockEntry{},
pkg.ConanfileEntry{},
pkg.ConaninfoEntry{},
pkg.CondaMetaPackage{},
pkg.CpanDistribution{},
pkg.CpanUnpackedRelease{},
pkg.DartPubspec{},
pkg.DartPubspecLockEntry{},
pkg.DenoLockEntry{},
pkg.DenoRemoteLockEntry{},
pkg.DotnetDepsEntry{},
pkg.DotnetPackagesLockEntry{},
pkg.DotnetPortableExecutableEntry{},
@@ -64,10 +70,12 @@ func AllTypes() []any {
pkg.RubyGemspec{},
pkg.RustBinaryAuditEntry{},
pkg.RustCargoLockEntry{},
pkg.SafeTensorsModelInfo{},
pkg.SnapEntry{},
pkg.SwiftPackageManagerResolvedEntry{},
pkg.SwiplPackEntry{},
pkg.TerraformLockProviderEntry{},
pkg.VcpkgManifest{},
pkg.WordpressPluginEntry{},
pkg.YarnLockEntry{},
}
+8
View File
@@ -71,8 +71,12 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.ConanV2LockEntry{}, "c-conan-lock-v2-entry"),
jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"),
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
jsonNames(pkg.CpanDistribution{}, "cpan-distribution"),
jsonNames(pkg.CpanUnpackedRelease{}, "cpan-unpacked-release"),
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
jsonNames(pkg.DartPubspec{}, "dart-pubspec"),
jsonNames(pkg.DenoLockEntry{}, "deno-lock-entry"),
jsonNames(pkg.DenoRemoteLockEntry{}, "deno-remote-lock-entry"),
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
jsonNames(pkg.DpkgArchiveEntry{}, "dpkg-archive-entry"),
@@ -96,6 +100,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
jsonNames(pkg.PnpmLockEntry{}, "javascript-pnpm-lock-entry"),
jsonNames(pkg.BunLockEntry{}, "javascript-bun-lock-entry"),
jsonNames(pkg.PEBinary{}, "pe-binary"),
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
@@ -121,11 +126,14 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.SnapEntry{}, "snap-entry"),
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
jsonNames(pkg.HomebrewFormula{}, "homebrew-formula"),
jsonNames(pkg.AppleAppBundleEntry{}, "apple-app-bundle-entry"),
jsonNames(pkg.LuaRocksPackage{}, "luarocks-package"),
jsonNames(pkg.TerraformLockProviderEntry{}, "terraform-lock-provider-entry"),
jsonNames(pkg.DotnetPackagesLockEntry{}, "dotnet-packages-lock-entry"),
jsonNames(pkg.CondaMetaPackage{}, "conda-metadata-entry", "CondaPackageMetadata"),
jsonNames(pkg.GGUFFileHeader{}, "gguf-file-header"),
jsonNames(pkg.SafeTensorsModelInfo{}, "safetensors-model-info"),
jsonNames(pkg.VcpkgManifest{}, "vcpkg-manifest"),
)
func expandLegacyNameVariants(names ...string) []string {
@@ -0,0 +1,74 @@
package packagemetadata
import (
"reflect"
"strings"
"testing"
)
// untypedFieldAllowlist holds package metadata fields that are allowed to carry untyped (interface) values.
// Values in these fields come straight from whatever a parser decoded, so they are not shaped by syft
// and can hold anything an encoder or hasher chokes on. Every entry is part of existing SBOM output and
// cannot be retyped without breaking it; new entries need a reason and a review.
var untypedFieldAllowlist = map[string]string{
"GGUFFileHeader.RemainingKeyValues": "raw GGUF header key-values; retyping changes the header output and metadataHash",
}
// TestNoUntypedMetadataFields fails when a package metadata type gains a field typed any, []any,
// map[string]any (or any other interface) that is not on the allowlist.
func TestNoUntypedMetadataFields(t *testing.T) {
var hits []string
for _, ty := range AllTypes() {
rt := reflect.TypeOf(ty)
collectUntyped(rt, rt.Name(), map[reflect.Type]bool{}, &hits)
}
seen := map[string]bool{}
for _, h := range hits {
seen[h] = true
if _, ok := untypedFieldAllowlist[h]; !ok {
t.Errorf("untyped field %s: use a concrete type, or add it to untypedFieldAllowlist with a reason", h)
}
}
for k := range untypedFieldAllowlist {
if !seen[k] {
t.Errorf("stale untypedFieldAllowlist entry %s: field no longer exists or is now typed", k)
}
}
}
// collectUntyped records "Struct.Field" for every field whose type reaches an interface, descending
// through pointers, slices, arrays, maps and nested syft-owned structs.
func collectUntyped(rt reflect.Type, path string, visited map[reflect.Type]bool, hits *[]string) {
switch rt.Kind() {
case reflect.Interface:
*hits = append(*hits, path)
case reflect.Pointer, reflect.Slice, reflect.Array:
collectUntyped(rt.Elem(), path, visited, hits)
case reflect.Map:
collectUntyped(rt.Key(), path, visited, hits)
collectUntyped(rt.Elem(), path, visited, hits)
case reflect.Struct:
if visited[rt] {
return
}
if rt.Name() != "" && !strings.HasPrefix(rt.PkgPath(), "github.com/anchore/syft/") {
// a third-party type in the model carries whatever shape the library gives it (the same leak as an
// interface field), so it counts as untyped. The standard library is left alone.
if strings.Contains(strings.Split(rt.PkgPath(), "/")[0], ".") {
*hits = append(*hits, path)
}
return
}
visited[rt] = true
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
// unexported embedded structs still promote their exported fields to the encoders and the hasher. A
// field is only out of reach when it is neither encoded nor hashed by SetID.
if (!f.IsExported() && !f.Anonymous) || (f.Tag.Get("json") == "-" && f.Tag.Get("hash") == "ignore") {
continue
}
collectUntyped(f.Type, rt.Name()+"."+f.Name, visited, hits)
}
}
}
@@ -279,6 +279,46 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
return []pkg.Package{parent, child}, nil
},
},
{
// a CPAN distribution owns whatever its .packlist recorded, which is how a distribution that
// installed a binary or a shared object is related to the package cataloged from it
name: "cpan-distribution-owns-packlist-paths",
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
parent := pkg.Package{
Locations: file.NewLocationSet(
file.NewLocation("/usr/local/lib/perl5/site_perl/5.40.4/x86_64-linux/auto/Net/SSLeay/.packlist"),
),
Type: pkg.CpanPkg,
Metadata: pkg.CpanDistribution{
MainModule: "Net::SSLeay",
Files: []string{
"/usr/local/lib/perl5/site_perl/5.40.4/x86_64-linux/Net/SSLeay.pm",
"/usr/local/lib/perl5/site_perl/5.40.4/x86_64-linux/auto/Net/SSLeay/SSLeay.so",
},
},
}
parent.SetID()
child := pkg.Package{
Locations: file.NewLocationSet(
file.NewLocation("/usr/local/lib/perl5/site_perl/5.40.4/x86_64-linux/auto/Net/SSLeay/SSLeay.so"),
),
Type: pkg.BinaryPkg,
}
child.SetID()
return []pkg.Package{parent, child}, []artifact.Relationship{
{
From: parent,
To: child,
Type: artifact.OwnershipByFileOverlapRelationship,
Data: ownershipByFilesMetadata{
Files: []string{"/usr/local/lib/perl5/site_perl/5.40.4/x86_64-linux/auto/Net/SSLeay/SSLeay.so"},
},
},
}
},
},
}
for _, test := range tests {
+50 -2
View File
@@ -65,6 +65,30 @@ func (i *Index) Add(relationships ...artifact.Relationship) {
}
func (i *Index) Remove(id artifact.ID) {
// scrub this node's edges from the adjacent nodes' maps before dropping the node itself. without this, the
// edges live on as dangling pointers inside the other endpoints' mappedRelationships, so query methods
// (From/To/References/Coordinates/Contains) would keep returning relationships that have been removed.
if mapped := i.fromID[id]; mapped != nil {
for _, sr := range mapped.allRelated {
if sr.to == id {
continue // self-edge: this node's own maps are deleted below
}
if other := i.toID[sr.to]; other != nil {
other.remove(id, sr)
}
}
}
if mapped := i.toID[id]; mapped != nil {
for _, sr := range mapped.allRelated {
if sr.from == id {
continue // self-edge: this node's own maps are deleted below
}
if other := i.fromID[sr.from]; other != nil {
other.remove(id, sr)
}
}
}
delete(i.fromID, id)
delete(i.toID, id)
@@ -78,6 +102,12 @@ func (i *Index) Remove(id artifact.ID) {
}
func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
if replacement == nil || replacement.ID() == ogID {
// replacing a node with one that has the same ID would re-add the (deduped) edges and then the trailing
// Remove(ogID) would delete them, silently wiping all of the node's relationships. treat it as a no-op.
return
}
for _, mapped := range fromMappedByID(i.fromID, ogID) {
// the stale relationship(i.e. if there's an elder ID in either side) should be discarded
if len(fromMappedByID(i.toID, mapped.relationship.To.ID())) == 0 {
@@ -91,8 +121,10 @@ func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
}
for _, mapped := range fromMappedByID(i.toID, ogID) {
// same as the above
if len(fromMappedByID(i.fromID, mapped.relationship.To.ID())) == 0 {
// same as the above, but check the surviving other endpoint (the From side, since these are edges TO ogID).
// note: this must reference From (not To, which is ogID itself) so that nodes appearing only on the To side
// of relationships (e.g. a go main module that nothing depends on) keep their edges after an ID change.
if len(fromMappedByID(i.fromID, mapped.relationship.From.ID())) == 0 {
continue
}
i.Add(artifact.Relationship{
@@ -206,6 +238,22 @@ func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelat
typeMap[id] = newRelationship
}
// remove deletes a single relationship (the other endpoint keyed by id) from this node's maps. it is the inverse
// of add and is used to keep adjacent nodes consistent when a node is removed from the index.
func (m *mappedRelationships) remove(id artifact.ID, target *sortableRelationship) {
filtered := m.allRelated[:0]
for _, r := range m.allRelated {
if r != target {
filtered = append(filtered, r)
}
}
m.allRelated = filtered
if typeMap := m.typeMap[target.relationship.Type]; typeMap != nil && typeMap[id] == target {
delete(typeMap, id)
}
}
type sortableRelationship struct {
from artifact.ID
to artifact.ID
+126
View File
@@ -380,6 +380,41 @@ func TestReplace(t *testing.T) {
compareRelationships(t, expectedRels, index.All())
}
func TestReplace_NodeOnlyOnToSide(t *testing.T) {
// regression: a node that only appears on the To side of relationships (nothing depends on it, e.g. a go main
// module that other modules are a dependency-of) must keep its incoming edges when its ID changes (such as when a
// compliance rule stubs a missing version and recomputes the package ID). previously the To-side remap guard
// checked ogID itself instead of the surviving From endpoint, so all such edges were dropped.
main := pkg.Package{Name: "main-module"} // empty version, like a go main module
dep1 := pkg.Package{Name: "dep-1", Version: "1.0.0"}
dep2 := pkg.Package{Name: "dep-2", Version: "2.0.0"}
for _, p := range []*pkg.Package{&main, &dep1, &dep2} {
p.SetID()
}
// both deps are a dependency-of main; main has no outgoing edges
r1 := artifact.Relationship{From: dep1, To: main, Type: artifact.DependencyOfRelationship}
r2 := artifact.Relationship{From: dep2, To: main, Type: artifact.DependencyOfRelationship}
index := NewIndex(r1, r2)
// simulate the version stub: main gets a new ID
stubbedMain := main
stubbedMain.Version = "UNKNOWN"
stubbedMain.SetID()
require.NotEqual(t, main.ID(), stubbedMain.ID())
index.Replace(main.ID(), &stubbedMain)
expectedRels := []artifact.Relationship{
{From: dep1, To: stubbedMain, Type: artifact.DependencyOfRelationship},
{From: dep2, To: stubbedMain, Type: artifact.DependencyOfRelationship},
}
compareRelationships(t, expectedRels, index.All())
}
func compareRelationships(t testing.TB, expected, actual []artifact.Relationship) {
assert.Equal(t, len(expected), len(actual), "number of relationships should match")
for _, e := range expected {
@@ -408,3 +443,94 @@ func TestReplace_NoExistingRelations(t *testing.T) {
allRels := index.All()
assert.Len(t, allRels, 0)
}
// Remove must scrub the removed node's edges from adjacent nodes too, otherwise query methods keep returning
// relationships that no longer exist. the original Remove only deleted the removed node's own maps, leaving
// dangling pointers in the other endpoints.
func TestRemove_NoStaleEdgesInAdjacentNodes(t *testing.T) {
p1 := pkg.Package{Name: "pkg-1", Version: "1"}
p2 := pkg.Package{Name: "pkg-2", Version: "2"}
p3 := pkg.Package{Name: "pkg-3", Version: "3"}
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
p.SetID()
}
r12 := artifact.Relationship{From: p1, To: p2, Type: artifact.DependencyOfRelationship}
r32 := artifact.Relationship{From: p3, To: p2, Type: artifact.DependencyOfRelationship}
index := NewIndex(r12, r32)
index.Remove(p1.ID())
// p2 was on the To side of the removed edge; its incoming view must no longer include the stale p1 edge,
// but must still include the live p3 edge.
compareRelationships(t, []artifact.Relationship{r32}, index.To(p2, artifact.DependencyOfRelationship))
assert.False(t, index.Contains(r12), "Contains must not report a removed edge")
assert.Len(t, index.References(p2), 1, "p2 should only reference the surviving p3 edge")
assert.True(t, index.Contains(r32), "surviving edge should remain")
compareRelationships(t, []artifact.Relationship{r32}, index.All())
}
// Replace must leave no dangling references to the old ID in adjacent nodes' query views.
func TestReplace_NoStaleEdgesAfterReplace(t *testing.T) {
main := pkg.Package{Name: "main-module"} // empty version
dep := pkg.Package{Name: "dep", Version: "1.0.0"}
main.SetID()
dep.SetID()
oldEdge := artifact.Relationship{From: dep, To: main, Type: artifact.DependencyOfRelationship}
index := NewIndex(oldEdge)
stubbed := main
stubbed.Version = "UNKNOWN"
stubbed.SetID()
require.NotEqual(t, main.ID(), stubbed.ID())
index.Replace(main.ID(), &stubbed)
newEdge := artifact.Relationship{From: dep, To: stubbed, Type: artifact.DependencyOfRelationship}
// the dep's outgoing view must point at the stubbed node, not the old one
from := index.From(dep, artifact.DependencyOfRelationship)
require.Len(t, from, 1)
assert.Equal(t, stubbed.ID(), from[0].To.ID())
assert.True(t, index.Contains(newEdge))
assert.False(t, index.Contains(oldEdge), "stale edge to old ID must be gone")
assert.Empty(t, index.To(main), "old node should have no incoming edges")
compareRelationships(t, []artifact.Relationship{newEdge}, index.All())
}
// replacing a node with one that has the same ID must be a no-op, not silently wipe its edges.
func TestReplace_SameIDIsNoOp(t *testing.T) {
a := pkg.Package{Name: "a", Version: "1"}
b := pkg.Package{Name: "b", Version: "2"}
a.SetID()
b.SetID()
edge := artifact.Relationship{From: a, To: b, Type: artifact.DependencyOfRelationship}
index := NewIndex(edge)
// same identity object -> same ID
index.Replace(b.ID(), b)
compareRelationships(t, []artifact.Relationship{edge}, index.All())
assert.True(t, index.Contains(edge))
}
// a self-edge on the replaced node should be remapped to replacement->replacement, not dropped.
func TestReplace_SelfEdge(t *testing.T) {
n := pkg.Package{Name: "n"} // empty version
n.SetID()
index := NewIndex(artifact.Relationship{From: n, To: n, Type: artifact.DependencyOfRelationship})
sn := n
sn.Version = "UNKNOWN"
sn.SetID()
require.NotEqual(t, n.ID(), sn.ID())
index.Replace(n.ID(), &sn)
all := index.All()
require.Len(t, all, 1)
assert.Equal(t, sn.ID(), all[0].From.ID())
assert.Equal(t, sn.ID(), all[0].To.ID())
assert.Empty(t, index.From(n), "no edges should reference the old ID")
assert.Empty(t, index.To(n), "no edges should reference the old ID")
}
+7
View File
@@ -0,0 +1,7 @@
package internal
// MaxSafeTensorsHeaderSize caps a safetensors header's JSON (excluding its
// 8-byte length prefix). Shared by the ai cataloger and the OCI model source so
// dir and OCI scans accept the same headers. Real-world shard headers are well
// under 1 MB.
const MaxSafeTensorsHeaderSize = 8 * 1024 * 1024

Some files were not shown because too many files have changed in this diff Show More