Compare commits

..
Author SHA1 Message Date
Christopher Phillips 83dfbcc24a fix(binary): require gzip identity evidence in gzip classifier
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2026-08-13 13:35:48 -04: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
55 changed files with 2160 additions and 238 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ tools:
# used to upload test fixture cache
- name: oras
version:
want: v1.3.2
want: v1.3.3
method: github-release
with:
repo: oras-project/oras
+9 -4
View File
@@ -32,7 +32,7 @@ jobs:
if: ${{ github.event.inputs.phase == 'all' }}
permissions:
contents: read # required for fetching tags
uses: anchore/workflows/.github/workflows/check-version-available.yaml@b0c30a80409130d329aaa356fd64a34d8c0b3375 # v0.7.2
uses: anchore/workflows/.github/workflows/check-version-available.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
with:
version: ${{ github.event.inputs.version }}
@@ -75,7 +75,7 @@ jobs:
packages: write # required for publishing release artifacts to GitHub packages
id-token: write # required for keyless signing (cosign/sigstore OIDC)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
fetch-depth: 0
persist-credentials: true
@@ -83,14 +83,19 @@ 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@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to Docker Hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 #v4.4.0
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 #v4.5.1
with:
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
- name: Login to GitHub Container Registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 #v4.4.0
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 #v4.5.1
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -20,12 +20,12 @@ jobs:
contents: read
security-events: write # for uploading SARIF results
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Run zizmor"
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
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
+7 -7
View File
@@ -28,7 +28,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -50,7 +50,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -72,7 +72,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -105,7 +105,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -135,7 +135,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -179,7 +179,7 @@ jobs:
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
@@ -213,7 +213,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
with:
persist-credentials: false
+52 -9
View File
@@ -3,8 +3,10 @@ package main
import (
"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"
@@ -27,22 +29,43 @@ func main() {
gotest.Name("unit"),
gotest.ExcludeGlob("**/test/**"),
gotest.CoverageThreshold(62),
race(),
),
// integration tests: native go-make Task. The race-detector smoke against a
// real image stays bundled here (RunsOn integration) so `make integration`
// behaves like the Taskfile version did.
gotest.Tasks(
gotest.Name("integration"),
gotest.IncludeGlob("./cmd/syft/internal/test/integration/..."),
gotest.Verbose(),
gotest.NoCoverage(),
),
// 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 -v"+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")
},
},
@@ -132,6 +155,26 @@ func main() {
)
}
// 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.
+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)
}
})
}
}
+1
View File
@@ -125,6 +125,7 @@ tasks:
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
@@ -0,0 +1,145 @@
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"
)
// 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) {
remoteImage := "docker.io/library/busybox:1.38.0"
// 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:e0e8b3cbfed68a90084781e2962f9c0deead51c5a3f11a488eef0283a4284bc2",
"s390x": "sha256:0cf160e720a8e4f20883b72276a6eaded83b79539f3f1d39e35a3336154b9960",
"amd64": "sha256:c6348fa86ba0fb2108c9334f5fe913ddc6d853313e655891f133a0127c30099f",
"ppc64le": "sha256:b144fc0e06537d07956cde340b878a81a717e394edb736d3110858a12a6635cb",
}
// 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, remoteImage)
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)
// ...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) {
remoteImage := "docker.io/library/busybox:1.38.0"
// 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, remoteImage)
_, 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) {
remoteImage := "docker.io/library/busybox:1.38.0"
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, remoteImage)
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)
})
}
}
// 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)
}
@@ -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/
+33 -33
View File
@@ -18,11 +18,11 @@ require (
github.com/anchore/go-homedir v0.1.1
github.com/anchore/go-logger v0.1.1
github.com/anchore/go-macholibre v0.1.1
github.com/anchore/go-rpmdb v0.1.0
github.com/anchore/go-rpmdb v0.2.0
github.com/anchore/go-sync v0.1.1
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.2.0
github.com/anchore/stereoscope v0.2.2
github.com/anchore/stereoscope v0.3.0
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
@@ -34,14 +34,14 @@ require (
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.9.3
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/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.7.0
github.com/gkampitakis/go-snaps v0.5.22
github.com/go-git/go-billy/v5 v5.9.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.1
github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.5.0
@@ -52,18 +52,18 @@ require (
github.com/google/licensecheck v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.6.1
github.com/gpustack/gguf-parser-go v0.24.1
github.com/gpustack/gguf-parser-go v0.25.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-getter v1.8.6
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/hcl/v2 v2.24.0
github.com/iancoleman/strcase v0.3.0
github.com/invopop/jsonschema v0.14.0
github.com/jedib0t/go-pretty/v6 v6.8.1
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/klauspost/compress v1.19.0
github.com/magiconair/properties v1.8.10
github.com/klauspost/compress v1.19.1
github.com/magiconair/properties v1.18.11
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
@@ -83,7 +83,7 @@ require (
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/ulikunitz/xz v0.5.16
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
@@ -93,12 +93,12 @@ require (
go.uber.org/goleak v1.3.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/mod v0.37.0
golang.org/x/net v0.56.0
golang.org/x/mod v0.38.0
golang.org/x/net v0.57.0
golang.org/x/time v0.15.0
golang.org/x/tools v0.47.0
golang.org/x/tools v0.48.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.53.0
modernc.org/sqlite v1.55.0
)
require (
@@ -118,7 +118,7 @@ require (
cloud.google.com/go/storage v1.61.3 // 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.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.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/Masterminds/goutils v1.1.1 // indirect
@@ -168,10 +168,10 @@ require (
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/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
github.com/containerd/cgroups/v3 v3.1.3 // indirect
github.com/containerd/containerd/api v1.11.1 // indirect
github.com/containerd/containerd/v2 v2.3.2 // indirect
github.com/containerd/containerd/v2 v2.3.3 // 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
@@ -183,14 +183,14 @@ require (
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.5.3+incompatible // indirect
github.com/docker/cli v29.6.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.7.0 // 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/felixge/fgprof v0.9.5 // indirect
@@ -238,8 +238,8 @@ require (
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.2 // indirect
github.com/moby/moby/client v0.4.1 // indirect
github.com/moby/moby/api v1.55.0 // indirect
github.com/moby/moby/client v0.5.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
@@ -257,7 +257,7 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.3.1 // 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
@@ -299,7 +299,7 @@ require (
github.com/zclconf/go-cty v1.16.3 // indirect
go.opencensus.io v0.24.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/detectors/gcp v1.43.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
@@ -309,22 +309,22 @@ require (
go.opentelemetry.io/otel/trace v1.43.0 // 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.53.0 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.38.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gonum.org/v1/gonum v0.17.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-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.73.4 // indirect
modernc.org/libc v1.74.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+72 -72
View File
@@ -82,8 +82,8 @@ github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByX
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
@@ -136,8 +136,8 @@ github.com/anchore/go-lzo v0.1.1 h1:IwL/fvkdtlIrYIXck6WxZ3nb8WjjHziYYmGxlooyOnM=
github.com/anchore/go-lzo v0.1.1/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
github.com/anchore/go-macholibre v0.1.1 h1:sTBHi3S1WVIOCamc6NyT8Sn9mBmD30DCTfjfVm4k6/M=
github.com/anchore/go-macholibre v0.1.1/go.mod h1:YNq2610RlvGCK0Za+Klz/OCMtUaEnwwAGA/VfUAQaro=
github.com/anchore/go-rpmdb v0.1.0 h1:Q8dc208/HYzCqhx0L1zurfm1UPil24hlo9NjkdFmLdE=
github.com/anchore/go-rpmdb v0.1.0/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
github.com/anchore/go-rpmdb v0.2.0 h1:ixcyMg/guhBL9XjBVwhTv9CG48xO07Qkg2uDX2+zx/c=
github.com/anchore/go-rpmdb v0.2.0/go.mod h1:ATsRlpCXstnoYfzqBfhwGw0U2dolx1BBQ9rAU8jWBAw=
github.com/anchore/go-struct-converter v0.2.0-rc2 h1:q+859fW2/jbHJHB2etbNfRlFwYpknyvbqqk1hUdamQ4=
github.com/anchore/go-struct-converter v0.2.0-rc2/go.mod h1:cDBA5vhcR62nXWo8QH9/Kk2807o65ISaHPNPX66L+Uw=
github.com/anchore/go-sync v0.1.1 h1:91SZ+YqUIIHmf2jPAYZPuHqM/ZqnK1pVJDWtET6+AJE=
@@ -146,8 +146,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk=
github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE=
github.com/anchore/stereoscope v0.2.2 h1:SGTLGoF6GHmKEn9Bb6LYpzzgz9nhvMZgN/c4fTSQjYY=
github.com/anchore/stereoscope v0.2.2/go.mod h1:ylJXJKebLctP7u9ewguB1d0zUETJxvAA7r2DiuljDTM=
github.com/anchore/stereoscope v0.3.0 h1:6mGGRxm4zRKj/jNiSM7g6XGwWihrnkQRJj11hcULDqY=
github.com/anchore/stereoscope v0.3.0/go.mod h1:QIBWxa5WCrjtBqXH0+RIuECATrffcbaNdMWquZ83+OI=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
@@ -290,14 +290,14 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ=
github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
github.com/containerd/containerd/api v1.11.1 h1:h8nfoDW9+fNsC/9TwiAHj8B1GzXKtR4eFtkhi/X5RLU=
github.com/containerd/containerd/api v1.11.1/go.mod h1:CaQFRu+N1MtbgL6JDOJLUB1hCKESU1lD6MuTJhgtdlw=
github.com/containerd/containerd/v2 v2.3.2 h1:eLven1YxRMkeiKu7IcMrPKE+gn8sGR1DqHbbshMEvWM=
github.com/containerd/containerd/v2 v2.3.2/go.mod h1:rHKGm3VW6wNrINb3x8mNT+w7qYXFVElTt/8HTuxVhD4=
github.com/containerd/containerd/v2 v2.3.3 h1:MUNBVVBTBpPll7KPh5GTvkC3cfG03PQLAHVdsUoue9k=
github.com/containerd/containerd/v2 v2.3.3/go.mod h1:rHKGm3VW6wNrINb3x8mNT+w7qYXFVElTt/8HTuxVhD4=
github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg=
github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@@ -333,14 +333,14 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/diskfs/go-diskfs v1.9.3 h1:cLciNCeZ4QAXVxyPJDr1ZJ9N9CCG3rQlQ/z/Cs/cNDM=
github.com/diskfs/go-diskfs v1.9.3/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc=
github.com/diskfs/go-diskfs v1.9.4 h1:0j2d7eG4IjyxL6+ChWbDPocdBCF6HQ4HBWU2WDYWVnc=
github.com/diskfs/go-diskfs v1.9.4/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/docker/cli v29.5.3+incompatible h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs=
github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8=
github.com/docker/cli v29.6.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
@@ -371,14 +371,14 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/erofs/go-erofs v0.3.0 h1:o/W5ABAA3sHYl97WL93dacKEfeDpJhdFf3c2snAti7I=
@@ -410,16 +410,16 @@ github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XF
github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0=
github.com/gkampitakis/ciinfo v0.3.4 h1:5eBSibVuSMbb/H6Elc0IIEFbkzCJi3lm94n0+U7Z0KY=
github.com/gkampitakis/ciinfo v0.3.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-snaps v0.5.22 h1:xg9omphRnbDnimMCl1KqznC4krlxOGpkB0vDSfX2P7M=
github.com/gkampitakis/go-snaps v0.5.22/go.mod h1:uy3lVzCCRRsAwYqSocyw5fY8xRLCYEfqoOJNxr8HonM=
github.com/gkampitakis/go-snaps v0.5.23 h1:okh5QR48zpUjpWtu65AtqxdCY8huJq+dEDuUzd1PuKg=
github.com/gkampitakis/go-snaps v0.5.23/go.mod h1:uy3lVzCCRRsAwYqSocyw5fY8xRLCYEfqoOJNxr8HonM=
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
@@ -558,8 +558,8 @@ github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gpustack/gguf-parser-go v0.24.1 h1:nTYtL8HFK6ZhB90RKBu4oX2b3ZHpJLrMmKRfL9w9Cyc=
github.com/gpustack/gguf-parser-go v0.24.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
github.com/gpustack/gguf-parser-go v0.25.0 h1:1AMBhMKtI24nTtn588Bq53FqNiOvEw1x9Nb4HbRrThs=
github.com/gpustack/gguf-parser-go v0.25.0/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
@@ -624,8 +624,8 @@ github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8i
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jedib0t/go-pretty/v6 v6.8.1 h1:0fkCNhjrX0zPpwkWaDYU5VMrygg41Tu197mWILIJoqQ=
github.com/jedib0t/go-pretty/v6 v6.8.1/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jedib0t/go-pretty/v6 v6.8.3 h1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=
github.com/jedib0t/go-pretty/v6 v6.8.3/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
@@ -645,8 +645,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -670,8 +670,8 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/magiconair/properties v1.18.11 h1:j5ozYZl0zCjG7ahMDH0GWIobOvvUzT0BdAguG0ViKy0=
github.com/magiconair/properties v1.18.11/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg=
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
@@ -723,10 +723,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=
github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
@@ -786,8 +786,8 @@ github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrp
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/piprate/json-gold v0.7.0 h1:bEMirgA5y8Z2loTQfxyIFfY+EflxH1CTP6r/KIlcJNw=
@@ -947,8 +947,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0=
github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
github.com/vbatts/go-mtree v0.7.0 h1:ytmOc3MTRidZiBi9VBCyZ2BHe4fZS47L5v7BVXDWW4E=
github.com/vbatts/go-mtree v0.7.0/go.mod h1:EjdpFC+LZy1TXbRGNa1MKKgjQ+7ew3foMFJK8o4/TdY=
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
@@ -998,8 +998,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
@@ -1040,8 +1040,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1081,8 +1081,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1127,8 +1127,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1160,8 +1160,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1236,13 +1236,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1253,8 +1253,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1315,8 +1315,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1431,10 +1431,10 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1462,8 +1462,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1513,20 +1513,20 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -1535,8 +1535,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
modernc.org/sqlite v1.55.0 h1:hIFh0MCH0rGinQ/4KYb5/UbCkRkb+UP+OkLCVWa5MTM=
modernc.org/sqlite v1.55.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+25
View File
@@ -152,6 +152,31 @@ func NewLocationFromImage(accessPath string, ref file.Reference, img *image.Imag
}
}
// NewVirtualLocationFromImage creates a new Location surfaced at realPath but whose contents and metadata are
// described by the given reference. This is used to present a hardlink as the underlying type it points to (at the
// hardlink's own path) so that image results are in parity with directory results, which cannot distinguish a
// hardlink from a regular file. Note that RealPath (the hardlink's own path) intentionally differs from
// ref.RealPath (the target's path); callers must not assume the two agree for these locations.
func NewVirtualLocationFromImage(realPath, accessPath string, ref file.Reference, img *image.Image) Location {
// the FileSystemID comes from the target ref's layer. a hardlink and its target are always materialized in the
// same layer tar (a tar hardlink entry can only reference a file within the same archive), so the target's layer
// digest is also the hardlink path's layer.
layer := img.FileCatalog.Layer(ref)
return Location{
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: realPath,
FileSystemID: layer.Metadata.Digest,
},
AccessPath: accessPath,
ref: ref,
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
},
}
}
// NewLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory.
func NewLocationFromDirectory(responsePath string, fd string, ref file.Reference) Location {
return Location{
@@ -53,8 +53,20 @@ func (r *ContainerImageAllLayers) HasPath(path string) bool {
return false
}
func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int) ([]stereoscopeFile.Reference, error) {
uniqueFiles := make([]stereoscopeFile.Reference, 0)
func (r *ContainerImageAllLayers) locationsByRef(ref stereoscopeFile.Reference, accessPath string, uniqueFileIDs stereoscopeFile.ReferenceSet, layerPos int) ([]file.Location, error) {
uniqueLocations := make([]file.Location, 0)
// if the access path is itself a hardlink, surface it as the underlying type it points to, at its own path
// (bound to the target's content), so that image results are in parity with directory results (which cannot
// tell a hardlink from a regular file). the path-based lookup is required because the search that produced ref
// already followed the basename link, collapsing the hardlink onto its target.
if ownRef, targetRef, ok := r.hardLinkAtPath(accessPath, r.layers[layerPos]); ok {
if !uniqueFileIDs.Contains(ownRef) {
uniqueFileIDs.Add(ownRef)
uniqueLocations = append(uniqueLocations, file.NewVirtualLocationFromImage(string(ownRef.RealPath), accessPath, targetRef, r.img))
}
return uniqueLocations, nil
}
// since there is potentially considerable work for each symlink/hardlink that needs to be resolved, let's check to see if this is a symlink/hardlink first
entry, err := r.img.FileCatalog.Get(ref)
@@ -65,22 +77,41 @@ func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqu
if entry.Type == stereoscopeFile.TypeHardLink || entry.Type == stereoscopeFile.TypeSymLink {
// a link may resolve in this layer or higher, assuming a squashed tree is used to search
// we should search all possible resolutions within the valid source
for _, subLayerIdx := range r.layers[layerIdx:] {
for _, subLayerIdx := range r.layers[layerPos:] {
resolvedRef, err := r.img.ResolveLinkByLayerSquash(ref, subLayerIdx)
if err != nil {
return nil, fmt.Errorf("failed to resolve link from layer (layer=%d ref=%+v): %w", subLayerIdx, ref, err)
}
if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
uniqueFileIDs.Add(*resolvedRef.Reference)
uniqueFiles = append(uniqueFiles, *resolvedRef.Reference)
uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(accessPath, *resolvedRef.Reference, r.img))
}
}
} else if !uniqueFileIDs.Contains(ref) {
uniqueFileIDs.Add(ref)
uniqueFiles = append(uniqueFiles, ref)
uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(accessPath, ref, r.img))
}
return uniqueFiles, nil
return uniqueLocations, nil
}
// hardLinkAtPath returns the hardlink's own reference and its resolved target reference when the basename of path is a
// hardlink within the given layer. ok is false when path does not exist there or is not a hardlink. The lookup does
// not follow the basename link so that the hardlink's own path is preserved. This adds a tree walk per
// matched ref (path x layer); if it shows up in profiles, fold the hardlink check into the existing search resolution.
func (r *ContainerImageAllLayers) hardLinkAtPath(path string, layerIdx int) (stereoscopeFile.Reference, stereoscopeFile.Reference, bool) {
var own stereoscopeFile.Reference
// use the squashed-to-layer view (not just this layer's additions) so a hardlink added in a lower layer is still
// detected when the path is searched at a higher layer; otherwise it would collapse onto its target there.
exists, resolution, err := r.img.Layers[layerIdx].SquashedTree.File(stereoscopeFile.Path(path))
if err != nil || !exists || !resolution.HasReference() {
return own, own, false
}
target, ok := r.resolveHardLinkTarget(*resolution.Reference, layerIdx)
if !ok {
return own, own, false
}
return *resolution.Reference, target, true
}
// FilesByPath returns all file.References that match the given paths from any layer in the image.
@@ -112,14 +143,13 @@ func (r *ContainerImageAllLayers) FilesByPath(paths ...string) ([]file.Location,
}
}
results, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
locations, err := r.locationsByRef(*ref.Reference, path, uniqueFileIDs, idx)
if err != nil {
return nil, err
}
for _, result := range results {
l := file.NewLocationFromImage(path, result, r.img)
r.annotateLocation(&l)
uniqueLocations = append(uniqueLocations, l)
for i := range locations {
r.annotateLocation(&locations[i])
uniqueLocations = append(uniqueLocations, locations[i])
}
}
}
@@ -158,14 +188,13 @@ func (r *ContainerImageAllLayers) FilesByGlob(patterns ...string) ([]file.Locati
}
}
refResults, err := r.fileByRef(*result.Reference, uniqueFileIDs, idx)
locations, err := r.locationsByRef(*result.Reference, string(result.RequestPath), uniqueFileIDs, idx)
if err != nil {
return nil, err
}
for _, refResult := range refResults {
l := file.NewLocationFromImage(string(result.RequestPath), refResult, r.img)
r.annotateLocation(&l)
uniqueLocations = append(uniqueLocations, l)
for i := range locations {
r.annotateLocation(&locations[i])
uniqueLocations = append(uniqueLocations, locations[i])
}
}
}
@@ -233,14 +262,13 @@ func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Locat
continue
}
refResults, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
locations, err := r.locationsByRef(*ref.Reference, string(ref.RequestPath), uniqueFileIDs, idx)
if err != nil {
return nil, err
}
for _, refResult := range refResults {
l := file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img)
r.annotateLocation(&l)
uniqueLocations = append(uniqueLocations, l)
for i := range locations {
r.annotateLocation(&locations[i])
uniqueLocations = append(uniqueLocations, locations[i])
}
}
}
@@ -256,6 +284,11 @@ func (r *ContainerImageAllLayers) AllLocations(ctx context.Context) <-chan file.
tree := r.img.Layers[layerIdx].Tree
for _, ref := range tree.AllFiles(stereoscopeFile.AllTypes()...) {
l := file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
// surface a hardlink as the underlying type it points to (at its own path) so image results match
// directory results, which cannot distinguish a hardlink from a regular file.
if targetRef, ok := r.resolveHardLinkTarget(ref, layerIdx); ok {
l = file.NewVirtualLocationFromImage(string(ref.RealPath), string(ref.RealPath), targetRef, r.img)
}
r.annotateLocation(&l)
select {
case <-ctx.Done():
@@ -273,6 +306,21 @@ func (r *ContainerImageAllLayers) FileMetadataByLocation(location file.Location)
return fileMetadataByLocation(r.img, location)
}
// resolveHardLinkTarget returns the reference of a hardlink's underlying target (resolved relative to the given layer)
// when ref is a hardlink; ok is false otherwise. No resolution is performed for non-hardlinks (symlinks keep their
// existing resolution semantics).
func (r *ContainerImageAllLayers) resolveHardLinkTarget(ref stereoscopeFile.Reference, layerIdx int) (stereoscopeFile.Reference, bool) {
metadata, err := r.img.FileCatalog.Get(ref)
if err != nil || metadata.Type != stereoscopeFile.TypeHardLink {
return ref, false
}
resolved, err := r.img.ResolveLinkByLayerSquash(ref, layerIdx)
if err != nil || !resolved.HasReference() {
return ref, false
}
return *resolved.Reference, true
}
func (r *ContainerImageAllLayers) annotateLocation(l *file.Location) {
if !r.markVisibility || l == nil {
return
@@ -282,26 +330,24 @@ func (r *ContainerImageAllLayers) annotateLocation(l *file.Location) {
annotation := file.VisibleAnnotation
// if we find a location for a path that matches the query (e.g. **/node_modules) but is not present in the squashed tree, skip it
ref, err := r.img.SquashedSearchContext.SearchByPath(l.RealPath, filetree.DoNotFollowDeadBasenameLinks)
if err != nil || !ref.HasReference() {
annotation = file.HiddenAnnotation
} else if ref.ID() != givenRef.ID() {
// we may have the path in the squashed tree, but this must not be in the same layer
if !r.pathResolvesToRef(l.RealPath, givenRef) {
annotation = file.HiddenAnnotation
}
// not only should the real path to the file exist, but the way we took to get there should also exist
// (e.g. if we are looking for /etc/passwd, but the real path is /etc/passwd -> /etc/passwd-1, then we should
// make certain that /etc/passwd-1 exists)
if annotation == file.VisibleAnnotation && l.AccessPath != "" {
ref, err := r.img.SquashedSearchContext.SearchByPath(l.AccessPath, filetree.DoNotFollowDeadBasenameLinks)
if err != nil || !ref.HasReference() {
annotation = file.HiddenAnnotation
} else if ref.ID() != givenRef.ID() {
// we may have the path in the squashed tree, but this must not be in the same layer
annotation = file.HiddenAnnotation
}
if annotation == file.VisibleAnnotation && l.AccessPath != "" && !r.pathResolvesToRef(l.AccessPath, givenRef) {
annotation = file.HiddenAnnotation
}
l.Annotations[file.VisibleAnnotationKey] = annotation
}
// pathResolvesToRef reports whether the given path in the squashed tree resolves to the given reference.
// SearchByPath always follows basename links, so a hardlink surfaced at its own path (whose reference is its
// target) still resolves to the target here and is correctly considered visible.
func (r *ContainerImageAllLayers) pathResolvesToRef(path string, target stereoscopeFile.Reference) bool {
ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.DoNotFollowDeadBasenameLinks)
return err == nil && ref.HasReference() && ref.ID() == target.ID()
}
@@ -40,6 +40,17 @@ func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, er
uniqueLocations := make([]file.Location, 0)
for _, path := range paths {
// if the requested path is itself a hardlink, surface it at its own path (bound to its target's content)
// rather than collapsing it onto the target's path, so that image results are in parity with directory
// results (which cannot tell a hardlink from a regular file).
if ownRef, targetRef, ok := r.hardLinkAtPath(path); ok {
if !uniqueFileIDs.Contains(ownRef) {
uniqueFileIDs.Add(ownRef)
uniqueLocations = append(uniqueLocations, file.NewVirtualLocationFromImage(string(ownRef.RealPath), path, targetRef, r.img))
}
continue
}
ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.FollowBasenameLinks)
if err != nil {
return nil, err
@@ -82,7 +93,7 @@ func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, er
//
//nolint:gocognit
func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) {
uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
uniqueCoordinates := file.NewCoordinateSet()
uniqueLocations := make([]file.Location, 0)
for _, pattern := range patterns {
@@ -116,10 +127,14 @@ func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location,
return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err)
}
for _, resolvedLocation := range resolvedLocations {
if uniqueFileIDs.Contains(resolvedLocation.Reference()) {
// dedup on the surfaced coordinate rather than the underlying reference: distinct hardlinks share a
// single target reference but each has its own real path, so a reference-based dedup would collapse
// them back onto one entry (the exact behavior this parity fix removes). symlink resolutions keep
// their target's real path, so they still collapse as before.
if uniqueCoordinates.Contains(resolvedLocation.Coordinates) {
continue
}
uniqueFileIDs.Add(resolvedLocation.Reference())
uniqueCoordinates.Add(resolvedLocation.Coordinates)
uniqueLocations = append(uniqueLocations, resolvedLocation)
}
}
@@ -179,10 +194,16 @@ func (r *ContainerImageSquash) AllLocations(ctx context.Context) <-chan file.Loc
go func() {
defer close(results)
for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) {
loc := file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
// surface a hardlink as the underlying type it points to (at its own path) so image results match
// directory results, which cannot distinguish a hardlink from a regular file.
if targetRef, ok := r.resolveHardLinkTarget(ref); ok {
loc = file.NewVirtualLocationFromImage(string(ref.RealPath), string(ref.RealPath), targetRef, r.img)
}
select {
case <-ctx.Done():
return
case results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img):
case results <- loc:
continue
}
}
@@ -190,6 +211,37 @@ func (r *ContainerImageSquash) AllLocations(ctx context.Context) <-chan file.Loc
return results
}
// hardLinkAtPath returns the hardlink's own reference and its resolved target reference when the basename of path is a
// hardlink. ok is false when path does not exist or is not a hardlink. The lookup does not follow the basename link so
// that the hardlink's own path is preserved. This adds a tree walk per FilesByPath path; if it shows up in
// profiles, fold the hardlink check into the existing SearchByPath resolution.
func (r *ContainerImageSquash) hardLinkAtPath(path string) (stereoscopeFile.Reference, stereoscopeFile.Reference, bool) {
var own stereoscopeFile.Reference
exists, resolution, err := r.img.SquashedTree().File(stereoscopeFile.Path(path))
if err != nil || !exists || !resolution.HasReference() {
return own, own, false
}
target, ok := r.resolveHardLinkTarget(*resolution.Reference)
if !ok {
return own, own, false
}
return *resolution.Reference, target, true
}
// resolveHardLinkTarget returns the reference of a hardlink's underlying target when ref is a hardlink; ok is false
// otherwise. No resolution is performed for non-hardlinks (symlinks keep their existing resolution semantics).
func (r *ContainerImageSquash) resolveHardLinkTarget(ref stereoscopeFile.Reference) (stereoscopeFile.Reference, bool) {
metadata, err := r.img.FileCatalog.Get(ref)
if err != nil || metadata.Type != stereoscopeFile.TypeHardLink {
return ref, false
}
resolved, err := r.img.ResolveLinkByImageSquash(ref)
if err != nil || !resolved.HasReference() {
return ref, false
}
return *resolved.Reference, true
}
func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) {
refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...)
if err != nil {
@@ -14,6 +14,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/file"
)
@@ -574,3 +575,87 @@ func TestSquashResolver_AllLocations(t *testing.T) {
assert.ElementsMatchf(t, expected, pathsList, "expected all paths to be indexed, but found different paths: \n%s", cmp.Diff(expected, paths.List()))
}
// a hardlink should be surfaced at its own path as the underlying type it points to (a regular file with the target's
// content and metadata), so that image results are in parity with directory results (which cannot distinguish a
// hardlink from a regular file). this must hold for both the squashed and all-layers resolvers.
func TestImageResolvers_Hardlinks(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-hardlinks")
resolvers := map[string]file.Resolver{}
squash, err := NewFromContainerImageSquash(img)
require.NoError(t, err)
resolvers["squashed"] = squash
allLayers, err := NewFromContainerImageAllLayers(img)
require.NoError(t, err)
resolvers["all-layers"] = allLayers
const wantContents = "hardlinked contents\n"
// asserts a location surfaces the hardlink at its own path as a regular file with the target's content, and is
// not marked hidden (all-layers annotates visibility; squash leaves the annotation unset, which must not read as
// hidden either).
assertHardlink := func(t *testing.T, resolver file.Resolver, wantPath string, loc file.Location) {
t.Helper()
assert.Equal(t, wantPath, loc.RealPath, "expected the hardlink's own path, not the target's")
assert.NotEqual(t, file.HiddenAnnotation, loc.Annotations[file.VisibleAnnotationKey], "path=%s should not be hidden", wantPath)
meta, err := resolver.FileMetadataByLocation(loc)
require.NoError(t, err)
assert.Equal(t, stereoscopeFile.TypeRegular, meta.Type, "path=%s", wantPath)
reader, err := resolver.FileContentsByLocation(loc)
require.NoError(t, err)
actual, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, wantContents, string(actual), "path=%s", wantPath)
}
for name, resolver := range resolvers {
t.Run(name, func(t *testing.T) {
t.Run("FilesByPath surfaces a hardlink at its own path with the target's content", func(t *testing.T) {
for _, path := range []string{"/hardlink-a", "/hardlink-b", "/file.txt"} {
locs, err := resolver.FilesByPath(path)
require.NoError(t, err)
require.Len(t, locs, 1, "path=%s", path)
assertHardlink(t, resolver, path, locs[0])
}
})
t.Run("FilesByGlob surfaces every hardlink in a matched set, not just one", func(t *testing.T) {
// the whole point of #5019: a glob that matches multiple hardlinks to the same target must return
// all of them (each at its own path), not collapse them onto a single entry.
locs, err := resolver.FilesByGlob("**/hardlink-*")
require.NoError(t, err)
byPath := map[string]file.Location{}
for _, loc := range locs {
_, dup := byPath[loc.RealPath]
assert.Falsef(t, dup, "path %s emitted more than once", loc.RealPath)
byPath[loc.RealPath] = loc
}
for _, path := range []string{"/hardlink-a", "/hardlink-b"} {
loc, ok := byPath[path]
require.Truef(t, ok, "expected glob to surface %s", path)
assertHardlink(t, resolver, path, loc)
}
})
t.Run("AllLocations includes every hardlink path as a regular file exactly once", func(t *testing.T) {
counts := map[string]int{}
locsByPath := map[string]file.Location{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for loc := range resolver.AllLocations(ctx) {
counts[loc.RealPath]++
locsByPath[loc.RealPath] = loc
}
for _, path := range []string{"/file.txt", "/hardlink-a", "/hardlink-b"} {
require.Equalf(t, 1, counts[path], "expected %s to be reported exactly once by AllLocations", path)
assertHardlink(t, resolver, path, locsByPath[path])
}
})
})
}
}
@@ -0,0 +1,12 @@
# LAYER 0:
FROM busybox:1.34.0@sha256:e8e5cca392e3cf056fcdb3093e7ac2bf83fcf28b3bcf5818fe8ae71cf360c231
# LAYER 1: a regular file with two hardlinks to it, all created in the same layer so the layer tar contains one
# regular file entry and two hardlink (tar TypeLink) entries pointing at it.
RUN echo "hardlinked contents" > /file.txt && ln /file.txt /hardlink-a && ln /file.txt /hardlink-b
# squash representation (of the files added here; busybox base also contributes hardlinked applets under /bin)
# .
# ├── file.txt
# ├── hardlink-a (hardlink -> file.txt)
# └── hardlink-b (hardlink -> file.txt)
@@ -32,12 +32,15 @@ type parsedData struct {
pkg.ApkDBEntry
}
const maxApkDBFieldSize = 10 * 1024 * 1024
// parseApkDB parses packages from a given APK "installed" flat-file DB. For more
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
//
//nolint:funlen
func parseApkDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
scanner := bufio.NewScanner(reader)
scanner.Buffer(nil, maxApkDBFieldSize)
var errs error
var apks []parsedData
@@ -719,6 +719,25 @@ func Test_processChecksum(t *testing.T) {
}
}
func TestParseApkDBAllowsLargeFieldValues(t *testing.T) {
contents := strings.Join([]string{
"P:large-description",
"V:1.0-r0",
"A:x86_64",
"S:1",
"I:1",
"T:" + strings.Repeat("a", 70*1024),
"",
}, "\n")
reader := file.NewLocationReadCloser(file.NewLocation("large-installed-db"), io.NopCloser(strings.NewReader(contents)))
pkgs, _, err := parseApkDB(context.Background(), nil, new(generic.Environment), reader)
require.NoError(t, err)
require.Len(t, pkgs, 1)
assert.Equal(t, "large-description", pkgs[0].Name)
}
func Test_parseApkDB_expectedPkgNames(t *testing.T) {
tests := []struct {
fixture string
@@ -433,6 +433,16 @@ catalogers:
cpes:
- cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*
type: BinaryPkg
- method: glob
criteria:
- '**/bun'
packages:
- class: bun-binary
name: bun
purl: pkg:generic/bun
cpes:
- cpe:2.3:a:oven-sh:bun:*:*:*:*:*:*:*:*
type: BinaryPkg
- method: glob
criteria:
- '**/ghc*'
@@ -1499,6 +1499,28 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("deno-binary"),
},
},
{
logicalFixture: "deno/1.11.3/linux-amd64",
expected: pkg.Package{
Name: "deno",
Version: "1.11.3",
Type: "binary",
PURL: "pkg:generic/deno@1.11.3",
Locations: locations("deno"),
Metadata: metadata("deno-binary"),
},
},
{
logicalFixture: "deno/1.11.4/linux-amd64",
expected: pkg.Package{
Name: "deno",
Version: "1.11.4",
Type: "binary",
PURL: "pkg:generic/deno@1.11.4",
Locations: locations("deno"),
Metadata: metadata("deno-binary"),
},
},
{
logicalFixture: "deno/1.16.4/linux-amd64",
expected: pkg.Package{
@@ -1565,6 +1587,61 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("deno-binary"),
},
},
{
logicalFixture: "deno/2.9.2/linux-amd64",
expected: pkg.Package{
Name: "deno",
Version: "2.9.2",
Type: "binary",
PURL: "pkg:generic/deno@2.9.2",
Locations: locations("deno"),
Metadata: metadata("deno-binary"),
},
},
{
logicalFixture: "bun/0.5.9/linux-amd64",
expected: pkg.Package{
Name: "bun",
Version: "0.5.9",
Type: "binary",
PURL: "pkg:generic/bun@0.5.9",
Locations: locations("bun"),
Metadata: metadata("bun-binary"),
},
},
{
logicalFixture: "bun/1.0.0/linux-amd64",
expected: pkg.Package{
Name: "bun",
Version: "1.0.0",
Type: "binary",
PURL: "pkg:generic/bun@1.0.0",
Locations: locations("bun"),
Metadata: metadata("bun-binary"),
},
},
{
logicalFixture: "bun/1.1.0/linux-amd64",
expected: pkg.Package{
Name: "bun",
Version: "1.1.0",
Type: "binary",
PURL: "pkg:generic/bun@1.1.0",
Locations: locations("bun"),
Metadata: metadata("bun-binary"),
},
},
{
logicalFixture: "bun/1.3.5/linux-amd64",
expected: pkg.Package{
Name: "bun",
Version: "1.3.5",
Type: "binary",
PURL: "pkg:generic/bun@1.3.5",
Locations: locations("bun"),
Metadata: metadata("bun-binary"),
},
},
{
logicalFixture: "haskell-ghc/7.10.3/linux-amd64",
expected: pkg.Package{
@@ -2992,8 +3069,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
Name: "busybox",
Version: "1.35.0",
PURL: "pkg:generic/busybox@1.35.0",
Locations: locations("/bin/["),
Metadata: metadata("busybox-binary", "/bin/[", "/bin/busybox"),
Locations: locations("/bin/busybox"),
Metadata: metadata("busybox-binary", "/bin/busybox"),
},
},
}
+29
View File
@@ -588,11 +588,20 @@ func DefaultClassifiers() []binutils.Classifier {
Class: "deno-binary",
FileGlob: "**/deno",
EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher(
// Deno/2.6.3Deno/
// Deno/1.41.0cli/
`Deno/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)(Deno/|cli/)`,
),
m.FileContentsVersionMatcher(
// Deno/2.6.3
// Deno/1.41.0
`Deno/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
m.FileContentsVersionMatcher(
// cli/tools/standalone.rsdeno-canary/f4bed1081456089559c82441a13c4fb700840cac1.11.3dlwindows
`deno-canary/[0-9a-z]{40}(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
m.FileContentsVersionMatcher(
// deno::tools::standalonedeno-65db94feba9d4d51a09b74629f566dbc90484fbarelease/v1.29.4windows
// cli/tools/standalone.rsdeno-74064c9d8c222b33b2a552ea0af1054f57002a96release/v1.28.3windows
@@ -611,6 +620,20 @@ func DefaultClassifiers() []binutils.Classifier {
PURL: mustPURL("pkg:generic/deno@version"),
CPEs: singleCPE("cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "bun-binary",
FileGlob: "**/bun",
EvidenceMatcher: binutils.MatchAny(
// bun 1.1.0
// Bun v1.0.0
m.FileContentsVersionMatcher(`(?m)[Bb]un v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
// bun-1.1.0
m.FileContentsVersionMatcher(`(?m)bun-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
),
Package: "bun",
PURL: mustPURL("pkg:generic/bun@version"),
CPEs: singleCPE("cpe:2.3:a:oven-sh:bun:*:*:*:*:*:*:*:*"),
},
{
Class: "haskell-ghc-binary",
FileGlob: "**/ghc*",
@@ -875,7 +898,13 @@ func DefaultClassifiers() []binutils.Classifier {
{
Class: "gzip-binary",
FileGlob: "**/gzip",
// GNU gzip assembles its version banner at runtime from argv[0], so the version is stored as a
// bare NUL-delimited token with nothing to anchor against. Require a gzip-specific string
// elsewhere in the file before trusting that token, otherwise any binary reachable by a path
// named "gzip" (such as the busybox multicall binary, which provides a gzip applet by symlink)
// is reported as GNU gzip with an arbitrary version.
EvidenceMatcher: m.FileContentsVersionMatcher(
`not in gzip format|bug-gzip@gnu\.org|GZIP environment variable`,
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
),
Package: "gzip",
Binary file not shown.
@@ -0,0 +1,12 @@
name: bun
offset: 1000000
length: 100
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000001
fileSha256: 0000000000000000000000000000000000000000000000000000000000000001
### byte snippet to follow ###
]
Bun v0.5.9
bun-0.5.9
JavaScript runtime for developing, testing, and bundling
@@ -0,0 +1,12 @@
name: bun
offset: 1000000
length: 100
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000002
fileSha256: 0000000000000000000000000000000000000000000000000000000000000002
### byte snippet to follow ###
]
Bun v1.0.0
bun-1.0.0
JavaScript runtime for developing, testing, and bundling
@@ -0,0 +1,12 @@
name: bun
offset: 1000000
length: 100
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000003
fileSha256: 0000000000000000000000000000000000000000000000000000000000000003
### byte snippet to follow ###
]
Bun v1.1.0
bun-1.1.0
JavaScript runtime for developing, testing, and bundling
@@ -0,0 +1,12 @@
name: bun
offset: 1000000
length: 100
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000004
fileSha256: 0000000000000000000000000000000000000000000000000000000000000004
### byte snippet to follow ###
]
Bun v1.3.5
bun-1.3.5
JavaScript runtime for developing, testing, and bundling
@@ -0,0 +1,9 @@
name: deno
offset: 4221667
length: 120
snippetSha256: 89509d15b73fd2d901b20ebfe82c67fd27f70e7b01dcbf376e7e80e6c80f97d9
fileSha256: 530a95c93ddfa41bd4d72d665e677d5af9a4275f1520e638305e60af5083a6d6
### byte snippet to follow ###
g ctrl+d or close()
Error: Uncaught cli/tools/standalone.rsdeno-canary/f4bed1081456089559c82441a13c4fb700840cac1.11.3dlw
@@ -0,0 +1,9 @@
name: deno
offset: 4225763
length: 120
snippetSha256: b47e8cf36bdf93e4bc63a2d7bd0a5d759975f12751a66908661777eba7e90025
fileSha256: 1a55c23bb0b666d1b26d9d16f3a9d27210b6b13421031f7f18ff18abfcb8664f
### byte snippet to follow ###
g ctrl+d or close()
Error: Uncaught cli/tools/standalone.rsdeno-canary/ed16f4332263e81bf00a39ebc2200291b1fcbb8e1.11.4dlw
+24
View File
@@ -140,6 +140,22 @@ from-images:
paths:
- /usr/bin/deno
- name: deno
version: 1.11.3
images:
- ref: denoland/deno:1.11.3@sha256:340d071dc71c8403c75713694db04f59b8cb7f6c7244798cd7447d55f2750596
platform: linux/amd64
paths:
- /usr/bin/deno
- name: deno
version: 1.11.4
images:
- ref: denoland/deno:1.11.4@sha256:8e9a449a50b71c779ad1c10ec79055b20a331e78a3852818fac47bc3e85b49f4
platform: linux/amd64
paths:
- /usr/bin/deno
- name: deno
version: 1.16.4
images:
@@ -188,6 +204,14 @@ from-images:
paths:
- /usr/bin/deno
- name: deno
version: 2.9.2
images:
- ref: denoland/deno:ubuntu-2.9.2@sha256:0d27941f871700ebd95953c36fb1a817135c20f5afe3a645935672dfab72b2e9
platform: linux/amd64
paths:
- /usr/bin/deno
# haskell:7.10.3 detail information is missing.
- name: haskell-ghc
version: 7.10.3
+1 -1
View File
@@ -156,7 +156,7 @@ func parseErlangString(data []byte, i *int) (erlangNode, error) {
}
if c == '\\' {
*i++
if len(data) >= *i {
if *i >= len(data) {
return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
}
c = data[*i]
@@ -96,6 +96,11 @@ func Test_parseErlang(t *testing.T) {
{ foo, bar }
]}`,
},
{
name: "string with an escaped quote",
content: `
{escaped, ["a\"b"]}`,
},
}
for _, test := range tests {
@@ -112,3 +117,14 @@ func Test_parseErlang(t *testing.T) {
})
}
}
func Test_parseErlangString_escapedByte(t *testing.T) {
// a backslash escape mid-string used to always error out, since the bounds
// check at the escape site was inverted (len(data) >= *i is true for
// almost every position, not just an out-of-range one).
data := []byte(`"a\"b"`)
i := 0
got, err := parseErlangString(data, &i)
require.NoError(t, err)
assert.Equal(t, `a"b`, got.String())
}
+5 -2
View File
@@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
// only use go.mod packages NOT found in source analysis
goModPackages := c.createGoModPackages(ctx, resolver, modFile, sourceModules, reader, digests)
c.applyReplaceDirectives(ctx, resolver, modFile, goModPackages, reader, digests)
c.applyReplaceDirectives(ctx, resolver, modFile, sourceModules, goModPackages, reader, digests)
c.applyExcludeDirectives(modFile, goModPackages)
pkgs = c.assembleResults(catalogedModules, goModPackages)
@@ -365,8 +365,11 @@ func (c *goModCataloger) createGoModPackages(ctx context.Context, resolver file.
}
// applyReplaceDirectives processes replace directives from go.mod
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, sourceModules map[string]*packages.Module, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
for _, m := range modFile.Replace {
if sourceModules != nil && sourceModules[m.Old.Path] != nil {
continue
}
lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version)
var finalPath string
if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") {
@@ -0,0 +1,44 @@
package golang
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
stereofile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/directorysource"
)
func Test_parseGoSource_replacedModulesAreNotDuplicated(t *testing.T) {
fixture := filepath.Join("testdata", "go-source-replacements")
s, err := directorysource.NewFromPath(fixture)
require.NoError(t, err)
resolver, err := s.FileResolver(source.AllLayersScope)
require.NoError(t, err)
modPath, err := filepath.Abs(filepath.Join(fixture, "go.mod"))
require.NoError(t, err)
contents, err := os.Open(modPath)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, contents.Close()) })
reader := file.LocationReadCloser{
Location: file.NewVirtualLocationFromDirectory("go.mod", "go.mod", *stereofile.NewFileReference(stereofile.Path(modPath))),
ReadCloser: contents,
}
config := DefaultCatalogerConfig().WithUsePackagesLib(true).WithSearchRemoteLicenses(false)
pkgs, _, err := newGoModCataloger(config).parseGoModFile(pkgtest.Context(t), resolver, nil, reader)
require.NoError(t, err)
versions := make(map[string][]string)
for _, p := range pkgs {
versions[p.Name] = append(versions[p.Name], p.Version)
}
require.Equal(t, []string{"v1.5.2"}, versions["rsc.io/quote"])
require.Equal(t, []string{"v1.3.1"}, versions["rsc.io/sampler"])
}
+95 -14
View File
@@ -16,6 +16,10 @@ import (
// mainPackage is the import path the linker assigns to the binary's main package.
const mainPackage = "main"
// vendorPrefix is the import-path prefix carried by vendored packages (e.g. from `go mod vendor`, or the
// standard library's own vendored dependencies such as "vendor/golang.org/x/net/http2").
const vendorPrefix = "vendor/"
// binarySymbol represents a single function symbol extracted from a go binary's pclntab.
type binarySymbol struct {
// packagePath is the import path of the package that owns the symbol (e.g. "github.com/foo/bar/internal/baz")
@@ -52,10 +56,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
continue
}
seen[fn.Name] = struct{}{}
syms = append(syms, binarySymbol{
packagePath: fn.PackageName(),
name: fn.Name,
})
syms = append(syms, makeBinarySymbol(fn.Name, fn.PackageName()))
}
// debug/gosym only exposes top-level functions; functions that the compiler inlined into their
@@ -71,7 +72,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
continue
}
seen[name] = struct{}{}
syms = append(syms, binarySymbol{packagePath: pkgPath, name: name})
syms = append(syms, makeBinarySymbol(name, pkgPath))
}
return syms, nil
@@ -94,6 +95,55 @@ func packagePathFromSymbolName(name string) string {
return name[:slash+1+dot]
}
// makeBinarySymbol builds a binarySymbol, unescaping the %xx sequences the go linker introduces in the
// import-path portion of a symbol name (see unescapePackagePath). The local-symbol suffix (method and
// function names) is left untouched, so only the path prefix shared by packagePath and name is rewritten.
func makeBinarySymbol(name, pkgPath string) binarySymbol {
unescaped := unescapePackagePath(pkgPath)
if unescaped != pkgPath && strings.HasPrefix(name, pkgPath) {
name = unescaped + name[len(pkgPath):]
}
return binarySymbol{packagePath: unescaped, name: name}
}
// unescapePackagePath reverses the escaping cmd/internal/objabi.PathToPrefix applies to import paths in
// symbol names: bytes like '.' (at or after the final '/'), '%', '"', control bytes, and high bytes are
// written as lowercase "%xx". For example "gopkg.in/yaml.v2" is stored as "gopkg.in/yaml%2ev2", so this
// restores it before matching against the (unescaped) module paths from build info. A lone or malformed
// '%' sequence is left as-is.
func unescapePackagePath(path string) string {
if !strings.Contains(path, "%") {
return path
}
var b strings.Builder
b.Grow(len(path))
for i := 0; i < len(path); i++ {
if path[i] == '%' && i+2 < len(path) {
if hi, ok1 := unhex(path[i+1]); ok1 {
if lo, ok2 := unhex(path[i+2]); ok2 {
b.WriteByte(hi<<4 | lo)
i += 2
continue
}
}
}
b.WriteByte(path[i])
}
return b.String()
}
func unhex(c byte) (byte, bool) {
switch {
case c >= '0' && c <= '9':
return c - '0', true
case c >= 'a' && c <= 'f':
return c - 'a' + 10, true
case c >= 'A' && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}
// nameWithoutTypeArgs strips the type-argument portion from an instantiated generic symbol name, e.g.
// "foo/bar.Do[net/url.Values]" -> "foo/bar.Do". The slashes and dots inside the brackets would otherwise
// corrupt package-path derivation (yielding "foo/bar.Do[net" for the example above). Mirrors
@@ -273,8 +323,13 @@ func readPclntab(r io.ReaderAt) (pclntab []byte, textStart uint64, err error) {
// "(*T).M"). Symbols from the "main" package are attributed to the main module and keyed by the "main"
// import path the linker assigns. Standard-library symbols (which belong to no module) are collected
// separately and returned as the second value, grouped by import path, so they can be attached to the
// synthetic "stdlib" package. Compiler/runtime-internal symbols that are neither module-owned nor a
// recognizable stdlib import path are dropped.
// synthetic "stdlib" package. Vendored packages carry a "vendor/" import-path prefix: such symbols match
// both modules whose own path carries the prefix and modules without it (matched with the prefix trimmed),
// and the prefix is retained in the group key only when the owning module itself is named "vendor/...".
// Module-less vendored packages (the stdlib's own vendored dependencies, e.g.
// "vendor/golang.org/x/net/http2") are dropped: stdlib vulnerabilities seem to be reported against the public
// packages (e.g. "crypto/x509"), not the vendored internal copies. Compiler/runtime-internal symbols that
// are neither module-owned nor a recognizable stdlib import path are likewise dropped.
func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Module) (byModule map[string]map[string][]string, stdlib map[string][]string) {
if len(symbols) == 0 {
return nil, nil
@@ -302,16 +357,17 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
attrPath = main.Path
}
var best string
for _, modPath := range modulePaths {
if len(modPath) > len(best) && (attrPath == modPath || strings.HasPrefix(attrPath, modPath+"/")) {
best = modPath
}
best := findBestMatch(modulePaths, attrPath)
// the vendor/ prefix is only retained when the owning module itself is named "vendor/...";
// in all other cases (non-vendored modules and vendored stdlib) the recorded import path is trimmed
if !strings.HasPrefix(best, vendorPrefix) {
importPath = strings.TrimPrefix(importPath, vendorPrefix)
}
local := localSymbolName(sym.name, importPath)
if best == "" {
if importPath != mainPackage && isStandardImportPath(importPath) {
if importPath != mainPackage && isStandardImportPath(importPath) { // drop stdlib vendored packages
stdlib[importPath] = append(stdlib[importPath], local)
}
continue
@@ -333,11 +389,36 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
return results, stdlib
}
// findBestMatch returns the module path that owns the given package path taking into account vendor/ prefixes: a vendor/ import path
// will take precedence and continue to match, non-vendored imports will match against their vendored equivalent
func findBestMatch(modulePaths []string, importPath string) string {
trimmedPath, trimmed := strings.CutPrefix(importPath, vendorPrefix)
candidatePaths := []string{importPath, trimmedPath}
if !trimmed {
candidatePaths = candidatePaths[:1]
}
var best string
for _, candidate := range candidatePaths {
for _, modPath := range modulePaths {
// the prefix must end at a path-segment boundary in candidate, so that e.g. the module
// "github.com/foo/bar" matches the package "github.com/foo/bar/baz" but not "github.com/foo/barbaz"
if len(modPath) > len(best) && strings.HasPrefix(candidate, modPath) && (candidate == modPath || candidate[len(modPath)] == '/') {
best = modPath
}
}
}
return best
}
// localSymbolName strips the owning package's import path prefix from a fully qualified symbol name, e.g.
// "github.com/foo/bar.(*T).M" with import path "github.com/foo/bar" becomes "(*T).M". The name is returned
// unchanged when it does not carry the expected prefix.
func localSymbolName(name, importPath string) string {
if importPath != "" && strings.HasPrefix(name, importPath+".") {
if !strings.HasPrefix(importPath, vendorPrefix) {
name = strings.TrimPrefix(name, vendorPrefix)
}
if len(importPath) < len(name) && strings.HasPrefix(name, importPath) && name[len(importPath)] == '.' {
return name[len(importPath)+1:]
}
return name
+114
View File
@@ -16,6 +16,8 @@ func Test_moduleSymbols(t *testing.T) {
deps := []*debug.Module{
{Path: "github.com/foo/bar"},
{Path: "github.com/foo/bar/v2"},
{Path: "vendor/github.com/vendored/mod"},
{Path: "github.com/vendored/mod"},
nil,
}
@@ -74,6 +76,63 @@ func Test_moduleSymbols(t *testing.T) {
"runtime": {"main"},
},
},
{
name: "vendored packages match non-vendored modules and are recorded under the canonical import path",
symbols: []binarySymbol{
{packagePath: "vendor/github.com/foo/bar", name: "vendor/github.com/foo/bar.Parse"},
{packagePath: "vendor/github.com/foo/bar/internal/util", name: "vendor/github.com/foo/bar/internal/util.(*Helper).Do"},
},
expected: map[string]map[string][]string{
"github.com/foo/bar": {
"github.com/foo/bar": {"Parse"},
"github.com/foo/bar/internal/util": {"(*Helper).Do"},
},
},
},
{
name: "stdlib-vendored packages are dropped",
symbols: []binarySymbol{
{packagePath: "vendor/golang.org/x/net/http2", name: "vendor/golang.org/x/net/http2.(*Framer).ReadFrame"},
},
expected: map[string]map[string][]string{},
},
{
name: "modules whose own path carries the vendor/ prefix match exactly and win over the trimmed match",
symbols: []binarySymbol{
{packagePath: "vendor/github.com/vendored/mod", name: "vendor/github.com/vendored/mod.Run"},
{packagePath: "github.com/vendored/mod", name: "github.com/vendored/mod.Run"},
},
expected: map[string]map[string][]string{
"vendor/github.com/vendored/mod": {
"vendor/github.com/vendored/mod": {"Run"},
},
"github.com/vendored/mod": {
"github.com/vendored/mod": {"Run"},
},
},
},
{
name: "vendored and unvendored symbols for the same package are merged and deduplicated under the canonical import path",
symbols: []binarySymbol{
{packagePath: "github.com/foo/bar", name: "github.com/foo/bar.Parse"},
{packagePath: "vendor/github.com/foo/bar", name: "vendor/github.com/foo/bar.Parse"},
},
expected: map[string]map[string][]string{
"github.com/foo/bar": {
"github.com/foo/bar": {"Parse"},
},
},
},
{
// "github.com/foo/barbaz" shares a string prefix with the "github.com/foo/bar" module but not a
// path-segment boundary, so it must not be attributed to it (and, having a dotted first element,
// it is not a stdlib path either — it is dropped)
name: "module paths only match the package path at a path-segment boundary",
symbols: []binarySymbol{
{packagePath: "github.com/foo/barbaz", name: "github.com/foo/barbaz.Parse"},
},
expected: map[string]map[string][]string{},
},
{
name: "duplicate symbols are deduplicated",
symbols: []binarySymbol{
@@ -97,6 +156,58 @@ func Test_moduleSymbols(t *testing.T) {
}
}
func Test_escapedImportPathAttribution(t *testing.T) {
// the go linker escapes the '.' in the last path element, so "gopkg.in/yaml.v2" appears in the symbol
// table as "gopkg.in/yaml%2ev2". attribution must still land these under the unescaped module path.
mainModule := &debug.Module{Path: "github.com/someorg/somecli"}
deps := []*debug.Module{{Path: "gopkg.in/yaml.v2"}}
symbols := []binarySymbol{
makeBinarySymbol("gopkg.in/yaml%2ev2.(*decoder).alias", "gopkg.in/yaml%2ev2"),
makeBinarySymbol("gopkg.in/yaml%2ev2.(*TypeError).Error", "gopkg.in/yaml%2ev2"),
}
gotByModule, gotStdlib := moduleSymbols(symbols, mainModule, deps)
assert.Nil(t, gotStdlib)
assert.Equal(t, map[string]map[string][]string{
"gopkg.in/yaml.v2": {
"gopkg.in/yaml.v2": {"(*TypeError).Error", "(*decoder).alias"},
},
}, gotByModule)
}
func Test_unescapePackagePath(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"gopkg.in/yaml%2ev2", "gopkg.in/yaml.v2"},
{"foo%25bar", "foo%bar"},
{"foo%22bar", "foo\"bar"},
// no escapes: unchanged
{"github.com/foo/bar", "github.com/foo/bar"},
{"", ""},
// malformed sequences are left as-is
{"foo%2", "foo%2"},
{"foo%zz", "foo%zz"},
{"foo%", "foo%"},
// uppercase hex is accepted too
{"foo%2Ebar", "foo.bar"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
assert.Equal(t, tt.expected, unescapePackagePath(tt.input))
})
}
}
func Test_makeBinarySymbol(t *testing.T) {
// only the import-path prefix is unescaped; the local-symbol suffix is preserved verbatim
sym := makeBinarySymbol("gopkg.in/yaml%2ev2.(*decoder).alias", "gopkg.in/yaml%2ev2")
assert.Equal(t, "gopkg.in/yaml.v2", sym.packagePath)
assert.Equal(t, "gopkg.in/yaml.v2.(*decoder).alias", sym.name)
}
func Test_localSymbolName(t *testing.T) {
tests := []struct {
name string
@@ -239,6 +350,9 @@ func Test_packagePathFromSymbolName(t *testing.T) {
// module paths that begin with "go." are not compiler-generated
{"go.uber.org/zap.(*Logger).Info", "go.uber.org/zap"},
{"go.opentelemetry.io/otel.Tracer", "go.opentelemetry.io/otel"},
// vendored packages retain their "vendor/" import-path prefix in symbol names
{"vendor/golang.org/x/net/http2.(*Framer).ReadFrame", "vendor/golang.org/x/net/http2"},
{"vendor/github.com/foo/bar.Parse", "vendor/github.com/foo/bar"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -0,0 +1,15 @@
module example.com/repro
go 1.23
require rsc.io/quote v1.5.1
require (
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
rsc.io/sampler v1.3.0 // indirect
)
replace (
rsc.io/quote => rsc.io/quote v1.5.2
rsc.io/sampler => rsc.io/sampler v1.3.1
)
@@ -0,0 +1,6 @@
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.1 h1:F0c3J2nQCdk9ODsNhU3sElnvPIxM/xV1c/qZuAeZmac=
rsc.io/sampler v1.3.1/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
@@ -0,0 +1,11 @@
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Hello())
}
@@ -7,6 +7,9 @@
"chainguard.dev/apko": [
"cpe:2.3:a:chainguard:apko:*:*:*:*:*:go:*:*"
],
"github.com/Azure/go-ntlmssp": [
"cpe:2.3:a:microsoft:go-ntlmssp:*:*:*:*:*:go:*:*"
],
"github.com/ClickHouse/ch-go": [
"cpe:2.3:a:clickhouse:ch:*:*:*:*:*:go:*:*"
],
@@ -223,6 +226,9 @@
"github.com/xddxdd/bird-lg-go": [
"cpe:2.3:a:xddxdd:bird-lg-go:*:*:*:*:*:go:*:*"
],
"github.com/xuri/excelize/v2": [
"cpe:2.3:a:excelize:excelize:*:*:*:*:*:go:*:*"
],
"github.com/yuin/goldmark": [
"cpe:2.3:a:yuin:goldmark:*:*:*:*:*:go:*:*"
],
@@ -2327,6 +2333,9 @@
"@ais-ltd/strategyen": [
"cpe:2.3:a:ais-ltd:strategyen:*:*:*:*:*:node.js:*:*"
],
"@amoy/common": [
"cpe:2.3:a:amoyjs:common:*:*:*:*:*:node.js:*:*"
],
"@angular/core": [
"cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:*",
"cpe:2.3:a:angularjs:angularjs:*:*:*:*:*:*:*:*"
@@ -2349,6 +2358,9 @@
"@apollosproject/data-connector-rock": [
"cpe:2.3:a:apollosapp:data-connector-rock:*:*:*:*:*:node.js:*:*"
],
"@appium/base-driver": [
"cpe:2.3:a:appium:appium\\/base-driver:*:*:*:*:*:node.js:*:*"
],
"@appium/support": [
"cpe:2.3:a:appium:appium\\/support:*:*:*:*:*:node.js:*:*"
],
@@ -2403,6 +2415,9 @@
"@better-auth/oauth-provider": [
"cpe:2.3:a:better-auth:better-auth\\/oauth-provider:*:*:*:*:*:node.js:*:*"
],
"@better-auth/sso": [
"cpe:2.3:a:better-auth:better-auth\\/sso:*:*:*:*:*:node.js:*:*"
],
"@braintree/sanitize-url": [
"cpe:2.3:a:paypal:braintree\\/sanitize-url:*:*:*:*:*:node.js:*:*"
],
@@ -2616,6 +2631,9 @@
"@octokit/webhooks": [
"cpe:2.3:a:octokit:webhooks:*:*:*:*:*:node.js:*:*"
],
"@openclaw/feishu": [
"cpe:2.3:a:openclaw:openclaw\\/feishu:*:*:*:*:*:node.js:*:*"
],
"@openclaw/voice-call": [
"cpe:2.3:a:openclaw:openclaw\\/voice-call:*:*:*:*:*:node.js:*:*"
],
@@ -3086,6 +3104,9 @@
"cli": [
"cpe:2.3:a:cli_project:cli:*:*:*:*:*:node.js:*:*"
],
"client-certificate-auth": [
"cpe:2.3:a:tgies:client-certificate-auth:*:*:*:*:*:node.js:*:*"
],
"closure-compiler-stream": [
"cpe:2.3:a:closure-compiler-stream_project:closure-compiler-stream:*:*:*:*:*:node.js:*:*"
],
@@ -4234,6 +4255,9 @@
"json8-merge-patch": [
"cpe:2.3:a:json8-merge-patch_project:json8-merge-patch:*:*:*:*:*:node.js:*:*"
],
"jsonpath": [
"cpe:2.3:a:dchester:jsonpath:*:*:*:*:*:node.js:*:*"
],
"jsonpointer": [
"cpe:2.3:a:janl:jsonpointer:*:*:*:*:*:node.js:*:*"
],
@@ -5752,6 +5776,9 @@
"ssrf-agent": [
"cpe:2.3:a:ssrf-agent_project:ssrf-agent:*:*:*:*:*:node.js:*:*"
],
"ssrfcheck": [
"cpe:2.3:a:felippe-regazio:ssrf_check:*:*:*:*:*:node.js:*:*"
],
"ssri": [
"cpe:2.3:a:ssri_project:ssri:*:*:*:*:*:node.js:*:*"
],
@@ -6819,6 +6846,9 @@
"pyanyapi": [
"cpe:2.3:a:pyanyapi_project:pyanyapi:*:*:*:*:*:*:*:*"
],
"pyasn1": [
"cpe:2.3:a:pyasn1:pyasn1:*:*:*:*:*:python:*:*"
],
"pybluemonday": [
"cpe:2.3:a:python:pybluemonday:*:*:*:*:*:*:*:*"
],
@@ -6834,6 +6864,9 @@
"pydantic-ai": [
"cpe:2.3:a:pydantic:pydantic_ai:*:*:*:*:*:python:*:*"
],
"pydantic-settings": [
"cpe:2.3:a:pydantic:pydantic-settings:*:*:*:*:*:*:*:*"
],
"pydash": [
"cpe:2.3:a:derrickgilland:pydash:*:*:*:*:*:python:*:*"
],
@@ -6978,6 +7011,9 @@
"sqlparse": [
"cpe:2.3:a:sqlparse_project:sqlparse:*:*:*:*:*:python:*:*"
],
"stanza": [
"cpe:2.3:a:stanford:stanza:*:*:*:*:*:python:*:*"
],
"starlette": [
"cpe:2.3:a:encode:starlette:*:*:*:*:*:python:*:*"
],
@@ -7181,6 +7217,9 @@
"encoded_id-rails": [
"cpe:2.3:a:diaconou:encodedid\\:\\:rails:*:*:*:*:*:ruby:*:*"
],
"excon": [
"cpe:2.3:a:excon_project:excon:*:*:*:*:*:ruby:*:*"
],
"faye": [
"cpe:2.3:a:faye_project:faye:*:*:*:*:*:ruby:*:*"
],
@@ -7588,6 +7627,9 @@
"ammonia": [
"cpe:2.3:a:ammonia_project:ammonia:*:*:*:*:*:rust:*:*"
],
"anchor-lang": [
"cpe:2.3:a:anchor-lang:anchor:*:*:*:*:*:rust:*:*"
],
"anymap": [
"cpe:2.3:a:anymap_project:anymap:*:*:*:*:*:rust:*:*"
],
@@ -7690,6 +7732,12 @@
"bzip2": [
"cpe:2.3:a:bzip2_project:bzip2:*:*:*:*:*:rust:*:*"
],
"c2pa": [
"cpe:2.3:a:adobe:c2pa:*:*:*:*:*:rust:*:*"
],
"c2patool": [
"cpe:2.3:a:adobe:c2patool:*:*:*:*:*:*:*:*"
],
"cache": [
"cpe:2.3:a:cache_project:cache:*:*:*:*:*:rust:*:*"
],
@@ -7951,6 +7999,9 @@
"one2html": [
"cpe:2.3:a:msiemens:one2html:*:*:*:*:*:rust:*:*"
],
"openssl": [
"cpe:2.3:a:sfackler:openssl:*:*:*:*:*:rust:*:*"
],
"outer_cgi": [
"cpe:2.3:a:outer_cgi_project:outer_cgi:*:*:*:*:*:rust:*:*"
],
@@ -8530,6 +8581,9 @@
"add-subtitle": [
"cpe:2.3:a:viitorcloud:add_subtitle:*:*:*:*:*:wordpress:*:*"
],
"add-svg-support-for-media-uploader-inventivo": [
"cpe:2.3:a:inventivo:inventivo:*:*:*:*:*:wordpress:*:*"
],
"add-to-any": [
"cpe:2.3:a:addtoany:addtoany_share_buttons:*:*:*:*:*:wordpress:*:*"
],
@@ -8720,6 +8774,9 @@
"advanced-recent-posts": [
"cpe:2.3:a:advanced_recent_posts_project:advanced_recent_posts:*:*:*:*:*:wordpress:*:*"
],
"advanced-schedule-posts": [
"cpe:2.3:a:hijiriworld:advanced_schedule_posts:*:*:*:*:*:wordpress:*:*"
],
"advanced-sermons": [
"cpe:2.3:a:wpcodeus:advanced_sermons:*:*:*:*:*:wordpress:*:*"
],
@@ -8784,6 +8841,7 @@
"cpe:2.3:a:ai_contact_us_form_project:ai_contact_us_form:*:*:*:*:*:wordpress:*:*"
],
"ai-engine": [
"cpe:2.3:a:jordymeow:ai_engine:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:meowapps:ai_engine:*:*:*:*:*:wordpress:*:*"
],
"ai-image-alt-text-generator-for-wp": [
@@ -8843,6 +8901,9 @@
"album-and-image-gallery-plus-lightbox": [
"cpe:2.3:a:essentialplugin:album_and_image_gallery_plus_lightbox:*:*:*:*:free:wordpress:*:*"
],
"alemha-watermark": [
"cpe:2.3:a:mnbaa:alemha_watermark:*:*:*:*:*:wordpress:*:*"
],
"alfred-click-collect": [
"cpe:2.3:a:alfred24_click_\\\u0026_collect_project:alfred24_click_\\\u0026_collect:*:*:*:*:*:wordpress:*:*"
],
@@ -9175,7 +9236,8 @@
"cpe:2.3:a:article_directory_project:article_directory:*:*:*:*:*:wordpress:*:*"
],
"article-directory-redux": [
"cpe:2.3:a:icontrolwp:article_directory_redux:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:icontrolwp:article_directory_redux:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:icontrolwp:icontrolwp:*:*:*:*:*:wordpress:*:*"
],
"article2pdf": [
"cpe:2.3:a:article2pdf_project:article2pdf:*:*:*:*:*:wordpress:*:*"
@@ -9469,6 +9531,9 @@
"barcode-scanner-lite-pos-to-manage-products-inventory-and-orders": [
"cpe:2.3:a:ukrsolution:barcode_scanner_and_inventory_manager:*:*:*:*:*:wordpress:*:*"
],
"base64-encoderdecoder": [
"cpe:2.3:a:mranderson:base64_encoder\\/decoder:*:*:*:*:*:wordpress:*:*"
],
"basic-interactive-world-map": [
"cpe:2.3:a:wpmapplugins:basic_interactive_world_map:*:*:*:*:*:wordpress:*:*"
],
@@ -9923,6 +9988,9 @@
"bsuite": [
"cpe:2.3:a:bsuite_project:bsuite:*:*:*:*:*:wordpress:*:*"
],
"bu-section-editing": [
"cpe:2.3:a:bu:bu_section_editing:*:*:*:*:*:wordpress:*:*"
],
"bubble-menu": [
"cpe:2.3:a:wow-company:bubble_menu:*:*:*:*:free:wordpress:*:*"
],
@@ -10993,6 +11061,9 @@
"cost-calculator-builder": [
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:free:wordpress:*:*"
],
"coub": [
"cpe:2.3:a:chartscss:coub:*:*:*:*:*:wordpress:*:*"
],
"countdown-block": [
"cpe:2.3:a:wpdeveloper:countdown_block:*:*:*:*:*:wordpress:*:*"
],
@@ -11644,6 +11715,9 @@
"donation-thermometer": [
"cpe:2.3:a:donation_thermometer_project:donation_thermometer:*:*:*:*:*:wordpress:*:*"
],
"donations-block": [
"cpe:2.3:a:bharatkambariya:donation_block_for_paypal:*:*:*:*:*:wordpress:*:*"
],
"donations-for-woocommerce": [
"cpe:2.3:a:wpzone:potent_donations_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -11731,6 +11805,9 @@
"dtabs": [
"cpe:2.3:a:dtabs_project:dtabs:*:*:*:*:*:wordpress:*:*"
],
"dts-simple-share": [
"cpe:2.3:a:missionmike:simple_share:*:*:*:*:*:wordpress:*:*"
],
"duitku-social-payment-gateway": [
"cpe:2.3:a:duitku:duitku_payment_gateway:*:*:*:*:*:wordpress:*:*"
],
@@ -12122,9 +12199,12 @@
"cpe:2.3:a:mandsconsulting:email_before_download:*:*:*:*:*:wordpress:*:*"
],
"email-encoder-bundle": [
"cpe:2.3:a:jannisthuemmig:email_encoder:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:onlineoptimisation:email_encoder:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wp-webhooks:email_encoder:*:*:*:*:*:wordpress:*:*"
],
"email-keep": [
"cpe:2.3:a:intricateweb:email_keep:*:*:*:*:*:wordpress:*:*"
],
"email-log": [
"cpe:2.3:a:email_log_project:email_log:*:*:*:*:*:wordpress:*:*"
],
@@ -12716,6 +12796,9 @@
"five-minute-webshop": [
"cpe:2.3:a:five_minute_webshop_project:five_minute_webshop:*:*:*:*:*:wordpress:*:*"
],
"fl3r-accessibility-suite": [
"cpe:2.3:a:fl3r:fl3r_accessibility_suite:*:*:*:*:*:wordpress:*:*"
],
"fl3r-feelbox": [
"cpe:2.3:a:armandofiore:fl3r_feelbox:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:fl3r-feelbox_project:fl3r-feelbox:*:*:*:*:*:wordpress:*:*"
@@ -13104,6 +13187,9 @@
"game-server-status": [
"cpe:2.3:a:game-server-status_project:game-server-status:*:*:*:*:*:wordpress:*:*"
],
"game-users-share-buttons": [
"cpe:2.3:a:gameusers:game_users_share_button:*:*:*:*:*:wordpress:*:*"
],
"gamepress": [
"cpe:2.3:a:gamepress_project:gamepress:*:*:*:*:*:wordpress:*:*"
],
@@ -13113,6 +13199,9 @@
"gamipress-link": [
"cpe:2.3:a:gamipress:gamipress_-_link:*:*:*:*:*:wordpress:*:*"
],
"gamipress-reset-user": [
"cpe:2.3:a:gamipress:gamipress_-_reset_user:*:*:*:*:*:wordpress:*:*"
],
"garden-gnome-package": [
"cpe:2.3:a:ggnome:garden_gnome_package:*:*:*:*:*:wordpress:*:*"
],
@@ -13197,6 +13286,9 @@
"gestpay-for-woocommerce": [
"cpe:2.3:a:fabrick:gestpay_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"get-bookings-wp": [
"cpe:2.3:a:istmoplugins:get_bookings_wp:*:*:*:*:*:wordpress:*:*"
],
"get-custom-field-values": [
"cpe:2.3:a:get_custom_field_values_project:get_custom_field_values:*:*:*:*:*:wordpress:*:*"
],
@@ -13224,9 +13316,21 @@
"gf-block-ips": [
"cpe:2.3:a:brightplugins:block_ips_for_gravity_forms:*:*:*:*:*:wordpress:*:*"
],
"gf-constant-contact": [
"cpe:2.3:a:rocketgenius:constant_contact:*:*:*:*:*:wordpress:*:*"
],
"gf-custom-style": [
"cpe:2.3:a:alefypimentel:gf_custom_style:*:*:*:*:*:wordpress:*:*"
],
"gf-hubspot": [
"cpe:2.3:a:crmperks:wp_gravity_forms_hubspot:*:*:*:*:*:wordpress:*:*"
],
"gf-insightly": [
"cpe:2.3:a:crmperks:wp_gravity_forms_insightly:*:*:*:*:*:wordpress:*:*"
],
"gf-salesforce-crmperks": [
"cpe:2.3:a:crmperks:wp_gravity_forms_salesforce:*:*:*:*:*:wordpress:*:*"
],
"gf-zoho": [
"cpe:2.3:a:crmperks:wp_gravity_forms_zoho_crm_and_bigin:*:*:*:*:*:wordpress:*:*"
],
@@ -13659,6 +13763,9 @@
"heureka": [
"cpe:2.3:a:heureka:heureka:*:*:*:*:*:wordpress:*:*"
],
"hide-links": [
"cpe:2.3:a:avovkdesign:hide_links:*:*:*:*:*:wordpress:*:*"
],
"hide-my-wp": [
"cpe:2.3:a:wpplugins:hide_my_wp_ghost:*:*:*:*:*:wordpress:*:*"
],
@@ -13683,6 +13790,9 @@
"hkdev-maintenance-mode": [
"cpe:2.3:a:helderk:maintenance_mode:*:*:*:*:*:wordpress:*:*"
],
"hmapsprem": [
"cpe:2.3:a:heroplugins:hero_maps_premium:*:*:*:*:*:wordpress:*:*"
],
"hms-testimonials": [
"cpe:2.3:a:hitmyserver:hms_testimonials:*:*:*:*:*:wordpress:*:*"
],
@@ -13831,6 +13941,9 @@
"ibtana-visual-editor": [
"cpe:2.3:a:vowelweb:ibtana:*:*:*:*:*:wordpress:*:*"
],
"ibuildapp": [
"cpe:2.3:a:ibuildapp:ibuildapp:*:*:*:*:*:wordpress:*:*"
],
"icegram": [
"cpe:2.3:a:icegram:icegram_engage:*:*:*:*:*:wordpress:*:*"
],
@@ -14054,6 +14167,9 @@
"cpe:2.3:a:infusionsoft_project:infusionsoft:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:katz:infusionsoft_gravity_forms:*:*:*:*:*:wordpress:*:*"
],
"infusionsoft-official-opt-in-forms": [
"cpe:2.3:a:keap:keap_official_opt_in_forms:*:*:*:*:*:wordpress:*:*"
],
"inline-google-spreadsheet-viewer": [
"cpe:2.3:a:inline_google_spreadsheet_viewer_project:inline_google_spreadsheet_viewer:*:*:*:*:*:wordpress:*:*"
],
@@ -14154,6 +14270,9 @@
"intelly-related-posts": [
"cpe:2.3:a:data443:inline_related_posts:*:*:*:*:*:wordpress:*:*"
],
"interact-quiz-embed": [
"cpe:2.3:a:tryinteract:embed_a_quiz_on_your_site:*:*:*:*:*:wordpress:*:*"
],
"interactive-3d-flipbook-powered-physics-engine": [
"cpe:2.3:a:3dflipbook:3d_flipbook:*:*:*:*:*:wordpress:*:*"
],
@@ -14390,6 +14509,9 @@
"jvm-rich-text-icons": [
"cpe:2.3:a:jorisvm:jvm_gutenberg_rich_text_icons:*:*:*:*:*:wordpress:*:*"
],
"jwp-a11y": [
"cpe:2.3:a:jidaikobo:jwp-a11y:*:*:*:*:*:wordpress:*:*"
],
"kadence-blocks": [
"cpe:2.3:a:kadencewp:gutenberg_blocks_with_ai:*:*:*:*:*:wordpress:*:*"
],
@@ -14879,6 +15001,9 @@
"logo-slider-wp": [
"cpe:2.3:a:logichunt:logo_slider:*:*:*:*:*:wordpress:*:*"
],
"logs-de-connexion": [
"cpe:2.3:a:floriansimunek:connexion_logs:*:*:*:*:*:wordpress:*:*"
],
"logwpmail": [
"cpe:2.3:a:premierethemes:log_wp_mail:*:*:*:*:*:wordpress:*:*"
],
@@ -14951,6 +15076,9 @@
"cpe:2.3:a:custom_popup_builder_project:custom_popup_builder:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:popup_\\|_custom_popup_builder_project:popup_\\|_custom_popup_builder:*:*:*:*:*:wordpress:*:*"
],
"magayo-lottery-results": [
"cpe:2.3:a:magayo:magayo_lottery_results:*:*:*:*:*:wordpress:*:*"
],
"magazine-blocks": [
"cpe:2.3:a:themegrill:magazine_blocks:*:*:*:*:*:wordpress:*:*"
],
@@ -15147,7 +15275,8 @@
"cpe:2.3:a:master-addons:master_addons:*:*:*:*:*:wordpress:*:*"
],
"master-slider": [
"cpe:2.3:a:averta:master_slider:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:averta:master_slider:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:averta:master_slider:*:*:*:*:free:wordpress:*:*"
],
"masterstudy-lms-learning-management-system": [
"cpe:2.3:a:stylemixthemes:masterstudy_lms:*:*:*:*:*:wordpress:*:*"
@@ -15167,6 +15296,9 @@
"materialis-companion": [
"cpe:2.3:a:extendthemes:materialis_companion:*:*:*:*:*:wordpress:*:*"
],
"matomo": [
"cpe:2.3:a:matomo:matomo:*:*:*:*:*:wordpress:*:*"
],
"matrix-pre-loader": [
"cpe:2.3:a:nkb-bd:preloader_matrix:*:*:*:*:*:wordpress:*:*"
],
@@ -15185,6 +15317,9 @@
"mdc-youtube-downloader": [
"cpe:2.3:a:mdc_youtube_downloader_project:mdc_youtube_downloader:*:*:*:*:*:wordpress:*:*"
],
"media-alt-renamer": [
"cpe:2.3:a:jordymeow:media_alt_renamer:*:*:*:*:*:wordpress:*:*"
],
"media-downloader": [
"cpe:2.3:a:media_downloader_project:media_downloader:*:*:*:*:*:wordpress:*:*"
],
@@ -15373,6 +15508,9 @@
"mihdan-index-now": [
"cpe:2.3:a:kobzarev:index_now:*:*:*:*:*:wordpress:*:*"
],
"mini-course-generator": [
"cpe:2.3:a:minicoursegenerator:mini_course_generator:*:*:*:*:*:wordpress:*:*"
],
"mini-mail-dashboard-widget": [
"cpe:2.3:a:mini_mail_dashboard_widget_project:mini_mail_dashboard_widget:*:*:*:*:*:wordpress:*:*"
],
@@ -15406,6 +15544,9 @@
"ml-slider": [
"cpe:2.3:a:metaslider:slider\\,_gallery\\,_and_carousel:*:*:*:*:*:wordpress:*:*"
],
"mm-breaking-news": [
"cpe:2.3:a:mm-breaking_news_project:mm-breaking_news:*:*:*:*:*:wordpress:*:*"
],
"mmm-file-list": [
"cpe:2.3:a:mediamanifesto:mmm_simple_file_list:*:*:*:*:*:wordpress:*:*"
],
@@ -15492,6 +15633,9 @@
"mortgage-calculators-wp": [
"cpe:2.3:a:lenderd:mortgage_calculators_wp:*:*:*:*:*:wordpress:*:*"
],
"mortgage-loan-calculator": [
"cpe:2.3:a:mlcalc:mortgage_loan_calculator:*:*:*:*:*:wordpress:*:*"
],
"most-and-least-read-posts-widget": [
"cpe:2.3:a:whiletrue:most_and_least_read_posts_widget:*:*:*:*:*:wordpress:*:*"
],
@@ -15508,7 +15652,7 @@
"cpe:2.3:a:motor_racing_league_project:motor_racing_league:*:*:*:*:*:wordpress:*:*"
],
"motors-car-dealership-classified-listings": [
"cpe:2.3:a:stylemixthemes:motors_-_car_dealer\\,_classifieds_\\\u0026_listing:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:stylemixthemes:motors_-_car_dealer\\,_classifieds_\\\u0026_listing:*:*:*:*:starter:wordpress:*:*"
],
"mousewheel-smooth-scroll": [
"cpe:2.3:a:mousewheel_smooth_scroll_project:mousewheel_smooth_scroll:*:*:*:*:*:wordpress:*:*"
@@ -15516,6 +15660,9 @@
"move-addons": [
"cpe:2.3:a:moveaddons:move_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
],
"movie-database": [
"cpe:2.3:a:heimkino-praxis:movie_database:*:*:*:*:*:wordpress:*:*"
],
"movies": [
"cpe:2.3:a:movies_project:movies:*:*:*:*:*:wordpress:*:*"
],
@@ -15982,6 +16129,9 @@
"official-sendle-shipping-method": [
"cpe:2.3:a:joovii:sendle_shipping:*:*:*:*:*:wordpress:*:*"
],
"offload-videos-bunny-netaws-s3": [
"cpe:2.3:a:webgarh:offload_videos:*:*:*:*:*:wordpress:*:*"
],
"og-tags": [
"cpe:2.3:a:og_tags_project:og_tags:*:*:*:*:*:wordpress:*:*"
],
@@ -16208,6 +16358,7 @@
"cpe:2.3:a:benhuson:page_parts:*:*:*:*:*:wordpress:*:*"
],
"page-scroll-to-id": [
"cpe:2.3:a:malihu:page_scroll_to_id:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:page_scroll_to_id_project:page_scroll_to_id:*:*:*:*:*:wordpress:*:*"
],
"page-views-count": [
@@ -16641,6 +16792,9 @@
"popup-zyrex": [
"cpe:2.3:a:zyrex:popup:*:*:*:*:*:wordpress:*:*"
],
"popup4phone": [
"cpe:2.3:a:ivanweb:popup4phone:*:*:*:*:*:wordpress:*:*"
],
"popupally": [
"cpe:2.3:a:accessally:popupally:*:*:*:*:*:wordpress:*:*"
],
@@ -16654,6 +16808,9 @@
"portfolio": [
"cpe:2.3:a:bestwebsoft:portfolio:*:*:*:*:*:wordpress:*:*"
],
"portfolio-and-projects": [
"cpe:2.3:a:essentialplugin:portfolio_and_projects:*:*:*:*:*:wordpress:*:*"
],
"portfolio-by-lisa-westlund": [
"cpe:2.3:a:portfolio_project:portfolio:*:*:*:*:*:wordpress:*:*"
],
@@ -17141,7 +17298,10 @@
"cpe:2.3:a:wepupil:quiz_expert_-_easy_quiz_maker\\,_exam_and_test_manager:*:*:*:*:*:wordpress:*:*"
],
"quiz-maker": [
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:agency:wordpress:*:*",
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:business:wordpress:*:*",
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:developer:wordpress:*:*"
],
"quiz-master-next": [
"cpe:2.3:a:expresstech:quiz_and_survey_master:*:*:*:*:*:wordpress:*:*",
@@ -17441,6 +17601,9 @@
"responsive-css-editor": [
"cpe:2.3:a:wpwox:responsive_css_editor:*:*:*:*:*:wordpress:*:*"
],
"responsive-filterable-portfolio": [
"cpe:2.3:a:i13websolution:responsive_filterable_portfolio:*:*:*:*:*:wordpress:*:*"
],
"responsive-food-and-drink-menu": [
"cpe:2.3:a:corporatezen:responsive_food_and_drink_menu:*:*:*:*:*:wordpress:*:*"
],
@@ -17470,6 +17633,12 @@
"responsive-vector-maps": [
"cpe:2.3:a:thinkupthemes:responsive_vector_maps:*:*:*:*:*:wordpress:*:*"
],
"responsive-video-embed": [
"cpe:2.3:a:lepileppanen:responsive_video_embed:*:*:*:*:*:wordpress:*:*"
],
"rest-api-to-miniprogram": [
"cpe:2.3:a:jianbo:rest_api_to_miniprogram:*:*:*:*:*:wordpress:*:*"
],
"restaurant-cafe-addon-for-elementor": [
"cpe:2.3:a:nicheaddons:restaurant_\\\u0026_cafe_addon_for_elementor:*:*:*:*:*:wordpress:*:*"
],
@@ -17655,6 +17824,9 @@
"safe-svg": [
"cpe:2.3:a:10up:safe_svg:*:*:*:*:*:wordpress:*:*"
],
"safetymails-forms": [
"cpe:2.3:a:henriquerodrigues:safetyforms:*:*:*:*:*:wordpress:*:*"
],
"sagepay-server-gateway-for-woocommerce": [
"cpe:2.3:a:patsatech:sagepay_server_gateway_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -17707,6 +17879,9 @@
"schema-app-structured-data-for-schemaorg": [
"cpe:2.3:a:schemaapp:schema_app_structured_data:*:*:*:*:*:wordpress:*:*"
],
"school-management": [
"cpe:2.3:a:dasinfomedia:school_management_system:*:*:*:*:*:wordpress:*:*"
],
"school-management-system": [
"cpe:2.3:a:weblizar:school_management_-_education_\\\u0026_learning_management:*:*:*:*:*:wordpress:*:*"
],
@@ -18091,6 +18266,9 @@
"simple-author-box": [
"cpe:2.3:a:webfactoryltd:simple_author_box:*:*:*:*:*:wordpress:*:*"
],
"simple-backup": [
"cpe:2.3:a:mywebsiteadvisor:simple_backup:*:*:*:*:*:wordpress:*:*"
],
"simple-banner": [
"cpe:2.3:a:simple_banner_project:simple_banner:*:*:*:*:*:wordpress:*:*"
],
@@ -18545,6 +18723,9 @@
"slideshow-se": [
"cpe:2.3:a:slideshow_se_project:slideshow_se:*:*:*:*:*:wordpress:*:*"
],
"slingblocks": [
"cpe:2.3:a:funnelkit:slingblocks:*:*:*:*:*:wordpress:*:*"
],
"small-package-quotes-fedex-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:fedex:wordpress:*:*"
],
@@ -18837,6 +19018,9 @@
"sportspress": [
"cpe:2.3:a:themeboy:sportspress:*:*:*:*:*:wordpress:*:*"
],
"spotbot": [
"cpe:2.3:a:jakehelbig:spotbot:*:*:*:*:*:wordpress:*:*"
],
"spotify-play-button-for-wordpress": [
"cpe:2.3:a:followmedarling:spotify-play-button-for-wordpress:*:*:*:*:*:wordpress:*:*"
],
@@ -18867,6 +19051,9 @@
"srbtranslatin": [
"cpe:2.3:a:srbtranslatin_project:srbtranslatin:*:*:*:*:*:wordpress:*:*"
],
"ssv-events": [
"cpe:2.3:a:moridrin:ssv_events:*:*:*:*:*:wordpress:*:*"
],
"st-daily-tip": [
"cpe:2.3:a:sanskruti:st-daily-tip:*:*:*:*:*:wordpress:*:*"
],
@@ -18981,6 +19168,9 @@
"strategery-migrations": [
"cpe:2.3:a:strategery_migrations_project:strategery_migrations:*:*:*:*:*:wordpress:*:*"
],
"stratum": [
"cpe:2.3:a:motopress:stratum:*:*:*:*:*:wordpress:*:*"
],
"stray-quotes": [
"cpe:2.3:a:unalignedcode:stray_random_quotes:*:*:*:*:*:wordpress:*:*"
],
@@ -19011,6 +19201,9 @@
"subaccounts-for-woocommerce": [
"cpe:2.3:a:mediaticus:subaccounts_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"sublanguage": [
"cpe:2.3:a:maximeschoeni:sublanguage:*:*:*:*:*:wordpress:*:*"
],
"subscribe-sidebar": [
"cpe:2.3:a:blubrry:subscribe_sidebar:*:*:*:*:*:wordpress:*:*"
],
@@ -19447,12 +19640,18 @@
"timetics": [
"cpe:2.3:a:arraytics:wp_timetics:*:*:*:*:*:wordpress:*:*"
],
"timezonecalculator": [
"cpe:2.3:a:bernhard-riedl:timezonecalculator:*:*:*:*:*:wordpress:*:*"
],
"timthumb-vulnerability-scanner": [
"cpe:2.3:a:peterbutler:timthumb_vulnerability_scanner:*:*:*:*:*:wordpress:*:*"
],
"tinychat-roomspy": [
"cpe:2.3:a:tinychat:room_spy:*:*:*:*:*:wordpress:*:*"
],
"tinymce-and-tinymce-advanced-professsional-formats-and-styles": [
"cpe:2.3:a:blackbam:tinymce_and_tinymce_advanced_professsional_formats_and_styles:*:*:*:*:*:wordpress:*:*"
],
"tinymce-custom-styles": [
"cpe:2.3:a:tinymce_custom_styles_project:tinymce_custom_styles:*:*:*:*:*:wordpress:*:*"
],
@@ -19521,6 +19720,9 @@
"tournamatch": [
"cpe:2.3:a:tournamatch:tournamatch:*:*:*:*:*:wordpress:*:*"
],
"tournament-bracket-generator": [
"cpe:2.3:a:blakelong:tournament_bracket_generator:*:*:*:*:*:wordpress:*:*"
],
"tp-education": [
"cpe:2.3:a:themepalace:tp_education:*:*:*:*:*:wordpress:*:*"
],
@@ -19939,6 +20141,9 @@
"user-login-log": [
"cpe:2.3:a:intechnosoftware:user_login_log:*:*:*:*:*:wordpress:*:*"
],
"user-messages": [
"cpe:2.3:a:marvinlabs:user_messages:*:*:*:*:*:wordpress:*:*"
],
"user-meta": [
"cpe:2.3:a:user-meta:user_meta_user_profile_builder_and_user_management:*:*:*:*:*:wordpress:*:*"
],
@@ -19962,6 +20167,9 @@
"user-role": [
"cpe:2.3:a:bestwebsoft:user_role:*:*:*:*:*:wordpress:*:*"
],
"user-shortcodes-plus": [
"cpe:2.3:a:kylebjohnson:user_shortcodes_plus:*:*:*:*:*:wordpress:*:*"
],
"user-spam-remover": [
"cpe:2.3:a:joelhardi:user_spam_remover:*:*:*:*:*:wordpress:*:*"
],
@@ -20282,6 +20490,9 @@
"weaverx-theme-support": [
"cpe:2.3:a:weavertheme:weaver_xtreme_theme_support:*:*:*:*:*:wordpress:*:*"
],
"web-application-firewall": [
"cpe:2.3:a:miniorange:web_application_firewall:*:*:*:*:*:wordpress:*:*"
],
"web-directory-free": [
"cpe:2.3:a:salephpscripts:web_directory_free:*:*:*:*:*:wordpress:*:*"
],
@@ -20574,6 +20785,9 @@
"woo-gutenberg-products-block": [
"cpe:2.3:a:automattic:woocommerce_blocks:*:*:*:*:*:wordpress:*:*"
],
"woo-mailerlite": [
"cpe:2.3:a:mailerlite:mailerlite:*:*:*:*:*:wordpress:*:*"
],
"woo-moneybird": [
"cpe:2.3:a:techastha:integration_of_moneybird_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -20808,6 +21022,9 @@
"wooemailreport": [
"cpe:2.3:a:eggemplo:woocommerce_email_report:*:*:*:*:*:wordpress:*:*"
],
"wooexim": [
"cpe:2.3:a:bulktheme:wooexim:*:*:*:*:*:wordpress:*:*"
],
"wooframework-tweaks": [
"cpe:2.3:a:woocommerce:wooframework_tweaks:*:*:*:*:*:wordpress:*:*"
],
@@ -20923,6 +21140,9 @@
"wordpress-users": [
"cpe:2.3:a:jonathonkemp:wordpress_users:*:*:*:*:*:wordpress:*:*"
],
"work-the-flow-file-upload": [
"cpe:2.3:a:lyntonreed:work_the_flow_file_upload:*:*:*:*:*:wordpress:*:*"
],
"workreap": [
"cpe:2.3:a:amentotech:workreap:*:*:*:*:*:wordpress:*:*"
],
@@ -21154,6 +21374,9 @@
"wp-consultant": [
"cpe:2.3:a:wp_consultant_project:wp_consultant:*:*:-:*:-:wordpress:*:*"
],
"wp-contact-form": [
"cpe:2.3:a:ftwr:wp_contact_form:*:*:*:*:*:wordpress:*:*"
],
"wp-contact-slider": [
"cpe:2.3:a:wpexperts:wp_contact_slider:*:*:*:*:*:wordpress:*:*"
],
@@ -21452,6 +21675,9 @@
"wp-fountain": [
"cpe:2.3:a:wp_fountain_project:wp_fountain:*:*:*:*:*:wordpress:*:*"
],
"wp-front-end-login-and-register": [
"cpe:2.3:a:hellomohsinkhan:wp_front-end_login_and_register:*:*:*:*:*:wordpress:*:*"
],
"wp-front-end-profile": [
"cpe:2.3:a:wp_front_end_profile_project:wp_front_end_profile:*:*:*:*:*:wordpress:*:*"
],
@@ -21612,6 +21838,9 @@
"wp-jobs": [
"cpe:2.3:a:intensewp:wp_jobs:*:*:*:*:*:wordpress:*:*"
],
"wp-jobs2careers": [
"cpe:2.3:a:skipthedrive:wp_talroo:*:*:*:*:*:wordpress:*:*"
],
"wp-jquery-lightbox": [
"cpe:2.3:a:lightpress:lightbox:*:*:*:*:*:wordpress:*:*"
],
@@ -21904,6 +22133,9 @@
"wp-product-gallery-lite": [
"cpe:2.3:a:accesspressthemes:wp_product_gallery_lite:*:*:*:*:*:wordpress:*:*"
],
"wp-programmmanager": [
"cpe:2.3:a:mantus667:wp-pmanager:*:*:*:*:*:wordpress:*:*"
],
"wp-propagator": [
"cpe:2.3:a:widgilabs:plugin_propagator:*:*:*:*:*:wordpress:*:*"
],
@@ -22585,7 +22817,8 @@
"cpe:2.3:a:maennchen1:wpshopgermany_-_protected_shops:*:*:*:*:*:wordpress:*:*"
],
"wpshopify": [
"cpe:2.3:a:shopwp_project:shopwp:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:shopwp_project:shopwp:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpshop:shopwp:*:*:*:*:*:wordpress:*:*"
],
"wpsimpletools-log-viewer": [
"cpe:2.3:a:wpsimpletools:basic_log_viewer:*:*:*:*:*:wordpress:*:*"
@@ -23112,6 +23345,9 @@
"empowerwp": [
"cpe:2.3:a:extendthemes:empowerwp:*:*:*:*:*:wordpress:*:*"
],
"enfold": [
"cpe:2.3:a:kriesi:enfold:*:*:*:*:*:wordpress:*:*"
],
"enlighten": [
"cpe:2.3:a:accesspressthemes:enlighten:*:*:*:*:*:wordpress:*:*"
],
@@ -23356,6 +23592,9 @@
"tijaji": [
"cpe:2.3:a:tijaji:tijaji:*:*:*:*:*:wordpress:*:*"
],
"total": [
"cpe:2.3:a:hashthemes:total:*:*:*:*:*:wordpress:*:*"
],
"transcend": [
"cpe:2.3:a:cpothemes:transcend:*:*:*:*:*:wordpress:*:*"
],
+36 -13
View File
@@ -1,26 +1,49 @@
package cpegenerate
import "github.com/anchore/syft/syft/pkg"
import (
"net/url"
"strings"
"github.com/anchore/syft/syft/pkg"
)
func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet {
vendors := newFieldCandidateSet()
var vendor string
switch m := p.Metadata.(type) {
case pkg.RpmDBEntry:
if m.Vendor != "" {
vendors.add(fieldCandidate{
value: normalizeName(m.Vendor),
disallowSubSelections: true,
})
}
vendor = m.Vendor
case pkg.RpmArchive:
if m.Vendor != "" {
vendors.add(fieldCandidate{
value: normalizeName(m.Vendor),
disallowSubSelections: true,
})
}
vendor = m.Vendor
}
vendor = stripTrailingURL(vendor)
if vendor != "" {
vendors.add(fieldCandidate{
value: normalizeName(vendor),
disallowSubSelections: true,
})
}
return vendors
}
func stripTrailingURL(value string) string {
trimmed := strings.TrimSpace(value)
if !strings.HasSuffix(trimmed, ">") {
return value
}
open := strings.LastIndex(trimmed, "<")
if open == -1 {
return value
}
parsed, err := url.Parse(trimmed[open+1 : len(trimmed)-1])
if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
return value
}
return strings.TrimSpace(trimmed[:open])
}
@@ -0,0 +1,60 @@
package cpegenerate
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
)
func TestCandidateVendorsForRPM(t *testing.T) {
tests := []struct {
name string
metadata any
expected []string
}{
{
name: "database vendor with publisher URL",
metadata: pkg.RpmDBEntry{
Vendor: "SUSE LLC <https://www.suse.com/>",
},
expected: []string{"susellc"},
},
{
name: "archive vendor with publisher URL",
metadata: pkg.RpmArchive{
Vendor: "SUSE LLC <https://www.suse.com/>",
},
expected: []string{"susellc"},
},
{
name: "plain vendor",
metadata: pkg.RpmDBEntry{
Vendor: "Red Hat, Inc.",
},
expected: []string{"redhat"},
},
{
name: "non-URL angle-bracket suffix",
metadata: pkg.RpmDBEntry{
Vendor: "Example <support@example.com>",
},
expected: []string{"example<support@example.com>"},
},
{
name: "non-HTTP URL",
metadata: pkg.RpmDBEntry{
Vendor: "Example <ftp://example.com>",
},
expected: []string{"example<ftp://example.com>"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
p := pkg.Package{Metadata: test.metadata}
assert.ElementsMatch(t, test.expected, candidateVendorsForRPM(p).uniqueValues())
})
}
}
@@ -27,10 +27,11 @@ type packageLock struct {
// lockDependency represents a single package dependency listed in the package.lock json file
type lockDependency struct {
Version string `json:"version"`
Resolved string `json:"resolved"`
Integrity string `json:"integrity"`
Dev bool `json:"dev"`
Version string `json:"version"`
Resolved string `json:"resolved"`
Integrity string `json:"integrity"`
Dev bool `json:"dev"`
Dependencies map[string]lockDependency `json:"dependencies"`
}
type lockPackage struct {
@@ -77,14 +78,7 @@ func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolve
}
if lock.LockfileVersion == 1 {
for name, pkgMeta := range lock.Dependencies {
// skip packages that are only present as a dev dependency
if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
continue
}
pkgs = append(pkgs, newPackageLockV1Package(ctx, a.cfg, resolver, reader.Location, name, pkgMeta))
}
pkgs = append(pkgs, a.packageLockV1Packages(ctx, resolver, reader.Location, lock.Dependencies)...)
}
if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 {
@@ -116,6 +110,32 @@ func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolve
return pkgs, dependency.Resolve(packageLockDependencySpecifier, pkgs), unknown.IfEmptyf(pkgs, "unable to determine packages")
}
func (a genericPackageLockAdapter) packageLockV1Packages(ctx context.Context, resolver file.Resolver, location file.Location, dependencies map[string]lockDependency) []pkg.Package {
var pkgs []pkg.Package
seen := make(map[string]struct{})
var walk func(map[string]lockDependency)
walk = func(dependencies map[string]lockDependency) {
for name, pkgMeta := range dependencies {
// Skipping a dev-only dependency also skips its dev-only subtree.
if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
continue
}
p := newPackageLockV1Package(ctx, a.cfg, resolver, location, name, pkgMeta)
if _, exists := seen[p.PURL]; !exists {
pkgs = append(pkgs, p)
seen[p.PURL] = struct{}{}
}
walk(pkgMeta.Dependencies)
}
}
walk(dependencies)
return pkgs
}
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
// The license field could be either a string or an array.
@@ -111,6 +111,66 @@ func TestParsePackageLock(t *testing.T) {
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
}
func TestParsePackageLockV1NestedDependencies(t *testing.T) {
fixture := "testdata/pkg-lock/nested-package-lock-1.json"
expectedPkgs := []pkg.Package{
{
Name: "duplicate",
Version: "1.0.0",
PURL: "pkg:npm/duplicate@1.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/duplicate/-/duplicate-1.0.0.tgz", Integrity: "sha512-duplicate"},
},
{
Name: "middle",
Version: "1.0.0",
PURL: "pkg:npm/middle@1.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/middle/-/middle-1.0.0.tgz", Integrity: "sha512-middle"},
},
{
Name: "parent",
Version: "1.0.0",
PURL: "pkg:npm/parent@1.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/parent/-/parent-1.0.0.tgz", Integrity: "sha512-parent"},
},
{
Name: "shared",
Version: "0.5.0",
PURL: "pkg:npm/shared@0.5.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/shared/-/shared-0.5.0.tgz", Integrity: "sha512-shared-0.5"},
},
{
Name: "shared",
Version: "1.0.0",
PURL: "pkg:npm/shared@1.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/shared/-/shared-1.0.0.tgz", Integrity: "sha512-shared-1"},
},
{
Name: "shared",
Version: "2.0.0",
PURL: "pkg:npm/shared@2.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/shared/-/shared-2.0.0.tgz", Integrity: "sha512-shared-2"},
},
}
for i := range expectedPkgs {
expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
}
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, nil)
}
func TestParsePackageLockV2(t *testing.T) {
ctx := context.TODO()
fixture := "testdata/pkg-lock/package-lock-2.json"
@@ -0,0 +1,54 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"parent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parent/-/parent-1.0.0.tgz",
"integrity": "sha512-parent",
"dependencies": {
"shared": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shared/-/shared-1.0.0.tgz",
"integrity": "sha512-shared-1"
},
"middle": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/middle/-/middle-1.0.0.tgz",
"integrity": "sha512-middle",
"dependencies": {
"shared": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/shared/-/shared-0.5.0.tgz",
"integrity": "sha512-shared-0.5"
}
}
},
"duplicate": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/duplicate/-/duplicate-1.0.0.tgz",
"integrity": "sha512-duplicate"
}
}
},
"shared": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shared/-/shared-2.0.0.tgz",
"integrity": "sha512-shared-2"
},
"duplicate": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/duplicate/-/duplicate-1.0.0.tgz",
"integrity": "sha512-duplicate"
},
"dev-parent": {
"version": "1.0.0",
"dev": true,
"dependencies": {
"dev-nested": {
"version": "1.0.0"
}
}
}
}
}
+13 -1
View File
@@ -14,7 +14,7 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc
Name: m.Name,
Version: m.Version,
Locations: file.NewLocationSet(locations...),
PURL: packageURL(m.Name, m.Version),
PURL: cargoLockPackageURL(m),
Language: pkg.Rust,
Type: pkg.RustPkg,
Metadata: m,
@@ -25,6 +25,18 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc
return p
}
func cargoLockPackageURL(m pkg.RustCargoLockEntry) string {
// Cargo omits source for packages resolved from the local workspace or a
// path dependency. A cargo PURL without a repository qualifier identifies
// a package on crates.io, so emitting one for a local package can cause
// downstream scanners to match unrelated registry vulnerabilities.
if m.Source == "" {
return ""
}
return packageURL(m.Name, m.Version)
}
func newPackageFromAudit(dep *rustaudit.Package, locations ...file.Location) pkg.Package {
p := pkg.Package{
Name: dep.Name,
+33
View File
@@ -4,6 +4,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
)
func Test_packageURL(t *testing.T) {
@@ -31,3 +33,34 @@ func Test_packageURL(t *testing.T) {
})
}
}
func TestNewPackageFromCargoMetadataPURL(t *testing.T) {
tests := []struct {
name string
source string
want string
}{
{
name: "crates.io package",
source: "registry+https://github.com/rust-lang/crates.io-index",
want: "pkg:cargo/telemetry@0.1.0",
},
{
name: "local path package",
source: "",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := newPackageFromCargoMetadata(pkg.RustCargoLockEntry{
Name: "telemetry",
Version: "0.1.0",
Source: tt.source,
})
assert.Equal(t, tt.want, got.PURL)
})
}
}
@@ -351,7 +351,7 @@ func TestCargoLockWithGitDependencies(t *testing.T) {
helloWorld := pkg.Package{
Name: "hello_world",
Version: "0.1.0",
PURL: "pkg:cargo/hello_world@0.1.0",
PURL: "",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
@@ -673,6 +673,77 @@ func TestCargoLockWithGitDependencies(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
}
func TestCargoLockWithPathDependencies(t *testing.T) {
fixture := "testdata/path-deps/Cargo.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
app := pkg.Package{
Name: "app",
Version: "1.0.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "app",
Version: "1.0.0",
Dependencies: []string{
"memchr",
"telemetry",
},
},
}
memchr := pkg.Package{
Name: "memchr",
Version: "2.7.4",
PURL: "pkg:cargo/memchr@2.7.4",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "memchr",
Version: "2.7.4",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3",
Dependencies: []string{},
},
}
telemetry := pkg.Package{
Name: "telemetry",
Version: "0.1.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "telemetry",
Version: "0.1.0",
Dependencies: []string{},
},
}
expectedPkgs := []pkg.Package{
app,
memchr,
telemetry,
}
expectedRelationships := []artifact.Relationship{
{
From: memchr,
To: app,
Type: artifact.DependencyOfRelationship,
},
{
From: telemetry,
To: app,
Type: artifact.DependencyOfRelationship,
},
}
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
}
func TestCargoLockDependencySpecification(t *testing.T) {
tests := []struct {
name string
+21
View File
@@ -0,0 +1,21 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "app"
version = "1.0.0"
dependencies = [
"memchr",
"telemetry",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "telemetry"
version = "0.1.0"
+10 -5
View File
@@ -1,6 +1,7 @@
package snapsource
import (
"errors"
"fmt"
"github.com/goccy/go-yaml"
@@ -21,6 +22,11 @@ type snapManifest struct {
const manifestLocation = "/meta/snap.yaml"
// errNoManifest means the payload is a readable squashfs that simply does not carry a snap manifest.
// This is distinct from a manifest that is present but unusable, and callers are expected to treat
// the two differently.
var errNoManifest = errors.New("no snap manifest file found")
func parseManifest(resolver file.Resolver) (*snapManifest, error) {
locations, err := resolver.FilesByPath(manifestLocation)
if err != nil {
@@ -28,7 +34,7 @@ func parseManifest(resolver file.Resolver) (*snapManifest, error) {
}
if len(locations) == 0 {
return nil, fmt.Errorf("no snap manifest file found")
return nil, errNoManifest
}
if len(locations) > 1 {
@@ -48,9 +54,8 @@ func parseManifest(resolver file.Resolver) (*snapManifest, error) {
return nil, fmt.Errorf("unable to decode snap manifest file: %w", err)
}
if manifest.Name == "" || manifest.Version == "" {
return nil, fmt.Errorf("invalid snap manifest file: missing name or version")
}
// note: name and version are deliberately not validated here. a manifest that decoded but is
// missing them still carries usable fields (summary, base, grade, ...) and throwing it away would
// describe less than we know. the caller surfaces the missing identity.
return &manifest, nil
}
+16 -5
View File
@@ -77,22 +77,33 @@ func getRemoteSnapFile(ctx context.Context, fs afero.Fs, getter intFile.Getter,
return newSnapFileFromRemote(ctx, fs, cfg, getter, info)
}
func newSnapFileFromRemote(ctx context.Context, fs afero.Fs, cfg Config, getter intFile.Getter, info *remoteSnap) (*snapFile, error) {
func newSnapFileFromRemote(ctx context.Context, fs afero.Fs, cfg Config, getter intFile.Getter, info *remoteSnap) (_ *snapFile, err error) {
t, err := afero.TempDir(fs, "", "syft-snap-")
if err != nil {
return nil, fmt.Errorf("failed to create temp directory: %w", err)
}
closer := func() error {
return fs.RemoveAll(t)
}
// any failure past this point must not leave the temp directory (or a partially downloaded snap)
// behind. On success the caller owns the directory by way of snapFile.Cleanup.
defer func() {
if err == nil {
return
}
if closeErr := closer(); closeErr != nil {
log.WithFields("error", closeErr, "directory", t).Warn("unable to remove temp directory for snap")
}
}()
snapFilePath := path.Join(t, path.Base(info.URL))
err = downloadSnap(getter, info, snapFilePath)
if err != nil {
return nil, fmt.Errorf("failed to download snap file: %w", err)
}
closer := func() error {
return fs.RemoveAll(t)
}
mimeType, digests, err := getSnapFileInfo(ctx, fs, snapFilePath, cfg.DigestAlgorithms)
if err != nil {
return nil, err
+46 -9
View File
@@ -3,6 +3,7 @@ package snapsource
import (
"context"
"crypto"
"errors"
"fmt"
"io"
"os"
@@ -119,7 +120,21 @@ func newFromPath(cfg Config, f *snapFile) (source.Source, error) {
closer: f.Cleanup,
}
return s, s.extractManifest()
if err := s.extractManifest(); err != nil {
// It is tried on any path ending in .snap or .squashfs, so declining one is routine and
// a later provider usually succeeds. When the user asked for --from snap there is no
// fallback and this error reaches them intact.
log.WithFields("error", err, "path", f.Path).Debug("unable to read snap")
// never hand back a source alongside an error: callers that discard the source on error would
// leave behind the open squashfs file and the temp directory holding a downloaded snap
if closeErr := s.Close(); closeErr != nil {
log.WithFields("error", closeErr, "path", f.Path).Warn("unable to clean up snap source")
}
return nil, err
}
return s, nil
}
func (s *snapSource) extractManifest() error {
@@ -128,13 +143,26 @@ func (s *snapSource) extractManifest() error {
return fmt.Errorf("unable to create snap file resolver: %w", err)
}
// a manifest problem must never fail the source. this source is the only provider that can descend
// into squashfs (filesource cannot), so returning an error here would fall through to a provider
// that reports zero packages for a payload that has plenty. describe what we can instead.
manifest, err := parseManifest(r)
if err != nil {
return fmt.Errorf("unable to parse snap manifest file: %w", err)
switch {
case errors.Is(err, errNoManifest):
// not every squashfs payload is a snap, and one without a manifest is still worth cataloging
log.WithFields("path", s.squashfsPath).Debug("no snap manifest file found")
return nil
case err != nil:
// the manifest is there but unusable, which is worth surfacing: the SBOM will have no source
// name or version to show for it
log.WithFields("error", err, "path", s.squashfsPath).Warn("unable to parse snap manifest file")
return nil
}
if manifest != nil {
s.manifest = *manifest
s.manifest = *manifest
if s.manifest.Name == "" || s.manifest.Version == "" {
log.WithFields("path", s.squashfsPath).Warn("snap manifest is missing name or version")
}
return nil
}
@@ -176,25 +204,34 @@ func (s snapSource) Describe() source.Description {
}
}
// Close releases everything the source holds. Every step runs even if an earlier one fails, since
// bailing early would skip the temp directory removal and leave a downloaded snap on disk. Safe to
// call more than once.
func (s *snapSource) Close() error {
s.mutex.Lock()
defer s.mutex.Unlock()
var errs error
if s.squashFileCloser != nil {
if err := s.squashFileCloser(); err != nil {
return fmt.Errorf("unable to close snap resolver: %w", err)
errs = errors.Join(errs, fmt.Errorf("unable to close snap resolver: %w", err))
}
s.squashFileCloser = nil
}
s.resolver = nil
if s.fs != nil {
if err := s.fs.Close(); err != nil {
return fmt.Errorf("unable to close snap squashfs: %w", err)
errs = errors.Join(errs, fmt.Errorf("unable to close snap squashfs: %w", err))
}
s.fs = nil
}
if s.closer != nil {
if err := s.closer(); err != nil {
return fmt.Errorf("unable to close snap source: %w", err)
errs = errors.Join(errs, fmt.Errorf("unable to close snap source: %w", err))
}
s.closer = nil
}
return nil
return errs
}
func (s *snapSource) FileResolver(_ source.Scope) (file.Resolver, error) {
+210
View File
@@ -2,15 +2,23 @@ package snapsource
import (
"crypto"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"testing"
diskFile "github.com/diskfs/go-diskfs/backend/file"
"github.com/diskfs/go-diskfs/filesystem/squashfs"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/go-homedir"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/syft/source"
)
func TestNewFromLocal(t *testing.T) {
@@ -84,3 +92,205 @@ func TestNewFromLocal(t *testing.T) {
})
}
}
func TestNewFromPathOnResolverError(t *testing.T) {
tests := []struct {
name string
// remote snaps carry a cleanup closure for the temp directory holding the download; local snaps
// have none, since the file belongs to the user
remote bool
wantCleanupRuns int
wantFileRemoved bool
}{
{
name: "downloaded snap is cleaned up",
remote: true,
wantCleanupRuns: 1,
wantFileRemoved: true,
},
{
name: "local snap file is left in place",
remote: false,
wantCleanupRuns: 0,
wantFileRemoved: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// the resolver reads the squashfs from the real filesystem, so the payload must live on disk
tempDir := t.TempDir()
snapPath := filepath.Join(tempDir, "not-really-a-snap.snap")
require.NoError(t, os.WriteFile(snapPath, []byte("not squashfs"), 0600))
var cleanupRuns int
f := &snapFile{
Path: snapPath,
MimeType: "text/plain",
}
if tt.remote {
f.Cleanup = func() error {
cleanupRuns++
return os.RemoveAll(tempDir)
}
}
got, err := newFromPath(Config{Request: snapPath}, f)
require.Error(t, err)
assert.Contains(t, err.Error(), "unable to create snap file resolver")
// a source returned alongside an error is discarded by callers without being closed, which
// would leave the downloaded snap behind
assert.Nil(t, got, "expected nil source on error")
assert.Equal(t, tt.wantCleanupRuns, cleanupRuns)
_, statErr := os.Stat(snapPath)
if tt.wantFileRemoved {
assert.True(t, os.IsNotExist(statErr), "snap file was not cleaned up")
} else {
assert.NoError(t, statErr, "the user's snap file must never be removed")
}
})
}
}
// a manifest problem must never fail the source: snapsource is the only provider that can descend
// into squashfs, so an error here falls through to filesource and every package inside the payload
// silently disappears from the SBOM.
func TestNewFromPathToleratesManifestProblems(t *testing.T) {
tests := []struct {
name string
manifest string // written to /meta/snap.yaml; empty means no manifest at all
wantName string
wantVersion string
wantSummary string
}{
{
name: "no manifest at all",
manifest: "",
},
{
name: "manifest is not valid yaml",
manifest: "name: etcd\n\tversion: 3.4\n",
},
{
name: "manifest is missing name and version",
manifest: "summary: a thing\nbase: core22\n",
wantSummary: "a thing",
},
{
name: "manifest is complete",
manifest: "name: etcd\nversion: 3.4.0\nsummary: a thing\n",
wantName: "etcd",
wantVersion: "3.4.0",
wantSummary: "a thing",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
snapPath := writeSquashfs(t, tt.manifest)
src, err := newFromPath(Config{Request: snapPath}, &snapFile{Path: snapPath})
require.NoError(t, err, "a manifest problem must not fail the source")
require.NotNil(t, src)
t.Cleanup(func() { _ = src.Close() })
desc := src.Describe()
assert.Equal(t, tt.wantName, desc.Name)
assert.Equal(t, tt.wantVersion, desc.Version)
meta, ok := desc.Metadata.(source.SnapMetadata)
require.True(t, ok, "expected snap metadata")
assert.Equal(t, tt.wantSummary, meta.Summary, "fields that did decode must survive")
// the whole point: the payload is still walkable, so cataloging sees what is inside
r, err := src.FileResolver(source.SquashedScope)
require.NoError(t, err)
locations, err := r.FilesByPath("/payload.txt")
require.NoError(t, err)
assert.Len(t, locations, 1, "contents must remain catalogable regardless of the manifest")
})
}
}
func TestSnapSourceClose(t *testing.T) {
newSource := func(closeErr, cleanupErr error) (*snapSource, *int) {
var cleanupRuns int
return &snapSource{
mutex: &sync.Mutex{},
squashFileCloser: func() error {
return closeErr
},
closer: func() error {
cleanupRuns++
return cleanupErr
},
}, &cleanupRuns
}
t.Run("temp directory is removed even when closing the file handle fails", func(t *testing.T) {
s, cleanupRuns := newSource(errors.New("handle is busy"), nil)
err := s.Close()
// the whole point: a failure here used to return early and skip the cleanup below, leaving a
// downloaded snap on disk
require.Error(t, err)
assert.Contains(t, err.Error(), "handle is busy")
assert.Equal(t, 1, *cleanupRuns, "temp directory removal was skipped")
})
t.Run("every failure is reported, not just the first", func(t *testing.T) {
s, _ := newSource(errors.New("handle is busy"), errors.New("directory is busy"))
err := s.Close()
require.Error(t, err)
assert.Contains(t, err.Error(), "handle is busy")
assert.Contains(t, err.Error(), "directory is busy")
})
t.Run("is safe to call twice", func(t *testing.T) {
s, cleanupRuns := newSource(nil, nil)
require.NoError(t, s.Close())
require.NoError(t, s.Close())
assert.Equal(t, 1, *cleanupRuns, "cleanup must not run again on a second close")
})
}
// writeSquashfs builds a real squashfs image containing /payload.txt and, when manifest is non-empty,
// /meta/snap.yaml. Returns the path to the image.
func writeSquashfs(t *testing.T, manifest string) string {
t.Helper()
snapPath := filepath.Join(t.TempDir(), "test.snap")
f, err := os.Create(snapPath)
require.NoError(t, err)
defer func() { require.NoError(t, f.Close()) }()
fs, err := squashfs.Create(diskFile.New(f, false), 0, 0, 4096)
require.NoError(t, err)
write := func(path, contents string) {
t.Helper()
w, err := fs.OpenFile(path, os.O_CREATE|os.O_RDWR)
require.NoError(t, err)
_, err = w.Write([]byte(contents))
require.NoError(t, err)
}
// note: go-diskfs wants paths relative to the image root, without a leading slash
require.NoError(t, fs.Mkdir("."))
write("payload.txt", "hello")
if manifest != "" {
require.NoError(t, fs.Mkdir("meta"))
write(strings.TrimPrefix(manifestLocation, "/"), manifest)
}
require.NoError(t, fs.Finalize(squashfs.FinalizeOptions{}))
return snapPath
}
+83
View File
@@ -264,6 +264,10 @@ func TestNewSnapFileFromRemote(t *testing.T) {
_, err = fs.Stat(result.Path)
assert.True(t, os.IsNotExist(err))
// cleanup owns the whole temp directory, not just the snap file within it
_, err = fs.Stat(filepath.Dir(result.Path))
assert.True(t, os.IsNotExist(err), "temp directory outlived the cleanup closure")
},
},
{
@@ -315,6 +319,75 @@ func TestNewSnapFileFromRemote(t *testing.T) {
expectError: true,
errorMsg: "failed to download snap file",
},
{
name: "download writes partial data then fails",
cfg: Config{
DigestAlgorithms: []crypto.Hash{crypto.SHA256},
},
info: &remoteSnap{
snapIdentity: snapIdentity{
Name: "partial-snap",
Channel: "stable",
Architecture: "amd64",
},
URL: "https://api.snapcraft.io/download/partial_snap.snap",
},
setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) {
mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/partial_snap.snap", mock.Anything).Run(func(args mock.Arguments) {
// simulate a transfer that writes some bytes before the connection drops
require.NoError(t, createMockSquashfsFile(fs, args.String(0)))
}).Return(fmt.Errorf("connection reset by peer"))
},
expectError: true,
errorMsg: "failed to download snap file",
},
{
// isSquashFSFile trusts the .snap extension, so non-squashfs content under that name is accepted
name: "downloaded file has a snap extension but non-snap content",
cfg: Config{
DigestAlgorithms: []crypto.Hash{crypto.SHA256},
},
info: &remoteSnap{
snapIdentity: snapIdentity{
Name: "not-a-snap",
Channel: "stable",
Architecture: "amd64",
},
URL: "https://api.snapcraft.io/download/not_a_snap.snap",
},
setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) {
mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/not_a_snap.snap", mock.Anything).Run(func(args mock.Arguments) {
require.NoError(t, afero.WriteFile(fs, args.String(0), []byte("not squashfs"), 0644))
}).Return(nil)
},
expectError: false,
validate: func(t *testing.T, result *snapFile, fs afero.Fs) {
assert.NotNil(t, result)
assert.Contains(t, result.Path, "not_a_snap.snap")
assert.NotNil(t, result.Cleanup)
},
},
{
name: "downloaded file is not a snap",
cfg: Config{
DigestAlgorithms: []crypto.Hash{crypto.SHA256},
},
info: &remoteSnap{
snapIdentity: snapIdentity{
Name: "not-a-snap",
Channel: "stable",
Architecture: "amd64",
},
URL: "https://api.snapcraft.io/download/not_a_snap.tar.gz",
},
setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) {
mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/not_a_snap.tar.gz", mock.Anything).Run(func(args mock.Arguments) {
require.NoError(t, afero.WriteFile(fs, args.String(0), []byte("not squashfs"), 0644))
}).Return(nil)
},
expectError: true,
errorMsg: "not a valid squashfs/snap file",
},
}
for _, tt := range tests {
@@ -334,8 +407,18 @@ func TestNewSnapFileFromRemote(t *testing.T) {
assert.Contains(t, err.Error(), tt.errorMsg)
}
assert.Nil(t, result)
// the temp directory and any partially downloaded payload must not be left behind. The
// download destination handed to the getter lives directly under the temp directory.
require.Len(t, mockGetter.Calls, 1)
tempDir := filepath.Dir(mockGetter.Calls[0].Arguments.String(0))
_, statErr := fs.Stat(tempDir)
assert.True(t, os.IsNotExist(statErr), "temp directory %q was not cleaned up after failure", tempDir)
} else {
require.NoError(t, err)
if result.Cleanup != nil {
t.Cleanup(func() { _ = result.Cleanup() })
}
if tt.validate != nil {
tt.validate(t, result, fs)
}