Compare commits

...
52 Commits
Author SHA1 Message Date
Keith Zantow 10f0631710 fix: provide separate nonroot image (#3998)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-06-11 17:00:55 -04:00
Alex Goodman 96c34ffc43 account for non-import shapes (#3997)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-06-11 13:11:40 -04:00
Alex Goodman 79b6d5daa4 Allow decoding of anchorectl json files (#3973)
* allow decoding of import sbom file shape

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

* address formatting

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

* add file mode and type processing

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

* use type to interpret the raw value

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

* safe mode convert should use uint32

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

* simpler decoder type

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

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-06-10 15:03:50 -04:00
dependabot[bot] cfa7cc5be9 chore(deps): bump github.com/anchore/stereoscope (#3991)
Bumps [github.com/anchore/stereoscope](https://github.com/anchore/stereoscope) from 0.1.5-0.20250604132324-344e29f37f05 to 0.1.5.
- [Release notes](https://github.com/anchore/stereoscope/releases)
- [Changelog](https://github.com/anchore/stereoscope/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/stereoscope/commits/v0.1.5)

---
updated-dependencies:
- dependency-name: github.com/anchore/stereoscope
  dependency-version: 0.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-10 11:47:40 -04:00
Alex Goodman 18f9b5ab58 remove benchmark utils (#3982)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-06-09 18:24:49 +00:00
Keith Zantow 9090c69708 fix: exclude packages with SPDX GENERATED_FROM source package indication (#3981)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-06-09 14:12:23 -04:00
dependabot[bot] 1396a14550 chore(deps): bump modernc.org/sqlite from 1.37.1 to 1.38.0 (#3979)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.37.1 to 1.38.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.37.1...v1.38.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.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>
2025-06-09 10:31:10 -04:00
dependabot[bot] 592bc0af7d chore(deps): bump github.com/go-git/go-git/v5 from 5.16.1 to 5.16.2 (#3978)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.16.1 to 5.16.2.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.16.1...v5.16.2)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.16.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>
2025-06-09 10:26:47 -04:00
anchore-actions-token-generator[bot]andspiffcs b6b8a8f52e chore(deps): update tools to latest versions (#3977)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-06-09 08:56:36 -04:00
anchore-actions-token-generator[bot]andwagoodman a196cc9215 chore(deps): update CPE dictionary index (#3976)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-06-09 08:56:18 -04:00
dependabot[bot] 12c8003317 chore(deps): bump golang.org/x/net from 0.40.0 to 0.41.0 (#3970)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-06 10:23:02 -04:00
dependabot[bot] 0a25c0ec5c chore(deps): bump github.com/sergi/go-diff (#3971)
Bumps [github.com/sergi/go-diff](https://github.com/sergi/go-diff) from 1.3.2-0.20230802210424-5b0b94c5c0d3 to 1.4.0.
- [Commits](https://github.com/sergi/go-diff/commits/v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/sergi/go-diff
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-06 10:22:54 -04:00
Christoph Blessing 5ae11bd1f7 Fix Python package dependency detection (#3965)
Previously a dependency relationship between two Python packages was not
detected if there were no parentheses around the version specifier in
the wheel metadata of the parent package. This commit allows detection
of such relationships.

Signed-off-by: Christoph Blessing <chris24.blessing@gmail.com>
2025-06-06 09:46:16 -04:00
John Vandenberg bc1cbde4f7 fix: Remove three Rust crate false positive CPE matches (#3967)
Signed-off-by: John Vandenberg <jayvdb@gmail.com>
2025-06-06 04:29:06 -04:00
Michael BrileyandAlex Goodman 868a6a7584 Harden Container Runtime with Non-Root User (#3941)
* Update Dockerfile

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update Dockerfile

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update validations.yaml

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update validations.yaml

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update Dockerfile

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update validations.yaml

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update Dockerfile

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update .goreleaser.yaml

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* Update .goreleaser.yaml

Signed-off-by: Michael Briley <michael.briley937@gmail.com>

* use distroless/static-debian12:nonroot directly

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

* keep manual manifest curation

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

* remove qemu usage

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

* add smoke test for snapshot

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

* split up manifests section with comments

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

* correct ci step name

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

* fix arch condition

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

* keep path prefix

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

---------

Signed-off-by: Michael Briley <michael.briley937@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-06-05 12:36:23 -04:00
John Vandenberg bd894b9c4d fix: Remove two Rust crate false positive CPE matches (#3962)
Rust crates opentelemetry and redis are being given CPEs that
match CVEs such as CVE-2023-45142 and CVE-2022-24735 respectively.
The vendor overrides added here prevent that.

Signed-off-by: John Vandenberg <jayvdb@gmail.com>
2025-06-05 10:28:54 -04:00
dependabot[bot] c36c69779a chore(deps): bump golang.org/x/mod from 0.24.0 to 0.25.0 (#3963)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/mod/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  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>
2025-06-05 13:46:58 +00:00
dependabot[bot] cd23ccc6e6 chore(deps): bump github.com/gkampitakis/go-snaps from 0.5.12 to 0.5.13 (#3964)
Bumps [github.com/gkampitakis/go-snaps](https://github.com/gkampitakis/go-snaps) from 0.5.12 to 0.5.13.
- [Release notes](https://github.com/gkampitakis/go-snaps/releases)
- [Commits](https://github.com/gkampitakis/go-snaps/compare/v0.5.12...v0.5.13)

---
updated-dependencies:
- dependency-name: github.com/gkampitakis/go-snaps
  dependency-version: 0.5.13
  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>
2025-06-05 13:45:40 +00:00
Keith Zantow 71d84603c1 fix: bump stereoscope to fix symlink performance issue (#3953)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-06-04 15:50:03 +00:00
dependabot[bot] f2118b568d chore(deps): bump github.com/go-git/go-git/v5 from 5.16.0 to 5.16.1 (#3960)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.16.0 to 5.16.1.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.16.0...v5.16.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.16.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>
2025-06-04 14:02:45 +00:00
dependabot[bot] bb50f1650d chore(deps): bump github/codeql-action from 3.28.18 to 3.28.19 (#3952)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff0a06e83cb2de871e5a09832bc6a81e7276941f...fca7ace96b7d713c7035871441bd52efbe39e27e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.19
  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>
2025-06-03 10:20:24 -04:00
Christopher Angelo Phillips a0be514184 feat: add syft schema version to version command (#3949)
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-06-02 15:50:21 +00:00
dependabot[bot] 8cc808f8f6 chore(deps): bump github.com/gkampitakis/go-snaps from 0.5.11 to 0.5.12 (#3943)
Bumps [github.com/gkampitakis/go-snaps](https://github.com/gkampitakis/go-snaps) from 0.5.11 to 0.5.12.
- [Release notes](https://github.com/gkampitakis/go-snaps/releases)
- [Commits](https://github.com/gkampitakis/go-snaps/compare/v0.5.11...v0.5.12)

---
updated-dependencies:
- dependency-name: github.com/gkampitakis/go-snaps
  dependency-version: 0.5.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 10:46:30 -04:00
anchore-actions-token-generator[bot]andspiffcs 967e2f85bf chore(deps): update tools to latest versions (#3945)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-06-02 10:46:20 -04:00
anchore-actions-token-generator[bot]andwagoodman 339fea9851 chore(deps): update CPE dictionary index (#3947)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-06-02 10:43:31 -04:00
Keith Zantow 576e729c84 fix: revert incorrect graalvm unknown behavior (#3944)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-05-30 11:36:57 -04:00
dependabot[bot] b3e8926025 chore(deps): bump github.com/google/go-containerregistry (#3933)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.20.4 to 0.20.5.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.20.4...v0.20.5)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.20.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-28 12:45:51 -04:00
anchore-actions-token-generator[bot]andwagoodman 002ec4510a chore(deps): update CPE dictionary index (#3935)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-05-28 12:45:42 -04:00
Thomas Gosteli 684e1e963d fix(terraform): parse provider lock entries without constraints (#3934)
In a .terraform.lock.hcl file in a provider block the `constraints` attribute is actually not required (=optional).

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>
2025-05-27 14:55:19 -04:00
Dan LuhringandKeith Zantow bbf3bb5856 fix(relationship): favor real paths over symlinks for ownership by file (#3923)
Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2025-05-23 14:33:19 -04:00
dependabot[bot] 31c1be6d4d chore(deps): bump modernc.org/sqlite from 1.37.0 to 1.37.1 (#3926)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.37.0 to 1.37.1.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.37.0...v1.37.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.37.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>
2025-05-22 10:47:26 -04:00
Keith Zantow 7bfb4c86a6 fix(dotnet-deps-cataloger): avoid repeated dependency resolution (#3930)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-05-21 21:28:40 -04:00
anchore-actions-token-generator[bot]andspiffcs 18ed8b60f8 chore(deps): update tools to latest versions (#3921)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-05-21 10:56:17 -04:00
dependabot[bot] b5e9f75ef1 chore(deps): bump github.com/google/go-containerregistry (#3925)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.20.3 to 0.20.4.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.20.3...v0.20.4)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.20.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>
2025-05-21 10:55:48 -04:00
Alex Goodman ac883f52ed add cdx group as purl namespace (#3922)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-20 15:56:08 -04:00
Alex Goodman e23ca43a83 add PE binary cataloger (#3911)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-19 14:17:09 -04:00
Christopher Angelo Phillips b4ca04001c chore: update dockerfile base images to latest rolling tags (#3915)
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-19 13:43:14 +00:00
anchore-actions-token-generator[bot]andwagoodman 828645ec27 chore(deps): update CPE dictionary index (#3913)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-05-19 09:16:25 -04:00
Alex Goodman db77b54c01 finalize go mod ref (#3908)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 17:36:26 +00:00
Alex Goodman 2d4fe513ec remove benchmark workflow (#3906)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 11:08:43 -04:00
Christopher Angelo Phillips e1374f758e fix: update license content filtering default case to be 'none' for no content returned
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-16 14:25:15 +00:00
dependabot[bot] 945893847f chore(deps): bump github/codeql-action from 3.28.17 to 3.28.18 (#3905)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.17 to 3.28.18.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/60168efe1c415ce0f5521ea06d5c2062adbeed1b...ff0a06e83cb2de871e5a09832bc6a81e7276941f)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.18
  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>
2025-05-16 14:16:11 +00:00
sathiya06andAlex Goodman 8cbdd38a63 fix: Make Native Image contains no embedded SBOM Error Discoverable (#3805)
* fix: Make Native Image contains no embedded SBOM Error Discoverable

Signed-off-by: Sathiya Narayanan Venkatesan <sathiyavenkat06@gmail.com>

* adjust error phrasing + wrap error

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

---------

Signed-off-by: Sathiya Narayanan Venkatesan <sathiyavenkat06@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 13:54:40 +00:00
Adam McClenaghanandAlex Goodman 8f02bd85f6 fix: Distinguish openjdk vs jdk when using file source (#3895)
* fix: Distinguish openjdk vs jdk when using file source

Signed-off-by: adammcclenaghan <adam@mcclenaghan.co.uk>

* fix: Fix goimport order

Signed-off-by: adammcclenaghan <adam@mcclenaghan.co.uk>

* add comment

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

---------

Signed-off-by: adammcclenaghan <adam@mcclenaghan.co.uk>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 13:29:53 +00:00
Alex Goodman 0480b516f6 chore: fix publishing test fixture images (#3896)
* update pkg counts

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

* bump max cache size

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

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-15 14:35:11 -04:00
Christopher Angelo Phillips 2a055690e6 chore: delete unused fixture (#3901)
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-15 17:30:36 +00:00
Christopher Angelo Phillips 4f73d35051 Include default config licenses (#3900)
* fix: the licenses config was not being carried through causing content to show by default
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-15 16:48:18 +00:00
Ralph BeanandAlex Goodman b369b02f4f Expose RPM signature information (for RPM DB and RPM archives) (#3179)
* feat: expose rpm signature information

This helps with more confident identification of an rpm.

In theory, two rpms can be built that have the same purl string, and
otherwise look identical in syft's output, but the PGP information
would distinguish them as signed either by different keys, or signed at
different times.

In practice, this usually makes no difference since rpms tend to have
unique name/version/release strings. This just gives increased
confidence about the identity of the rpm found in the db.

Signed-off-by: Ralph Bean <rbean@redhat.com>

* chore: generate json schema

Signed-off-by: Ralph Bean <rbean@redhat.com>

* re-generate json schema

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

* rename to a more generic signature field

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

* rename rpm.pgp to rpm.signatures

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

* split out signature fields

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

* bump json schema

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

* include RPM archives

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

* update json schema

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

* dont fail on unknown signature type

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

---------

Signed-off-by: Ralph Bean <rbean@redhat.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-15 16:01:00 +00:00
dependabot[bot] 5effed06a8 chore(deps): bump github.com/mholt/archives from 0.1.1 to 0.1.2 (#3898)
Bumps [github.com/mholt/archives](https://github.com/mholt/archives) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/mholt/archives/releases)
- [Commits](https://github.com/mholt/archives/compare/v0.1.1...v0.1.2)

---
updated-dependencies:
- dependency-name: github.com/mholt/archives
  dependency-version: 0.1.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>
2025-05-15 10:23:30 -04:00
dependabot[bot] 5e25d52845 chore(deps): bump anchore/sbom-action from 0.19.0 to 0.20.0 (#3899)
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/9f7302141466aa6482940f15371237e9d9f4c34a...e11c554f704a0b820cbf8c51673f6945e0731532)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-version: 0.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 10:23:20 -04:00
Laurent GoderreandAlex Goodman a8e5b25632 Add PHP interpreter + extensions cataloger (#2585)
* Add PHP extensions binary classifiers

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>

* [wip] add php extensions cataloger

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

* fix linting

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

* find interpreters + extension

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

* internalize binary cataloger utilities

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

* default to linux/amd64 for test fixtures

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

---------

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-15 08:22:50 -04:00
Alex Goodman 0521ccaf5e chore: update fixtures based on CI builds (#3894)
* update fixtures based on ci builds

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

* fix typo

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

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-14 21:30:20 +00:00
108 changed files with 10471 additions and 1527 deletions
+4 -17
View File
@@ -34,7 +34,7 @@ tools:
# used for showing the changelog at release
- name: glow
version:
want: v2.1.0
want: v2.1.1
method: github-release
with:
repo: charmbracelet/glow
@@ -58,7 +58,7 @@ tools:
# used to release all artifacts
- name: goreleaser
version:
want: v2.9.0
want: v2.10.2
method: github-release
with:
repo: goreleaser/goreleaser
@@ -87,23 +87,10 @@ tools:
with:
repo: wagoodman/go-bouncer
# used for showing benchmark testing
- name: benchstat
version:
want: latest
method: go-proxy
with:
module: golang.org/x/perf
allow-unresolved-version: true
method: go-install
with:
entrypoint: cmd/benchstat
module: golang.org/x/perf
# used for running all local and CI tasks
- name: task
version:
want: v3.43.3
want: v3.44.0
method: github-release
with:
repo: go-task/task
@@ -111,7 +98,7 @@ tools:
# used for triggering a release
- name: gh
version:
want: v2.72.0
want: v2.74.0
method: github-release
with:
repo: cli/cli
-62
View File
@@ -1,62 +0,0 @@
name: "Benchmark testing"
on:
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
Benchmark-Test:
name: "Benchmark tests"
runs-on: ubuntu-24.04
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
# the job by event.
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore base benchmark result
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
with:
path: test/results/benchmark-main.txt
# use base sha for PR or new commit hash for main push in benchmark result key
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Run benchmark tests
id: benchmark
run: |
REF_NAME=${GITHUB_REF##*/} make benchmark
OUTPUT=$(make show-benchstat)
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: benchmark-test-results
path: test/results/**/*
- name: Update PR benchmark results comment
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db #v2.9.2
continue-on-error: true
with:
header: benchmark
message: |
### Benchmark Test Results
<details>
<summary>Benchmark results from the latest changes vs base branch</summary>
```
${{ steps.benchmark.outputs.result }}
```
</details>
+3 -3
View File
@@ -45,7 +45,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,4 +70,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
+1 -1
View File
@@ -148,7 +148,7 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@9f7302141466aa6482940f15371237e9d9f4c34a #v0.19.0
- uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 #v0.20.0
continue-on-error: true
with:
file: go.mod
+3
View File
@@ -76,6 +76,9 @@ jobs:
- name: Build snapshot artifacts
run: make snapshot
- name: Smoke test snapshot build
run: make snapshot-smoke-test
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts
+187 -119
View File
@@ -1,11 +1,11 @@
version: 2
project_name: syft
release:
prerelease: auto
draft: false
env:
# required to support multi architecture docker builds
- DOCKER_CLI_EXPERIMENTAL=enabled
- CGO_ENABLED=0
@@ -13,14 +13,8 @@ builds:
- id: linux-build
dir: ./cmd/syft
binary: syft
goos:
- linux
goarch:
- amd64
- arm64
- ppc64le
- s390x
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
goos: [linux]
goarch: [amd64, arm64, ppc64le, s390x]
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
ldflags: &build-ldflags |
-w
@@ -34,11 +28,8 @@ builds:
- id: darwin-build
dir: ./cmd/syft
binary: syft
goos:
- darwin
goarch:
- amd64
- arm64
goos: [darwin]
goarch: [amd64, arm64]
mod_timestamp: *build-timestamp
ldflags: *build-ldflags
hooks:
@@ -50,115 +41,42 @@ builds:
- id: windows-build
dir: ./cmd/syft
binary: syft
goos:
- windows
goarch:
- amd64
goos: [windows]
goarch: [amd64]
mod_timestamp: *build-timestamp
ldflags: *build-ldflags
archives:
- id: linux-archives
builds:
- linux-build
# note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted
builds: [linux-build]
- id: darwin-archives
builds:
- darwin-build
builds: [darwin-build]
- id: windows-archives
format: zip
builds:
- windows-build
builds: [windows-build]
nfpms:
- license: "Apache 2.0"
maintainer: "Anchore, Inc"
homepage: &website "https://github.com/anchore/syft"
description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
formats:
- rpm
- deb
formats: [rpm, deb]
brews:
- repository:
owner: anchore
name: homebrew-syft
token: "{{.Env.GITHUB_BREW_TOKEN}}"
ids:
- darwin-archives
- linux-archives
ids: [darwin-archives, linux-archives]
homepage: *website
description: *description
license: "Apache License 2.0"
dockers:
# production images...
- image_templates:
- anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:debug
- ghcr.io/anchore/syft:{{.Tag}}-debug
goarch: amd64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-arm64v8
- anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
goarch: arm64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-ppc64le
- anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-s390x
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft:debug-s390x
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
goarch: s390x
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:latest
- anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:latest
- ghcr.io/anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-amd64
goarch: amd64
dockerfile: Dockerfile
use: buildx
@@ -208,51 +126,201 @@ dockers:
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
# nonroot images...
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
goarch: amd64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
goarch: arm64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-s390x
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
goarch: s390x
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
# debug images...
- image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
goarch: amd64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
goarch: arm64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
goarch: s390x
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-amd64
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}-amd64
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
# nonroot images...
- name_template: anchore/syft:nonroot
image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:nonroot
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: anchore/syft:{{.Tag}}-nonroot
image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
# debug images...
- name_template: anchore/syft:debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
- name_template: anchore/syft:{{.Tag}}-debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
sboms:
- artifacts: archive
# this is relative to the snapshot/dist directory, not the root of the repo
cmd: ../.tool/syft
documents:
- "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
+1 -1
View File
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian12:debug@sha256:5c474275684bbf271ec40502ab50158b2f9826de5877d8feec27e22e8d6ee3d2 AS build
FROM gcr.io/distroless/static-debian12:latest AS build
FROM scratch
# needed for version check HTTPS request
+3 -1
View File
@@ -1,10 +1,12 @@
FROM gcr.io/distroless/static-debian12:debug@sha256:5c474275684bbf271ec40502ab50158b2f9826de5877d8feec27e22e8d6ee3d2
FROM gcr.io/distroless/static-debian12:debug-nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
USER nonroot
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
+27
View File
@@ -0,0 +1,27 @@
FROM gcr.io/distroless/static-debian12:nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
USER nonroot
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
ARG VCS_URL
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.title="syft"
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
LABEL org.opencontainers.image.source=$VCS_URL
LABEL org.opencontainers.image.revision=$VCS_REF
LABEL org.opencontainers.image.vendor="Anchore, Inc."
LABEL org.opencontainers.image.version=$BUILD_VERSION
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ENTRYPOINT ["/syft"]
+18 -29
View File
@@ -27,10 +27,12 @@ vars:
sh: uname -s | tr '[:upper:]' '[:lower:]'
ARCH:
sh: |
[ "$(uname -m)" = "x86_64" ] && echo "amd64_v1" || { [ "$(uname -m)" = "aarch64" ] && echo "arm64_v8.0" || echo $(uname -m); }
[ "$(uname -m)" = "x86_64" ] && echo "amd64_v1" || { [ "$(uname -m)" = "aarch64" ] && echo "arm64_v8.0" || [ "$(uname -m)" = "arm64" ] && echo "arm64_v8.0" || echo $(uname -m); }
PROJECT_ROOT:
sh: echo $PWD
# note: the snapshot dir must be a relative path starting with ./
# e.g. when installing snapshot debs from a local path, ./ forces the deb to be installed in the current working directory instead of referencing a package name
SNAPSHOT_DIR: ./snapshot
SNAPSHOT_BIN: "{{ .PROJECT_ROOT }}/{{ .SNAPSHOT_DIR }}/{{ .OS }}-build_{{ .OS }}_{{ .ARCH }}/{{ .PROJECT }}"
SNAPSHOT_CMD: "{{ .TOOL_DIR }}/goreleaser release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign"
@@ -75,7 +77,6 @@ tasks:
- task: unit
- task: integration
- task: validate-cyclonedx-schema
- task: benchmark
- task: test-utils
- task: cli
- task: check-docker-cache
@@ -231,10 +232,7 @@ tasks:
cli:
desc: Run CLI tests
# note: we don't want to regenerate the snapshot unless we have to. In CI it's probable
# that the cache being restored with the correct binary will be rebuilt since the timestamps
# and local checksums will not line up.
deps: [tools, snapshot]
deps: [tools]
cmds:
- cmd: "echo 'testing binary: {{ .SNAPSHOT_BIN }}'"
silent: true
@@ -251,27 +249,6 @@ tasks:
- cmd: .github/scripts/labeler_test.py
## Benchmark test targets #################################
benchmark:
deps: [tmpdir]
generates:
- "{{ .TMP_DIR }}/benchmark-main.txt"
cmds:
- "go test -p 1 -run=^Benchmark -bench=. -count=7 -benchmem ./... | tee {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt"
- |
bash -c "(test -s {{ .TMP_DIR }}/benchmark-main.txt && \
{{ .TOOL_DIR }}/benchstat {{ .TMP_DIR }}/benchmark-main.txt {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt || \
{{ .TOOL_DIR }}/benchstat {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt) \
| tee {{ .TMP_DIR }}/benchstat.txt"
show-benchstat:
deps: [benchmark, tmpdir]
cmds:
- cmd: "cat {{ .TMP_DIR }}/benchstat.txt"
silent: true
## Test-fixture-related targets #################################
fingerprints:
@@ -453,8 +430,8 @@ tasks:
find . | grep cache | grep tar | xargs du
echo "total $total_size KB"
if [ "$total_size" -gt 1048576 ]; then
echo 'docker cache is larger than 1GB'
if [ "$total_size" -gt 5242880 ]; then
echo 'docker cache is larger than 5GB'
exit 1
fi
@@ -574,6 +551,18 @@ tasks:
- "{{ .SNAPSHOT_CMD }}"
snapshot-smoke-test:
desc: Run a smoke test on the snapshot builds + docker images
cmds:
- cmd: "echo 'testing snapshot binary: {{ .SNAPSHOT_BIN }}'"
silent: true
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
silent: true
- "{{ .SNAPSHOT_BIN }} version"
- "{{ .SNAPSHOT_BIN }} scan alpine:latest"
- docker run --rm anchore/syft:latest version
- docker run --rm anchore/syft:latest scan alpine:latest
changelog:
desc: Generate a changelog
deps: [tools]
+6 -1
View File
@@ -10,6 +10,7 @@ import (
"github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/internal"
"github.com/anchore/syft/cmd/syft/internal/commands"
constants "github.com/anchore/syft/internal"
)
// Application constructs the `syft packages` command and aliases the root command to `syft packages`.
@@ -47,7 +48,7 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
commands.Cataloger(app),
commands.Attest(app),
commands.Convert(app),
clio.VersionCommand(id),
clio.VersionCommand(id, schemaVersion),
clio.ConfigCommand(app, nil),
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
)
@@ -59,3 +60,7 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
return app, rootCmd
}
func schemaVersion() (string, any) {
return "SchemaVersion", constants.JSONSchemaVersion
}
+6 -38
View File
@@ -4,18 +4,13 @@ import (
"fmt"
"github.com/anchore/clio"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/cataloging"
)
type licenseConfig struct {
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
// Deprecated: please use include-license-content instead
IncludeUnknownLicenseContent *bool `yaml:"-" json:"-" mapstructure:"include-unknown-license-content"`
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
// Deprecated: please use coverage instead
LicenseCoverage *float64 `yaml:"license-coverage" json:"license-coverage" mapstructure:"license-coverage"`
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
AvailableLicenseContent []cataloging.LicenseContent `yaml:"-" json:"-" mapstructure:"-"`
}
@@ -25,43 +20,16 @@ var _ interface {
func (o *licenseConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Content, fmt.Sprintf("include the content of licenses in the SBOM for a given syft scan; valid values are: %s", o.AvailableLicenseContent))
descriptions.Add(&o.IncludeUnknownLicenseContent, `deprecated: please use 'license-content' instead`)
descriptions.Add(&o.Coverage, `adjust the percent as a fraction of the total text, in normalized words, that
matches any valid license for the given inputs, expressed as a percentage across all of the licenses matched.`)
descriptions.Add(&o.LicenseCoverage, `deprecated: please use 'coverage' instead`)
}
func (o *licenseConfig) PostLoad() error {
cfg := cataloging.DefaultLicenseConfig()
defaultContent := cfg.IncludeContent
defaultCoverage := cfg.Coverage
// if both legacy and new fields are specified, error out
if o.IncludeUnknownLicenseContent != nil && o.Content != defaultContent {
return fmt.Errorf("both 'include-unknown-license-content' and 'content' are set, please use only 'content'")
validContent := internal.NewSet(o.AvailableLicenseContent...)
if !validContent.Contains(o.Content) {
return fmt.Errorf("could not use %q as license content option; valid values are: %v", o.Content, validContent.ToSlice())
}
if o.LicenseCoverage != nil && o.Coverage != defaultCoverage {
return fmt.Errorf("both 'license-coverage' and 'coverage' are set, please use only 'coverage'")
}
// finalize the license content value
if o.IncludeUnknownLicenseContent != nil {
// convert 'include-unknown-license-content' -> 'license-content'
v := cataloging.LicenseContentExcludeAll
if *o.IncludeUnknownLicenseContent {
v = cataloging.LicenseContentIncludeUnknown
}
o.Content = v
}
// finalize the coverage value
if o.LicenseCoverage != nil {
// convert 'license-coverage' -> 'coverage'
o.Coverage = *o.LicenseCoverage
}
return nil
}
@@ -19,7 +19,7 @@ func TestPackageDeduplication(t *testing.T) {
}{
{
scope: source.AllLayersScope,
packageCount: 178, // without deduplication this would be ~600
packageCount: 175, // without deduplication this would be ~600
instanceCount: map[string]int{
"basesystem": 1,
"wget": 1,
@@ -30,15 +30,15 @@ func TestPackageDeduplication(t *testing.T) {
locationCount: map[string]int{
"basesystem-11-13.el9": 5, // in all layers
"curl-minimal-7.76.1-26.el9_3.2.0.1": 2, // base + wget layer
"curl-minimal-7.76.1-29.el9_4.1": 3, // curl upgrade layer + all above layers
"curl-minimal-7.76.1-31.el9": 3, // curl upgrade layer + all above layers
"wget-1.21.1-8.el9_4": 4, // wget + all above layers
"vsftpd-3.0.5-5.el9": 2, // vsftpd + all above layers
"httpd-2.4.57-11.el9_4.1": 1, // last layer
"vsftpd-3.0.5-6.el9": 2, // vsftpd + all above layers
"httpd-2.4.62-1.el9_5.2": 1, // last layer
},
},
{
scope: source.SquashedScope,
packageCount: 172,
packageCount: 169,
instanceCount: map[string]int{
"basesystem": 1,
"wget": 1,
@@ -47,11 +47,11 @@ func TestPackageDeduplication(t *testing.T) {
"httpd": 1, // rpm, binary is now excluded by overlap
},
locationCount: map[string]int{
"basesystem-11-13.el9": 1,
"curl-minimal-7.76.1-29.el9_4.1": 1, // upgrade
"wget-1.21.1-8.el9_4": 1,
"vsftpd-3.0.5-5.el9": 1,
"httpd-2.4.57-11.el9_4.1": 1,
"basesystem-11-13.el9": 1,
"curl-minimal-7.76.1-31.el9": 1, // upgrade
"wget-1.21.1-8.el9_4": 1,
"vsftpd-3.0.5-6.el9": 1,
"httpd-2.4.62-1.el9_5.2": 1,
},
},
}
@@ -3,18 +3,18 @@ package integration
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func TestPhotonPackageRegression(t *testing.T) { // Regression: https://github.com/anchore/syft/pull/1997
sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope)
var packages []pkg.Package
for p := range sbom.Artifacts.Packages.Enumerate() {
packages = append(packages, p)
var count int
for range sbom.Artifacts.Packages.Enumerate(pkg.RpmPkg) {
count++
}
if len(packages) < 1 {
t.Errorf("failed to find packages for photon distro; wanted > 0 got 0")
}
assert.Greater(t, count, 0, "expected to find RPM packages in the SBOM (but did not)")
}
+30 -29
View File
@@ -19,11 +19,12 @@ require (
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
github.com/anchore/stereoscope v0.1.4
github.com/anchore/stereoscope v0.1.5
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
@@ -39,14 +40,14 @@ require (
github.com/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.3.3
github.com/gkampitakis/go-snaps v0.5.11
github.com/gkampitakis/go-snaps v0.5.13
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.0
github.com/go-git/go-git/v5 v5.16.2
github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/gohugoio/hashstructure v0.5.0
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.3
github.com/google/go-containerregistry v0.20.5
github.com/google/licensecheck v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.5.4
@@ -57,9 +58,8 @@ require (
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jinzhu/copier v0.4.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/knqyf263/go-rpmdb v0.1.1
github.com/magiconair/properties v1.8.10
github.com/mholt/archives v0.1.1
github.com/mholt/archives v0.1.2
github.com/moby/sys/mountinfo v0.7.2
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
github.com/olekukonko/tablewriter v0.0.5
@@ -72,7 +72,7 @@ require (
github.com/sassoftware/go-rpmutils v0.4.0
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/sergi/go-diff v1.4.0
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb
github.com/spdx/tools-golang v0.5.5
github.com/spf13/afero v1.14.0
@@ -85,11 +85,11 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
go.uber.org/goleak v1.3.0
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
golang.org/x/mod v0.24.0
golang.org/x/net v0.40.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/mod v0.25.0
golang.org/x/net v0.41.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.37.0
modernc.org/sqlite v1.38.0
)
require (
@@ -100,11 +100,11 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
@@ -133,10 +133,10 @@ require (
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v28.1.1+incompatible // indirect
github.com/docker/cli v28.2.2+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.1.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-units v0.5.0 // indirect
@@ -147,13 +147,13 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gkampitakis/ciinfo v0.3.1 // indirect
github.com/gkampitakis/ciinfo v0.3.2 // indirect
github.com/gkampitakis/go-diff v1.3.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/goccy/go-yaml v1.15.13 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v0.0.4 // indirect
@@ -226,7 +226,7 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vbatts/tar-split v0.11.6 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
@@ -235,31 +235,32 @@ require (
github.com/zclconf/go-cty v1.13.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.31.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
google.golang.org/grpc v1.67.3 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.62.1 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.9.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
retract (
v1.25.0 // published with a replace directive (confusing for API users)
v0.53.2
v0.53.1 // Published accidentally with incorrect license in depdencies
v0.53.1 // published accidentally with incorrect license in depdencies
)
+66 -64
View File
@@ -78,8 +78,8 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
@@ -110,6 +110,8 @@ github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw=
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 h1:Ha+LSCVuXYSYGi7wIkJK6G8g6jI3LH7y6LbyEVyp4Io=
@@ -120,11 +122,11 @@ 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.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
github.com/anchore/stereoscope v0.1.4 h1:e+iT9UdUzLBabWGe84hn5sTHDRioY+4IHsVzJXuJlek=
github.com/anchore/stereoscope v0.1.4/go.mod h1:omWgXDEp/XfqCJlZXIByEo1c3ArZg/qTJ5LBKVLAIdw=
github.com/anchore/stereoscope v0.1.5 h1:/wdjSoerdbYtXNvezReNrmzWeBOFJeHt7hLXz6Xg/WE=
github.com/anchore/stereoscope v0.1.5/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -262,14 +264,14 @@ github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4i
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
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/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
@@ -323,12 +325,12 @@ github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFA
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/github/go-spdx/v2 v2.3.3 h1:QI7evnHWEfWkT54eJwkoV/f3a0xD3gLlnVmT5wQG6LE=
github.com/github/go-spdx/v2 v2.3.3/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ=
github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgagapo=
github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY=
github.com/gkampitakis/go-snaps v0.5.13 h1:Hhjmvv1WboSCxkR9iU2mj5PQ8tsz/y8ECGrIbjjPF8Q=
github.com/gkampitakis/go-snaps v0.5.13/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
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=
@@ -339,8 +341,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
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.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -364,8 +366,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg=
github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -426,8 +428,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-containerregistry v0.20.5 h1:4RnlYcDs5hoA++CeFjlbZ/U9Yp1EuWr+UhhTyYQjOP0=
github.com/google/go-containerregistry v0.20.5/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
@@ -548,8 +550,6 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584=
github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -600,8 +600,8 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY=
github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I=
github.com/mholt/archives v0.1.2 h1:UBSe5NfYKHI1sy+S5dJsEsG9jsKKk8NJA4HCC+xTI4A=
github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprUOwZWDs=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -751,8 +751,8 @@ github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEW
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -836,8 +836,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/go-mtree v0.5.4 h1:OMAb8jaCyiFA7zXj0Zc/oARcxBDBoeu2LizjB8BVJl0=
github.com/vbatts/go-mtree v0.5.4/go.mod h1:5GqJbVhm9BBiCc4K5uc/c42FPgXulHaQs4sFUEfIWMo=
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
@@ -885,20 +885,22 @@ 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
@@ -925,8 +927,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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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=
@@ -937,8 +939,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -966,8 +968,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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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=
@@ -1012,8 +1014,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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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=
@@ -1043,8 +1045,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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
@@ -1137,8 +1139,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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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=
@@ -1199,8 +1201,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.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
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=
@@ -1390,26 +1392,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
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.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/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.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
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=
+1 -1
View File
@@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.32"
JSONSchemaVersion = "16.0.34"
)
+63 -6
View File
@@ -92,6 +92,12 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
return relationships
}
// Build a map of real paths to packages that directly own them. We'll use this
// to check if a file is already owned by the same type of package when we're
// determining ownership via symlink.
directOwnership := directOwnersByPath(catalog)
// Now establish relationships, considering symlinks
for _, candidateOwnerPkg := range catalog.Sorted() {
id := candidateOwnerPkg.ID()
if candidateOwnerPkg.Metadata == nil {
@@ -105,16 +111,34 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
}
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
// find the first path that results in a hit
for _, ownedPath := range allPaths(ownedFilePath, resolver) {
if matchesAny(ownedPath, globsForbiddenFromBeingOwned) {
if ownedFilePath == "" {
continue
}
// find paths that result in a hit (includes resolving symlinks)
resolvedPaths := resolvePaths(ownedFilePath, resolver)
for _, resolvedPath := range resolvedPaths {
if matchesAny(resolvedPath, globsForbiddenFromBeingOwned) {
// we skip over known exceptions to file ownership, such as the RPM package owning
// the RPM DB path, otherwise the RPM package would "own" all RPMs, which is not intended
continue
}
// Skip claiming ownership via symlink if another package of the same type
// directly owns this real path. This is the specific fix for the issue where a
// symlink shouldn't allow a package to claim ownership when another package of
// the same type directly owns the real file.
if resolvedPath != ownedFilePath { // This is a resolved symlink path
// Check if another package of the same type directly owns this path
if paths := directOwnership[candidateOwnerPkg.Type]; paths != nil && paths.Has(resolvedPath) {
// Skip this path - a package of the same type directly owns it
continue
}
}
// look for package(s) in the catalog that may be owned by this package and mark the relationship
for _, subPackage := range catalog.PackagesByPath(ownedPath) {
for _, subPackage := range catalog.PackagesByPath(resolvedPath) {
subID := subPackage.ID()
if subID == id {
continue
@@ -126,7 +150,7 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
if _, exists := relationships[id][subID]; !exists {
relationships[id][subID] = strset.New()
}
relationships[id][subID].Add(ownedPath)
relationships[id][subID].Add(resolvedPath)
}
}
}
@@ -135,7 +159,40 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
return relationships
}
func allPaths(ownedFilePath string, resolver file.Resolver) []string {
func directOwnersByPath(catalog *pkg.Collection) map[pkg.Type]*strset.Set {
directOwnership := map[pkg.Type]*strset.Set{}
// First, identify direct ownership of all files
for _, p := range catalog.Sorted() {
if p.Metadata == nil {
continue
}
// check to see if this is a file owner
pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
if !ok {
continue
}
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
if ownedFilePath == "" {
continue
}
// Register direct ownership
paths := directOwnership[p.Type]
if paths == nil {
paths = strset.New()
directOwnership[p.Type] = paths
}
paths.Add(ownedFilePath)
}
}
return directOwnership
}
func resolvePaths(ownedFilePath string, resolver file.Resolver) []string {
// though we have a string path, we need to resolve symlinks and other filesystem oddities since we cannot assume this is a real path
var locs []file.Location
var err error
@@ -78,6 +78,64 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
return []pkg.Package{parent, child}, []artifact.Relationship{relationship}
},
},
{
name: "only-real-file-owner-gets-ownership-relationship-not-symlink-owner",
resolver: mockFR{
translate: map[string]string{
"/usr/bin/jenkins.war-symlink": "/usr/share/jenkins/jenkins.war", // symlink to the real file
},
},
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
// Package that owns the symlink
symlinkOwner := pkg.Package{
Type: pkg.ApkPkg,
Metadata: pkg.ApkDBEntry{
Package: "jenkins-symlink-pkg",
Files: []pkg.ApkFileRecord{
{Path: "/usr/bin/jenkins.war-symlink"}, // this symlinks to the real file
},
},
}
symlinkOwner.SetID()
// Package that owns the real file
realFileOwner := pkg.Package{
Type: pkg.ApkPkg,
Metadata: pkg.ApkDBEntry{
Package: "jenkins-real-pkg",
Files: []pkg.ApkFileRecord{
{Path: "/usr/share/jenkins/jenkins.war"}, // the real file
},
},
}
realFileOwner.SetID()
// A third package that is "located at" the real file path
// This simulates a package being discovered at the jenkins.war location
consumerPackage := pkg.Package{
Type: pkg.JavaPkg,
Locations: file.NewLocationSet(
file.NewVirtualLocation("/usr/share/jenkins/jenkins.war", "/usr/share/jenkins/jenkins.war"),
),
}
consumerPackage.SetID()
// Only the real file owner should have an ownership relationship with the consumer package
// The symlink owner should NOT have a relationship with the consumer
relationship := artifact.Relationship{
From: realFileOwner,
To: consumerPackage,
Type: artifact.OwnershipByFileOverlapRelationship,
Data: ownershipByFilesMetadata{
Files: []string{
"/usr/share/jenkins/jenkins.war",
},
},
}
return []pkg.Package{symlinkOwner, realFileOwner, consumerPackage}, []artifact.Relationship{relationship}
},
},
{
name: "misses-by-dead-symlink",
resolver: mockFR{
+5 -5
View File
@@ -278,16 +278,16 @@ func applyLicenseContentRules(p *pkg.Package, cfg cataloging.LicenseConfig) {
l := &licenses[i]
switch cfg.IncludeContent {
case cataloging.LicenseContentIncludeUnknown:
// we don't have an SPDX expression, which means we didn't find an SPDX license
// include the unknown licenses content in the final SBOM
// we have an SPDX expression, which means this is NOT an unknown license
// remove the content, we are only including content for unknown licenses by default
if l.SPDXExpression != "" {
licenses[i].Contents = ""
}
case cataloging.LicenseContentExcludeAll:
// clear it all out
licenses[i].Contents = ""
case cataloging.LicenseContentIncludeAll:
// always include the content
default:
// clear it all out
licenses[i].Contents = ""
}
}
+36 -1
View File
@@ -174,7 +174,7 @@ func TestApplyLicenseContentRules(t *testing.T) {
},
},
{
name: "IncludeLicenseContentDefault",
name: "LicenseContentIncludeAll",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
@@ -193,6 +193,41 @@ func TestApplyLicenseContentRules(t *testing.T) {
},
},
},
{
name: "default license config should be LicenseContentExcludeAll",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.DefaultLicenseConfig(),
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
},
{
Value: "License-Not-A-SPDX-Expression",
},
},
},
{
name: "invalid license content cataloging config results in the default case",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.LicenseConfig{
IncludeContent: cataloging.LicenseContent("invalid"),
},
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
},
{
Value: "License-Not-A-SPDX-Expression",
Contents: "", // content all removed
},
},
},
{
name: "Empty licenses",
inputLicenses: []pkg.License{},
+3 -1
View File
@@ -154,7 +154,8 @@ func DefaultPackageTaskFactories() Factories {
},
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary",
),
newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package"),
newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package", "elf"),
newSimplePackageTaskFactory(binary.NewPEPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "pe-package", "pe", "dll", "exe"),
newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
newSimplePackageTaskFactory(java.NewJvmDistributionCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "java", "jvm", "jdk", "jre"),
@@ -164,6 +165,7 @@ func DefaultPackageTaskFactories() Factories {
},
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
),
newSimplePackageTaskFactory(php.NewInterpreterCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "php"),
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag),
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.32/document",
"$id": "anchore.io/schema/syft/json/16.0.34/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@@ -1971,6 +1971,9 @@
{
"$ref": "#/$defs/OpamPackage"
},
{
"$ref": "#/$defs/PeBinary"
},
{
"$ref": "#/$defs/PhpComposerInstalledEntry"
},
@@ -2045,6 +2048,17 @@
"purl"
]
},
"PeBinary": {
"properties": {
"VersionResources": {
"$ref": "#/$defs/KeyValues"
}
},
"type": "object",
"required": [
"VersionResources"
]
},
"PhpComposerAuthors": {
"properties": {
"name": {
@@ -2677,6 +2691,12 @@
"sourceRpm": {
"type": "string"
},
"signatures": {
"items": {
"$ref": "#/$defs/RpmSignature"
},
"type": "array"
},
"size": {
"type": "integer"
},
@@ -2745,6 +2765,12 @@
"sourceRpm": {
"type": "string"
},
"signatures": {
"items": {
"$ref": "#/$defs/RpmSignature"
},
"type": "array"
},
"size": {
"type": "integer"
},
@@ -2821,6 +2847,29 @@
"flags"
]
},
"RpmSignature": {
"properties": {
"algo": {
"type": "string"
},
"hash": {
"type": "string"
},
"created": {
"type": "string"
},
"issuer": {
"type": "string"
}
},
"type": "object",
"required": [
"algo",
"hash",
"created",
"issuer"
]
},
"RubyGemspec": {
"properties": {
"name": {
-4
View File
@@ -14,10 +14,6 @@ const (
)
type LicenseConfig struct {
// IncludeUnknownLicenseContent controls whether the content of a license should be included in the SBOM when the license ID cannot be determined.
// Deprecated: use IncludeContent instead
IncludeUnknownLicenseContent bool `json:"-" yaml:"-" mapstructure:"-"`
// IncludeContent controls whether license copy discovered should be included in the SBOM.
IncludeContent LicenseContent `json:"include-content" yaml:"include-content" mapstructure:"include-content"`
+1
View File
@@ -255,6 +255,7 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t
RelationshipsConfig: c.Relationships,
DataGenerationConfig: c.DataGeneration,
PackagesConfig: c.Packages,
LicenseConfig: c.Licenses,
ComplianceConfig: c.Compliance,
FilesConfig: c.Files,
}
@@ -958,7 +958,7 @@ func Test_otherLicenses(t *testing.T) {
{
LicenseIdentifier: "LicenseRef-3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
// Carries through the syft-json license value when we shasum large texts
LicenseName: "LicenseRef-sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
LicenseName: "sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
ExtractedText: strings.TrimSpace(bigText),
},
},
@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"github.com/scylladb/go-set/strset"
"github.com/spdx/tools-golang/spdx"
"github.com/spdx/tools-golang/spdx/v2/common"
@@ -45,7 +46,7 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
},
}
collectSyftPackages(s, spdxIDMap, doc.Packages)
collectSyftPackages(s, spdxIDMap, doc)
collectSyftFiles(s, spdxIDMap, doc)
@@ -279,8 +280,12 @@ func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
return nil
}
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, packages []*spdx.Package) {
for _, p := range packages {
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document) {
skipIDs := packageIDsToSkip(doc)
for _, p := range doc.Packages {
if p == nil || skipIDs.Has(string(p.PackageSPDXIdentifier)) {
continue
}
syftPkg := toSyftPackage(p)
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
s.Artifacts.Packages.Add(syftPkg)
@@ -657,3 +662,15 @@ func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
}
return cpes
}
// packageIDsToSkip returns a set of packageIDs that should not be imported
func packageIDsToSkip(doc *spdx.Document) *strset.Set {
skipIDs := strset.New()
for i := 0; i < len(doc.Relationships); i++ {
r := doc.Relationships[i]
if r != nil && r.Relationship == spdx.RelationshipGeneratedFrom {
skipIDs.Add(string(r.RefB.ElementRefID))
}
}
return skipIDs
}
@@ -759,3 +759,37 @@ func Test_useSPDXIdentifierOverDerivedSyftArtifactID(t *testing.T) {
assert.Nil(t, err)
assert.NotNil(t, s.Artifacts.Packages.Package("1"))
}
func Test_skipsPackagesWithGeneratedFromRelationship(t *testing.T) {
doc := &spdx.Document{
SPDXVersion: "SPDX-2.3",
Packages: []*spdx.Package{
{
PackageName: "package-1",
PackageSPDXIdentifier: "1",
PackageVersion: "1.0.5",
},
{
PackageName: "package-1-src",
PackageSPDXIdentifier: "1-src",
PackageVersion: "1.0.5-src",
},
},
Relationships: []*spdx.Relationship{
{
Relationship: spdx.RelationshipGeneratedFrom,
RefA: common.DocElementID{ // package 1
ElementRefID: spdx.ElementID("1"),
},
RefB: common.DocElementID{ // generated from package 1-src
ElementRefID: spdx.ElementID("1-src"),
},
},
},
}
s, err := ToSyftModel(doc)
assert.Nil(t, err)
assert.NotNil(t, s.Artifacts.Packages.Package("1"))
assert.Nil(t, s.Artifacts.Packages.Package("1-src"))
}
+9 -1
View File
@@ -94,7 +94,15 @@ func redactor(values ...string) testutil.Redactor {
testutil.PatternReplacement{
// only the source component bom-ref (not package or other component bom-refs)
Search: regexp.MustCompile(`<component bom-ref="(?P<redact>[^"]*)" type="file">`),
Groups: []string{"redact"}, // use the regex to anchore the search, but only replace bytes within the capture group
Groups: []string{"redact"}, // use the regex to anchor the search, but only replace bytes within the capture group
Replace: "redacted",
},
).
WithPatternRedactorSpec(
testutil.PatternReplacement{
// only the source component bom-ref (not package or other component bom-refs)
Search: regexp.MustCompile(`<component bom-ref="(?P<redact>[^"]*)" type="container">`),
Groups: []string{"redact"}, // use the regex to anchor the search, but only replace bytes within the capture group
Replace: "redacted",
},
).
+5 -22
View File
@@ -71,31 +71,14 @@ func Backfill(p *pkg.Package) {
}
}
func setJavaMetadataFromPurl(p *pkg.Package, purl packageurl.PackageURL) {
func setJavaMetadataFromPurl(p *pkg.Package, _ packageurl.PackageURL) {
if p.Type != pkg.JavaPkg {
return
}
if purl.Namespace != "" {
if p.Metadata == nil {
p.Metadata = pkg.JavaArchive{}
}
meta, got := p.Metadata.(pkg.JavaArchive)
if got && meta.PomProperties == nil {
meta.PomProperties = &pkg.JavaPomProperties{}
p.Metadata = meta
}
if meta.PomProperties != nil {
// capture the group id from the purl if it is not already set
if meta.PomProperties.ArtifactID == "" {
meta.PomProperties.ArtifactID = purl.Name
}
if meta.PomProperties.GroupID == "" {
meta.PomProperties.GroupID = purl.Namespace
}
if meta.PomProperties.Version == "" {
meta.PomProperties.Version = purl.Version
}
}
if p.Metadata == nil {
// since we don't know if the purl elements directly came from pom properties or the manifest,
// we can only go as far as to set the type to JavaArchive, but not fill in the group id and artifact id
p.Metadata = pkg.JavaArchive{}
}
}
+3 -7
View File
@@ -101,13 +101,9 @@ func Test_Backfill(t *testing.T) {
Language: pkg.Java,
Name: "some-thing",
Version: "1.2.3",
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.apache",
ArtifactID: "some-thing",
Version: "1.2.3",
},
},
// we intentionally don't claim we found a pom properties file with a groupID from the purl.
// but we do claim that we found java data with an empty type.
Metadata: pkg.JavaArchive{},
},
},
}
@@ -3,6 +3,7 @@ package helpers
import (
"fmt"
"reflect"
"strings"
"github.com/CycloneDX/cyclonedx-go"
@@ -90,15 +91,19 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
Locations: decodeLocations(values),
Licenses: pkg.NewLicenseSet(decodeLicenses(c)...),
CPEs: decodeCPEs(c),
PURL: c.PackageURL,
}
// note: this may write in syft package type information
DecodeInto(p, values, "syft:package", CycloneDXFields)
metadataType := values["syft:package:metadataType"]
p.Metadata = decodePackageMetadata(values, c, metadataType)
// this will either use the purl from the component or generate a new one based off of any type information
// that was decoded above.
p.PURL = getPURL(c, p.Type)
if p.Type == "" {
p.Type = pkg.TypeFromPURL(p.PURL)
}
@@ -111,6 +116,41 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
return p
}
func getPURL(c *cyclonedx.Component, ty pkg.Type) string {
if c.PackageURL != "" {
// if there is a purl that where the namespace does not match the group information, we may
// accidentally drop group. We should consider adding group as a top-level syft package field.
return c.PackageURL
}
if strings.HasPrefix(c.BOMRef, "pkg:") {
// the bomref is a purl, so try to use that as the purl
_, err := packageurl.FromString(c.BOMRef)
if err == nil {
return c.BOMRef
}
}
if ty == "" {
return ""
}
tyStr := ty.PackageURLType()
switch tyStr {
case "", packageurl.TypeGeneric:
return ""
}
purl := packageurl.PackageURL{
Type: tyStr,
Namespace: c.Group,
Name: c.Name,
Version: c.Version,
}
return purl.ToString()
}
func setPackageName(p *pkg.Package, c *cyclonedx.Component) {
name := c.Name
if c.Group != "" {
@@ -275,6 +275,7 @@ func Test_decodeComponent(t *testing.T) {
component cyclonedx.Component
wantLanguage pkg.Language
wantMetadata any
wantPURL string
}{
{
name: "derive language from pURL if missing",
@@ -286,6 +287,18 @@ func Test_decodeComponent(t *testing.T) {
BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
},
wantLanguage: pkg.Java,
wantPURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
},
{
name: "derive language from bomref if missing",
component: cyclonedx.Component{
Name: "ch.qos.logback/logback-classic",
Version: "1.2.3",
Type: "library",
BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
},
wantLanguage: pkg.Java,
wantPURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
},
{
name: "handle RpmdbMetadata type without properties",
@@ -303,6 +316,7 @@ func Test_decodeComponent(t *testing.T) {
},
},
wantMetadata: pkg.RpmDBEntry{},
wantPURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
},
{
name: "handle RpmdbMetadata type with properties",
@@ -326,6 +340,24 @@ func Test_decodeComponent(t *testing.T) {
wantMetadata: pkg.RpmDBEntry{
Release: "some-release",
},
wantPURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
},
{
name: "generate a purl from package type",
component: cyclonedx.Component{
Name: "log4j",
Group: "org.apache.logging.log4j",
Version: "2.0.4",
Type: "library",
BOMRef: "log4j",
Properties: &[]cyclonedx.Property{
{
Name: "syft:package:type",
Value: "java-archive",
},
},
},
wantPURL: "pkg:maven/org.apache.logging.log4j/log4j@2.0.4",
},
}
@@ -338,9 +370,122 @@ func Test_decodeComponent(t *testing.T) {
if tt.wantMetadata != nil {
assert.Truef(t, reflect.DeepEqual(tt.wantMetadata, p.Metadata), "metadata should match: %+v != %+v", tt.wantMetadata, p.Metadata)
}
if tt.wantMetadata == nil && tt.wantLanguage == "" {
if tt.wantPURL != "" {
assert.Equal(t, tt.wantPURL, p.PURL, "purl should match")
}
if tt.wantMetadata == nil && tt.wantLanguage == "" && tt.wantPURL == "" {
t.Fatal("this is a useless test, please remove it")
}
})
}
}
func TestGetPURL(t *testing.T) {
tests := []struct {
name string
component *cyclonedx.Component
pkgType pkg.Type
expected string
}{
{
name: "component with PackageURL",
component: &cyclonedx.Component{
PackageURL: "pkg:npm/lodash@4.17.21",
Name: "lodash",
Version: "4.17.20", // different version to verify PackageURL is used
Group: "npm",
},
pkgType: pkg.NpmPkg,
expected: "pkg:npm/lodash@4.17.21",
},
{
name: "component with BOMRef as valid PURL",
component: &cyclonedx.Component{
BOMRef: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
Name: "commons-lang3",
Version: "3.11.0", // different version to verify BOMRef is used
Group: "org.apache.commons",
},
pkgType: pkg.JavaPkg,
expected: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
},
{
name: "component with BOMRef not a valid PURL",
component: &cyclonedx.Component{
BOMRef: "not-a-purl-ref",
Name: "commons-lang3",
Version: "3.12.0",
Group: "org.apache.commons",
},
pkgType: pkg.JavaPkg,
expected: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
},
{
name: "component with unknown package type",
component: &cyclonedx.Component{
Name: "some-component",
Version: "1.0.0",
Group: "org.example",
},
pkgType: pkg.UnknownPkg,
expected: "",
},
{
name: "component with empty package type",
component: &cyclonedx.Component{
Name: "some-component",
Version: "1.0.0",
Group: "org.example",
},
pkgType: "",
expected: "",
},
{
name: "component with generic package type",
component: &cyclonedx.Component{
Name: "some-component",
Version: "1.0.0",
Group: "org.example",
},
pkgType: pkg.LinuxKernelModulePkg,
expected: "",
},
{
name: "component with valid package type",
component: &cyclonedx.Component{
Name: "react",
Version: "18.2.0",
Group: "facebook",
},
pkgType: pkg.NpmPkg,
expected: "pkg:npm/facebook/react@18.2.0",
},
{
name: "component with no group",
component: &cyclonedx.Component{
Name: "express",
Version: "4.18.2",
},
pkgType: pkg.NpmPkg,
expected: "pkg:npm/express@4.18.2",
},
{
name: "component with no version",
component: &cyclonedx.Component{
Name: "express",
Group: "npm",
},
pkgType: pkg.NpmPkg,
expected: "pkg:npm/npm/express",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getPURL(tt.component, tt.pkgType)
assert.Equal(t, tt.expected, result)
})
}
}
@@ -73,7 +73,7 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense, otherL
for _, l := range raw {
candidate := createSPDXLicense(l)
// isCustomLicense determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
// this determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
// of the SPDX spec, where:
// - we should not have a complex SPDX expression
// - if a single license, it should not be a known license (on the SPDX license list)
@@ -50,6 +50,14 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: gocyclo,f
case pkg.DotnetPortableExecutableEntry:
typ = orgType
author = metadata.CompanyName
case pkg.PEBinary:
// this is a known common keyword used in version resources
// for more info see: https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
val, ok := metadata.VersionResources.Get("CompanyName")
if ok {
typ = orgType
author = val
}
case pkg.DpkgDBEntry:
author = metadata.Maintainer
@@ -112,6 +112,21 @@ func Test_OriginatorSupplier(t *testing.T) {
originator: "Organization: Microsoft Corporation",
supplier: "Organization: Microsoft Corporation",
},
{
name: "from PE binary",
input: pkg.Package{
Metadata: pkg.PEBinary{
VersionResources: pkg.KeyValues{
{
Key: "CompanyName",
Value: "Microsoft Corporation",
},
},
},
},
originator: "Organization: Microsoft Corporation",
supplier: "Organization: Microsoft Corporation",
},
{
name: "from dpkg DB",
input: pkg.Package{
+3 -7
View File
@@ -165,13 +165,9 @@ func TestDecoder_Decode(t *testing.T) {
Type: pkg.JavaPkg,
PURL: "pkg:maven/org.apache/some-pkg@4.11.3",
Language: pkg.Java,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.apache",
ArtifactID: "some-pkg",
Version: "4.11.3",
},
},
// we intentionally do not claim we found a pom properties file (don't derive this from the purl).
// but we need a metadata allocated since all Java packages have a this metadata type (a consistency point)
Metadata: pkg.JavaArchive{},
},
},
},
+24
View File
@@ -1,5 +1,10 @@
package model
import (
"encoding/json"
"fmt"
)
// Document represents the syft cataloging findings as a JSON document
type Document struct {
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
@@ -11,6 +16,25 @@ type Document struct {
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
}
func (d *Document) UnmarshalJSON(data []byte) error {
type Alias *Document
aux := Alias(d)
if err := json.Unmarshal(data, aux); err != nil {
return fmt.Errorf("could not unmarshal syft JSON document: %w", err)
}
// in previous versions of anchorectl, the file modes were stored as decimal values instead of octal.
if d.Schema.Version == "1.0.0" && d.Descriptor.Name == "anchorectl" {
// convert all file modes from decimal to octal
for i := range d.Files {
d.Files[i].Metadata.Mode = convertBase10ToBase8(d.Files[i].Metadata.Mode)
}
}
return nil
}
// Descriptor describes what created the document as well as surrounding metadata
type Descriptor struct {
Name string `json:"name"`
@@ -0,0 +1,99 @@
package model
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) {
tests := []struct {
name string
jsonData string
modes []int
}{
{
name: "schema version 1.0.0 + anchorectl",
jsonData: `{
"files": [
{"metadata": {"mode": 493}},
{"metadata": {"mode": 420}}
],
"schema": {"version": "1.0.0"},
"descriptor": {
"name": "anchorectl"
}
}`,
modes: []int{755, 644},
},
{
name: "schema version 1.0.0 + syft",
jsonData: `{
"files": [
{"metadata": {"mode": 755}},
{"metadata": {"mode": 644}}
],
"schema": {"version": "1.0.0"},
"descriptor": {
"name": "syft"
}
}`,
modes: []int{755, 644},
},
{
name: "schema version 2.0.0 + anchorectl",
jsonData: `{
"files": [
{"metadata": {"mode": 755}},
{"metadata": {"mode": 644}}
],
"schema": {"version": "2.0.0"},
"descriptor": {
"name": "anchorectl"
}
}`,
modes: []int{755, 644},
},
{
name: "missing schema version should not convert modes",
jsonData: `{
"files": [
{"metadata": {"mode": 755}}
],
"schema": {}
}`,
modes: []int{755},
},
{
name: "empty files array with version 1.0.0",
jsonData: `{
"files": [],
"schema": {"version": "1.0.0"},
"descriptor": {
"name": "anchorectl"
}
}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var doc Document
err := json.Unmarshal([]byte(tt.jsonData), &doc)
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}
var modes []int
for _, file := range doc.Files {
modes = append(modes, file.Metadata.Mode)
}
require.Len(t, doc.Files, len(tt.modes), "Unexpected number of files")
assert.Equal(t, tt.modes, modes, "File modes do not match expected values")
})
}
}
+92
View File
@@ -1,6 +1,11 @@
package model
import (
"encoding/json"
"fmt"
"strconv"
stereoFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
)
@@ -26,6 +31,57 @@ type FileMetadataEntry struct {
Size int64 `json:"size"`
}
type auxFileMetadataEntry FileMetadataEntry
type fileMetadataEntryWithLegacyHint struct {
*auxFileMetadataEntry `json:",inline"`
LegacyHint any `json:"FileInfo"`
}
func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error {
aux := fileMetadataEntryWithLegacyHint{
auxFileMetadataEntry: (*auxFileMetadataEntry)(f),
}
if err := json.Unmarshal(data, &aux); err == nil {
fieldsSpecified := f.Mode != 0 || f.Type != "" || f.LinkDestination != "" ||
f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0
if aux.LegacyHint == nil && fieldsSpecified {
// we should have at least one field set to a non-zero value... (this is not a legacy shape)
return nil
}
}
var legacy sbomImportLegacyFileMetadataEntry
if err := json.Unmarshal(data, &legacy); err != nil {
return err
}
if !legacy.Type.WasInt {
// this occurs for document shapes from a non-import path and indicates that the mode has already been converted to octal.
// That being said, we want to handle all legacy shapes the same, so we will convert this to base 10 for consistency.
legacy.Mode = convertBase8ToBase10(legacy.Mode)
}
f.Mode = legacy.Mode
f.Type = legacy.Type.Value
f.LinkDestination = legacy.LinkDestination
f.UserID = legacy.UserID
f.GroupID = legacy.GroupID
f.MIMEType = legacy.MIMEType
f.Size = legacy.Size
return nil
}
type sbomImportLegacyFileMetadataEntry struct {
Mode int `json:"Mode"`
Type intOrStringFileType `json:"Type"`
LinkDestination string `json:"LinkDestination"`
UserID int `json:"UserID"`
GroupID int `json:"GroupID"`
MIMEType string `json:"MIMEType"`
Size int64 `json:"Size"`
}
type FileLicense struct {
Value string `json:"value"`
SPDXExpression string `json:"spdxExpression"`
@@ -38,3 +94,39 @@ type FileLicenseEvidence struct {
Offset int `json:"offset"`
Extent int `json:"extent"`
}
type intOrStringFileType struct {
Value string
WasInt bool
}
func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err == nil {
lt.Value = str
return nil
}
var num stereoFile.Type
if err := json.Unmarshal(data, &num); err != nil {
return fmt.Errorf("file.Type must be either string or int, got: %s", string(data))
}
lt.Value = num.String()
lt.WasInt = true
return nil
}
func convertBase10ToBase8(rawMode int) int {
octalStr := fmt.Sprintf("%o", rawMode)
// we don't need to check that this is a valid octal string since the input is always an integer
result, _ := strconv.Atoi(octalStr)
return result
}
func convertBase8ToBase10(octalMode int) int {
octalStr := strconv.Itoa(octalMode)
result, _ := strconv.ParseInt(octalStr, 8, 64)
return int(result)
}
+320
View File
@@ -0,0 +1,320 @@
package model
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
jsonData []byte
expected FileMetadataEntry
}{
{
name: "unmarshal current format",
jsonData: []byte(`{
"mode": 644,
"type": "RegularFile",
"linkDestination": "/usr/bin/python3",
"userID": 1000,
"groupID": 1000,
"mimeType": "text/plain",
"size": 10174
}`),
expected: FileMetadataEntry{
Mode: 644,
Type: "RegularFile",
LinkDestination: "/usr/bin/python3",
UserID: 1000,
GroupID: 1000,
MIMEType: "text/plain",
Size: 10174,
},
},
{
name: "unmarshal legacy image add internal document format",
jsonData: []byte(`{
"FileInfo": {},
"Mode": 644,
"Type": "RegularFile",
"LinkDestination": "/usr/bin/python3",
"UserID": 1000,
"GroupID": 1000,
"MIMEType": "text/plain",
"Size": 10174
}`),
expected: FileMetadataEntry{
Mode: 420, // important! we convert this to base 10 so that all documents are consistent
Type: "RegularFile",
LinkDestination: "/usr/bin/python3",
UserID: 1000,
GroupID: 1000,
MIMEType: "text/plain",
Size: 10174,
},
},
{
name: "unmarshal legacy sbom import format",
jsonData: []byte(`{
"FileInfo": {},
"Mode": 644,
"Type": 0,
"LinkDestination": "/usr/bin/python3",
"UserID": 1000,
"GroupID": 1000,
"MIMEType": "text/plain",
"Size": 10174
}`),
expected: FileMetadataEntry{
Mode: 644,
Type: "RegularFile",
LinkDestination: "/usr/bin/python3",
UserID: 1000,
GroupID: 1000,
MIMEType: "text/plain",
Size: 10174,
},
},
{
name: "unmarshal minimal current format",
jsonData: []byte(`{
"mode": 0,
"type": "RegularFile",
"userID": 0,
"groupID": 0,
"size": 0
}`),
expected: FileMetadataEntry{
Type: "RegularFile",
},
},
{
name: "unmarshal minimal legacy format",
jsonData: []byte(`{
"FileInfo": {},
"Mode": 0,
"Type": "RegularFile",
"UserID": 0,
"GroupID": 0,
"Size": 0
}`),
expected: FileMetadataEntry{
Type: "RegularFile",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var actual FileMetadataEntry
err := actual.UnmarshalJSON(test.jsonData)
require.NoError(t, err)
if diff := cmp.Diff(test.expected, actual); diff != "" {
t.Errorf("FileMetadataEntry mismatch (-expected +actual):\n%s", diff)
}
})
}
}
func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
jsonData []byte
expected string
expectedWasInt bool
wantErr require.ErrorAssertionFunc
}{
// string inputs - should pass through unchanged
{
name: "string RegularFile",
jsonData: []byte(`"RegularFile"`),
expected: "RegularFile",
},
{
name: "string HardLink",
jsonData: []byte(`"HardLink"`),
expected: "HardLink",
},
{
name: "string Directory",
jsonData: []byte(`"Directory"`),
expected: "Directory",
},
{
name: "string custom value",
jsonData: []byte(`"CustomFileType"`),
expected: "CustomFileType",
},
// integer inputs - should convert to string representation
{
name: "int 0 (TypeRegular)",
jsonData: []byte(`0`),
expected: "RegularFile",
expectedWasInt: true,
},
{
name: "int 1 (TypeHardLink)",
jsonData: []byte(`1`),
expected: "HardLink",
expectedWasInt: true,
},
{
name: "int 2 (TypeSymLink)",
jsonData: []byte(`2`),
expected: "SymbolicLink",
expectedWasInt: true,
},
{
name: "int 3 (TypeCharacterDevice)",
jsonData: []byte(`3`),
expected: "CharacterDevice",
expectedWasInt: true,
},
{
name: "int 4 (TypeBlockDevice)",
jsonData: []byte(`4`),
expected: "BlockDevice",
expectedWasInt: true,
},
{
name: "int 5 (TypeDirectory)",
jsonData: []byte(`5`),
expected: "Directory",
expectedWasInt: true,
},
{
name: "int 6 (TypeFIFO)",
jsonData: []byte(`6`),
expected: "FIFONode",
expectedWasInt: true,
},
{
name: "int 7 (TypeSocket)",
jsonData: []byte(`7`),
expected: "Socket",
expectedWasInt: true,
},
{
name: "int 8 (TypeIrregular)",
jsonData: []byte(`8`),
expected: "IrregularFile",
expectedWasInt: true,
},
{
name: "unknown int",
jsonData: []byte(`99`),
expected: "Unknown",
expectedWasInt: true,
},
{
name: "negative int",
jsonData: []byte(`-1`),
expected: "Unknown",
expectedWasInt: true,
},
{
name: "null value",
jsonData: []byte(`null`),
},
{
name: "invalid JSON",
jsonData: []byte(`{`),
wantErr: require.Error,
},
{
name: "boolean value",
jsonData: []byte(`true`),
wantErr: require.Error,
},
{
name: "array value",
jsonData: []byte(`[]`),
wantErr: require.Error,
},
{
name: "object value",
jsonData: []byte(`{}`),
wantErr: require.Error,
},
{
name: "float value",
jsonData: []byte(`1.5`),
wantErr: require.Error,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.wantErr == nil {
test.wantErr = require.NoError
}
var ft intOrStringFileType
err := ft.UnmarshalJSON(test.jsonData)
test.wantErr(t, err)
if err != nil {
return
}
assert.Equal(t, test.expected, ft.Value)
assert.Equal(t, test.expectedWasInt, ft.WasInt)
})
}
}
func Test_convertBase10ToBase8(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{
name: "no permissions",
input: 0,
expected: 0,
},
{
name: "symlink + rwxrwxrwx",
input: 134218239,
expected: 1000000777,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := convertBase10ToBase8(tt.input)
require.Equal(t, tt.expected, actual)
})
}
}
func Test_convertBase8ToBase10(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{
name: "no permissions",
input: 0,
expected: 0,
},
{
name: "symlink + rwxrwxrwx",
input: 1000000777,
expected: 134218239,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := convertBase8ToBase10(tt.input)
require.Equal(t, tt.expected, actual)
})
}
}
+3 -4
View File
@@ -144,13 +144,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
}
func safeFileModeConvert(val int) (fs.FileMode, error) {
if val < math.MinInt32 || val > math.MaxInt32 {
// Value is out of the range that int32 can represent
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
if mode < 0 || mode > math.MaxUint32 {
// value is out of the range that int32 can represent
return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val)
}
// Safe to convert to os.FileMode
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
if err != nil {
return 0, err
}
@@ -491,6 +491,12 @@ func Test_safeFileModeConvert(t *testing.T) {
want: os.FileMode(511), // 777 in octal equals 511 in decimal
wantErr: false,
},
{
name: "valid perm with symlink type",
val: 1000000777, // symlink + rwxrwxrwx
want: os.FileMode(0o1000000777), // 134218239
wantErr: false,
},
{
name: "outside int32 high",
val: int(math.MaxInt32) + 1,
@@ -1451,7 +1451,7 @@ func TestFileResolver_FilesByGlob(t *testing.T) {
resolver, err := NewFromFile(parentPath, filePath)
assert.NoError(t, err)
refs, err := resolver.FilesByGlob("*.txt")
refs, err := resolver.FilesByGlob("**/*.txt")
assert.NoError(t, err)
assert.Len(t, refs, 1)
@@ -42,6 +42,7 @@ func AllTypes() []any {
pkg.NpmPackage{},
pkg.NpmPackageLockEntry{},
pkg.OpamPackage{},
pkg.PEBinary{},
pkg.PhpComposerInstalledEntry{},
pkg.PhpComposerLockEntry{},
pkg.PhpPearEntry{},
+1
View File
@@ -94,6 +94,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.NpmPackage{}, "javascript-npm-package", "NpmPackageJsonMetadata"),
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
jsonNames(pkg.PEBinary{}, "pe-binary"),
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.PhpPeclEntry{}, "php-pecl-entry", "PhpPeclMetadata"),
+5
View File
@@ -46,3 +46,8 @@ type ELFBinaryPackageNoteJSONPayload struct {
// Commit is the commit hash of the source repository for which the binary was built from
Commit string `json:"commit,omitempty"`
}
// PEBinary represents metadata captured from a Portable Executable formatted binary (dll, exe, etc.)
type PEBinary struct {
VersionResources KeyValues
}
@@ -13,12 +13,13 @@ import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
const catalogerName = "binary-classifier-cataloger"
type ClassifierCatalogerConfig struct {
Classifiers []Classifier `yaml:"classifiers" json:"classifiers" mapstructure:"classifiers"`
Classifiers []binutils.Classifier `yaml:"classifiers" json:"classifiers" mapstructure:"classifiers"`
}
func DefaultClassifierCatalogerConfig() ClassifierCatalogerConfig {
@@ -48,7 +49,7 @@ func (cfg ClassifierCatalogerConfig) MarshalJSON() ([]byte, error) {
// related runtimes like Python, Go, Java, or Node. Some exceptions can be made for widely-used binaries such
// as busybox.
type cataloger struct {
classifiers []Classifier
classifiers []binutils.Classifier
}
// Name returns a string that uniquely describes the cataloger
@@ -101,7 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
target.Metadata = meta
}
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
func catalog(resolver file.Resolver, cls binutils.Classifier) (packages []pkg.Package, err error) {
var errs error
locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil {
@@ -109,7 +110,7 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
return nil, err
}
for _, location := range locations {
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
pkgs, err := cls.EvidenceMatcher(cls, binutils.MatcherContext{Resolver: resolver, Location: location})
if err != nil {
errs = unknown.Append(errs, location, err)
continue
@@ -20,6 +20,7 @@ import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/directorysource"
"github.com/anchore/syft/syft/source/stereoscopesource"
@@ -248,45 +249,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("httpd-binary"),
},
},
{
// TODO: find original binary...
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
logicalFixture: "php-cli/8.2.1/linux-amd64",
expected: pkg.Package{
Name: "php-cli",
Version: "8.2.1",
Type: "binary",
PURL: "pkg:generic/php-cli@8.2.1",
Locations: locations("php"),
Metadata: metadata("php-cli-binary"),
},
},
{
// TODO: find original binary...
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
logicalFixture: "php-fpm/8.2.1/linux-amd64",
expected: pkg.Package{
Name: "php-fpm",
Version: "8.2.1",
Type: "binary",
PURL: "pkg:generic/php-fpm@8.2.1",
Locations: locations("php-fpm"),
Metadata: metadata("php-fpm-binary"),
},
},
{
// TODO: find original binary...
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
logicalFixture: "php-apache/8.2.1/linux-amd64",
expected: pkg.Package{
Name: "libphp",
Version: "8.2.1",
Type: "binary",
PURL: "pkg:generic/php@8.2.1",
Locations: locations("libphp.so"),
Metadata: metadata("php-apache-binary"),
},
},
{
// TODO: original binary is different than whats in config.yaml
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
@@ -1497,11 +1459,13 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
Locations: locations("foo"),
Metadata: metadata("foo-binary"),
}
fooClassifier := Classifier{
fooClassifier := binutils.Classifier{
Class: "foo-binary",
FileGlob: "**/foo",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
EvidenceMatcher: binutils.FileContentsVersionMatcher(
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
catalogerName,
),
Package: "foo",
PURL: mustPURL("pkg:generic/foo@version"),
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
@@ -1516,7 +1480,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
{
name: "empty-negative",
config: ClassifierCatalogerConfig{
Classifiers: []Classifier{},
Classifiers: []binutils.Classifier{},
},
fixtureDir: "test-fixtures/custom/go-1.14",
expected: nil,
@@ -1532,7 +1496,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
{
name: "nodefault-negative",
config: ClassifierCatalogerConfig{
Classifiers: []Classifier{fooClassifier},
Classifiers: []binutils.Classifier{fooClassifier},
},
fixtureDir: "test-fixtures/custom/go-1.14",
expected: nil,
@@ -1541,7 +1505,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
name: "default-extended-positive",
config: ClassifierCatalogerConfig{
Classifiers: append(
append([]Classifier{}, defaultClassifers...),
append([]binutils.Classifier{}, defaultClassifers...),
fooClassifier,
),
},
@@ -1553,11 +1517,11 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
config: ClassifierCatalogerConfig{
Classifiers: append(
append([]Classifier{}, defaultClassifers...),
Classifier{
append([]binutils.Classifier{}, defaultClassifers...),
binutils.Classifier{
Class: "foo-binary",
FileGlob: "**/foo",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)not there`),
EvidenceMatcher: binutils.FileContentsVersionMatcher(`(?m)not there`, catalogerName),
Package: "foo",
PURL: mustPURL("pkg:generic/foo@version"),
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
@@ -1571,7 +1535,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
name: "default-cutsom-positive",
config: ClassifierCatalogerConfig{
Classifiers: append(
append([]Classifier{}, defaultClassifers...),
append([]binutils.Classifier{}, defaultClassifers...),
fooClassifier,
),
},
@@ -1771,11 +1735,11 @@ func TestCatalogerConfig_MarshalJSON(t *testing.T) {
{
name: "only show names of classes",
cfg: ClassifierCatalogerConfig{
Classifiers: []Classifier{
Classifiers: []binutils.Classifier{
{
Class: "class",
FileGlob: "glob",
EvidenceMatcher: FileContentsVersionMatcher(".thing"),
EvidenceMatcher: binutils.FileContentsVersionMatcher(".thing", catalogerName),
Package: "pkg",
PURL: packageurl.PackageURL{
Type: "type",
+121 -123
View File
@@ -1,22 +1,44 @@
package binary
import (
"fmt"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
//nolint:funlen
func DefaultClassifiers() []Classifier {
return []Classifier{
func DefaultClassifiers() []binutils.Classifier {
m := binutils.ContextualEvidenceMatchers{CatalogerName: catalogerName}
var libpythonMatcher = m.FileNameTemplateVersionMatcher(
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
pythonVersionTemplate,
)
var rubyMatcher = m.FileContentsVersionMatcher(
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
return []binutils.Classifier{
{
Class: "python-binary",
FileGlob: "**/python*",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// try to find version information from libpython shared libraries
sharedLibraryLookup(
binutils.SharedLibraryLookup(
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
libpythonMatcher),
// check for version information in the binary
fileNameTemplateVersionMatcher(
m.FileNameTemplateVersionMatcher(
`(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`,
pythonVersionTemplate),
),
@@ -41,7 +63,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "pypy-binary-lib",
FileGlob: "**/libpypy*.so*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\[PyPy (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "pypy",
PURL: mustPURL("pkg:generic/pypy@version"),
@@ -49,7 +71,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "go-binary",
FileGlob: "**/go",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
Package: "go",
PURL: mustPURL("pkg:generic/go@version"),
@@ -58,7 +80,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "julia-binary",
FileGlob: "**/libjulia-internal.so",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)__init__\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00verify`),
Package: "julia",
PURL: mustPURL("pkg:generic/julia@version"),
@@ -67,7 +89,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "helm",
FileGlob: "**/helm",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "helm",
PURL: mustPURL("pkg:golang/helm.sh/helm@version"),
@@ -76,13 +98,13 @@ func DefaultClassifiers() []Classifier {
{
Class: "redis-binary",
FileGlob: "**/redis-server",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
m.FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
// matches against older versions of redis (~v2), e.g. "Server started, Redis version 2.8.23"
FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
m.FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
),
Package: "redis",
PURL: mustPURL("pkg:generic/redis@version"),
@@ -94,13 +116,13 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-openjdk",
FileGlob: "**/java",
EvidenceMatcher: matchExcluding(
evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.MatchExcluding(
binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
@@ -115,7 +137,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-ibm",
FileGlob: "**/java",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL]
`(?m)\x00java\x00(?P<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
Package: "java/jre",
@@ -125,8 +147,8 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-oracle",
FileGlob: "**/java",
EvidenceMatcher: matchExcluding(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.MatchExcluding(
m.FileContentsVersionMatcher(
// [NUL]19.0.1+10-21[NUL]
`(?m)\x00(?P<version>[0-9]+[.0-9]+[+][-0-9]+)\x00`),
// don't match openjdk
@@ -139,7 +161,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-graalvm",
FileGlob: "**/java",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+[.0-9]+[.0-9]+\+[0-9]+-jvmci-[0-9]+[.0-9]+-b[0-9]+)\x00`),
Package: "java/graalvm",
PURL: mustPURL("pkg:generic/java/graalvm@version"),
@@ -148,7 +170,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-jdk",
FileGlob: "**/jdb",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?([-._a-zA-Z0-9]+)?)\x00`),
Package: "java/jdk",
PURL: mustPURL("pkg:generic/java/jdk@version"),
@@ -157,13 +179,13 @@ func DefaultClassifiers() []Classifier {
{
Class: "nodejs-binary",
FileGlob: "**/node",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// [NUL]node v0.10.48[NUL]
// [NUL]v0.12.18[NUL]
// [NUL]v4.9.1[NUL]
// node.js/v22.9.0
FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
m.FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
m.FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
),
Package: "node",
PURL: mustPURL("pkg:generic/node@version"),
@@ -172,7 +194,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "go-binary-hint",
FileGlob: "**/VERSION*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?(-[0-9a-f]{7})?)`),
Package: "go",
PURL: mustPURL("pkg:generic/go@version"),
@@ -181,7 +203,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "busybox-binary",
FileGlob: "**/busybox",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "busybox",
PURL: mustPURL("pkg:generic/busybox@version"),
@@ -190,7 +212,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "util-linux-binary",
FileGlob: "**/getopt",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00util-linux\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "util-linux",
PURL: mustPURL("pkg:generic/util-linux@version"),
@@ -199,10 +221,10 @@ func DefaultClassifiers() []Classifier {
{
Class: "haproxy-binary",
FileGlob: "**/haproxy",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
m.FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
m.FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
),
Package: "haproxy",
PURL: mustPURL("pkg:generic/haproxy@version"),
@@ -211,44 +233,16 @@ func DefaultClassifiers() []Classifier {
{
Class: "perl-binary",
FileGlob: "**/perl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\/usr\/local\/lib\/perl\d\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "perl",
PURL: mustPURL("pkg:generic/perl@version"),
CPEs: singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-cli-binary",
FileGlob: "**/php*",
EvidenceMatcher: fileNameTemplateVersionMatcher(
`(.*/|^)php[0-9]*$`,
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-cli",
PURL: mustPURL("pkg:generic/php-cli@version"),
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-fpm-binary",
FileGlob: "**/php-fpm*",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-fpm",
PURL: mustPURL("pkg:generic/php-fpm@version"),
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-apache-binary",
FileGlob: "**/libphp*.so",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "libphp",
PURL: mustPURL("pkg:generic/php@version"),
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-composer-binary",
FileGlob: "**/composer*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)'pretty_version'\s*=>\s*'(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)'`),
Package: "composer",
PURL: mustPURL("pkg:generic/composer@version"),
@@ -257,7 +251,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "httpd-binary",
FileGlob: "**/httpd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)Apache\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "httpd",
PURL: mustPURL("pkg:generic/httpd@version"),
@@ -266,7 +260,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "memcached-binary",
FileGlob: "**/memcached",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)memcached\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "memcached",
PURL: mustPURL("pkg:generic/memcached@version"),
@@ -275,7 +269,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "traefik-binary",
FileGlob: "**/traefik",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]v1.7.34[NUL]
// [NUL]2.9.6[NUL]
// 3.0.4[NUL]
@@ -287,7 +281,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "arangodb-binary",
FileGlob: "**/arangosh",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s\[linux\]`),
Package: "arangodb",
PURL: mustPURL("pkg:generic/arangodb@version"),
@@ -296,7 +290,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "postgresql-binary",
FileGlob: "**/postgres",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]PostgreSQL 15beta4
// [NUL]PostgreSQL 15.1
// [NUL]PostgreSQL 9.6.24
@@ -309,11 +303,11 @@ func DefaultClassifiers() []Classifier {
{
Class: "mysql-binary",
FileGlob: "**/mysql",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// shutdown[NUL]8.0.37[NUL][NUL][NUL][NUL][NUL]mysql_real_esc
FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
m.FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
// /export/home/pb2/build/sb_0-26781090-1516292385.58/release/mysql-8.0.4-rc/mysys_ssl/my_default.cc
FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
m.FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
),
Package: "mysql",
PURL: mustPURL("pkg:generic/mysql@version"),
@@ -322,7 +316,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "mysql-binary",
FileGlob: "**/mysql",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m).*/percona-server-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
Package: "percona-server",
PURL: mustPURL("pkg:generic/percona-server@version"),
@@ -334,7 +328,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "mysql-binary",
FileGlob: "**/mysql",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m).*/Percona-XtraDB-Cluster-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
Package: "percona-xtradb-cluster",
PURL: mustPURL("pkg:generic/percona-xtradb-cluster@version"),
@@ -347,7 +341,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "xtrabackup-binary",
FileGlob: "**/xtrabackup",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m).*/percona-xtrabackup-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
Package: "percona-xtrabackup",
PURL: mustPURL("pkg:generic/percona-xtrabackup@version"),
@@ -356,7 +350,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "mariadb-binary",
FileGlob: "**/{mariadb,mysql}",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// 10.6.15-MariaDB
`(?m)(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`),
Package: "mariadb",
@@ -366,7 +360,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "rust-standard-library-linux",
FileGlob: "**/libstd-????????????????.so",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// clang LLVM (rustc version 1.48.0 (7eac88abb 2020-11-16))
`(?m)(\x00)clang LLVM \(rustc version (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
Package: "rust",
@@ -376,7 +370,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "rust-standard-library-macos",
FileGlob: "**/libstd-????????????????.dylib",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// c 1.48.0 (7eac88abb 2020-11-16)
`(?m)c (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
Package: "rust",
@@ -386,9 +380,9 @@ func DefaultClassifiers() []Classifier {
{
Class: "ruby-binary",
FileGlob: "**/ruby",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
rubyMatcher,
sharedLibraryLookup(
binutils.SharedLibraryLookup(
// try to find version information from libruby shared libraries
`^libruby\.so.*$`,
rubyMatcher),
@@ -400,12 +394,12 @@ func DefaultClassifiers() []Classifier {
{
Class: "erlang-binary",
FileGlob: "**/erlexec",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
@@ -417,16 +411,16 @@ func DefaultClassifiers() []Classifier {
{
Class: "erlang-alpine-binary",
FileGlob: "**/beam.smp",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// [NUL][NUL]26.1.2[NUL][NUL][NUL][NUL][NUL][NUL][NUL]NUL[NUL][NUL]Erlang/OTP
`\x00+(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)\x00+Erlang/OTP`,
),
@@ -438,12 +432,12 @@ func DefaultClassifiers() []Classifier {
{
Class: "erlang-library",
FileGlob: "**/liberts_internal.a",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
@@ -455,7 +449,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "swipl-binary",
FileGlob: "**/swipl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)swipl-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\/`,
),
Package: "swipl",
@@ -465,7 +459,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "dart-binary",
FileGlob: "**/dart",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// MathAtan[NUL]2.12.4 (stable)
// "%s"[NUL]3.0.0 (stable)
// Dart,GC"[NUL]3.5.2 (stable)
@@ -479,7 +473,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "haskell-ghc-binary",
FileGlob: "**/ghc*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00GHC (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "haskell/ghc",
@@ -489,7 +483,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "haskell-cabal-binary",
FileGlob: "**/cabal",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00Cabal-(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?)-`,
),
Package: "haskell/cabal",
@@ -499,7 +493,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "haskell-stack-binary",
FileGlob: "**/stack",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)Version\s*(?P<version>[0-9]+\.[0-9]+\.[0-9]+),\s*Git`,
),
Package: "haskell/stack",
@@ -509,7 +503,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "consul-binary",
FileGlob: "**/consul",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// NOTE: This is brittle and may not work for past or future versions
`CONSUL_VERSION: (?P<version>\d+\.\d+\.\d+)`,
),
@@ -520,7 +514,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "nginx-binary",
FileGlob: "**/nginx",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1'
// [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part
`(?m)(\x00|\?)nginx version: [^\/]+\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`,
@@ -535,7 +529,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "bash-binary",
FileGlob: "**/bash",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// @(#)Bash version 5.2.15(1) release GNU
// @(#)Bash version 5.2.0(1) alpha GNU
// @(#)Bash version 5.2.0(1) beta GNU
@@ -549,7 +543,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "openssl-binary",
FileGlob: "**/openssl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]OpenSSL 3.1.4'
// [NUL]OpenSSL 1.1.1w'
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
@@ -561,7 +555,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "gcc-binary",
FileGlob: "**/gcc",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// GCC: \(GNU\) 12.3.0'
`GCC: \(GNU\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
@@ -572,7 +566,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "fluent-bit-binary",
FileGlob: "**/fluent-bit",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]3.0.2[NUL]%sFluent Bit
// [NUL]2.2.3[NUL]Fluent Bit
// [NUL]2.2.1[NUL][NUL][NUL]Fluent Bit
@@ -587,7 +581,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "wordpress-cli-binary",
FileGlob: "**/wp",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// wp-cli/wp-cli 2.9.0'
`(?m)wp-cli/wp-cli (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
@@ -598,7 +592,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "curl-binary",
FileGlob: "**/curl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`curl/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
Package: "curl",
@@ -608,7 +602,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "lighttpd-binary",
FileGlob: "**/lighttpd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00lighttpd/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "lighttpd",
@@ -618,7 +612,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "proftpd-binary",
FileGlob: "**/proftpd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00ProFTPD Version (?P<version>[0-9]+\.[0-9]+\.[0-9]+[a-z]?)\x00`,
),
Package: "proftpd",
@@ -628,7 +622,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "zstd-binary",
FileGlob: "**/zstd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "zstd",
@@ -638,7 +632,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "xz-binary",
FileGlob: "**/xz",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00xz \(XZ Utils\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "xz",
@@ -648,7 +642,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "gzip-binary",
FileGlob: "**/gzip",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
),
Package: "gzip",
@@ -658,7 +652,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "sqlcipher-binary",
FileGlob: "**/sqlcipher",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`[^0-9]\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "sqlcipher",
@@ -668,7 +662,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "jq-binary",
FileGlob: "**/jq",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00(?P<version>[0-9]{1,3}\.[0-9]{1,3}(\.[0-9]+)?)\x00`,
),
Package: "jq",
@@ -678,7 +672,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "chrome-binary",
FileGlob: "**/chrome",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]127.0.6533.119[NUL]Default
`\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\x00Default`,
),
@@ -689,18 +683,22 @@ func DefaultClassifiers() []Classifier {
}
}
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
// cpe string cannot be parsed into valid CPE Attributes
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
src := cpe.GeneratedSource
if len(source) > 0 {
src = source[0]
}
return []cpe.CPE{
cpe.Must(cpeString, src),
}
}
var libpythonMatcher = fileNameTemplateVersionMatcher(
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
pythonVersionTemplate,
)
var rubyMatcher = FileContentsVersionMatcher(
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
func mustPURL(purl string) packageurl.PackageURL {
p, err := packageurl.FromString(purl)
if err != nil {
panic(fmt.Sprintf("invalid PURL: %s", p))
}
return p
}
+18
View File
@@ -0,0 +1,18 @@
package binary
import "github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
// Note: all generic utilities for catalogers have been moved to the internal/binutils package.
// Deprecated: This package is deprecated and will be removed in syft v2
type Classifier = binutils.Classifier
// Deprecated: This package is deprecated and will be removed in syft v2
type EvidenceMatcher = binutils.EvidenceMatcher
// Deprecated: This package is deprecated and will be removed in syft v2
func FileContentsVersionMatcher(
pattern string,
) EvidenceMatcher {
return binutils.FileContentsVersionMatcher(pattern, catalogerName)
}
+2 -2
View File
@@ -15,7 +15,7 @@ func newELFPackage(ctx context.Context, metadata elfBinaryPackageNotes, location
Name: metadata.Name,
Version: metadata.Version,
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, metadata.License)),
PURL: packageURL(metadata),
PURL: elfPackageURL(metadata),
Type: pkgType(metadata.Type),
Locations: locations,
Metadata: metadata.ELFBinaryPackageNoteJSONPayload,
@@ -26,7 +26,7 @@ func newELFPackage(ctx context.Context, metadata elfBinaryPackageNotes, location
return p
}
func packageURL(metadata elfBinaryPackageNotes) string {
func elfPackageURL(metadata elfBinaryPackageNotes) string {
var qualifiers []packageurl.Qualifier
os, osVersion := osNameAndVersionFromMetadata(metadata)
@@ -10,7 +10,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_ELF_Package_Cataloger(t *testing.T) {
func Test_ELFPackageCataloger(t *testing.T) {
cases := []struct {
name string
@@ -130,7 +130,7 @@ func Test_packageURL(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.want, packageURL(test.metadata))
assert.Equal(t, test.want, elfPackageURL(test.metadata))
})
}
}
+134
View File
@@ -0,0 +1,134 @@
package binary
import (
"path"
"regexp"
"sort"
"strings"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
var (
// spaceRegex includes nbsp (#160) considered to be a space character
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
numberRegex = regexp.MustCompile(`\d`)
)
func newPEPackage(versionResources map[string]string, f file.Location) pkg.Package {
name := findNameFromVR(versionResources)
if name == "" {
// it's possible that the version resources are empty, so we fall back to the file name
name = strings.TrimSuffix(strings.TrimSuffix(path.Base(f.RealPath), ".exe"), ".dll")
}
p := pkg.Package{
Name: name,
Version: findVersionFromVR(versionResources),
Locations: file.NewLocationSet(f.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Type: pkg.BinaryPkg,
Metadata: newPEBinaryVersionResourcesFromMap(versionResources),
}
p.SetID()
return p
}
func newPEBinaryVersionResourcesFromMap(vr map[string]string) pkg.PEBinary {
var kvs pkg.KeyValues
for k, v := range vr {
if v == "" {
continue
}
kvs = append(kvs, pkg.KeyValue{
Key: k,
Value: spaceNormalize(v),
})
}
sort.Slice(kvs, func(i, j int) bool {
return kvs[i].Key < kvs[j].Key
})
return pkg.PEBinary{
VersionResources: kvs,
}
}
func findNameFromVR(versionResources map[string]string) string {
// PE files not authored by Microsoft tend to use ProductName as an identifier.
nameFields := []string{"ProductName", "FileDescription", "InternalName", "OriginalFilename"}
if isMicrosoftVR(versionResources) {
// for Microsoft files, prioritize FileDescription.
nameFields = []string{"FileDescription", "InternalName", "OriginalFilename", "ProductName"}
}
var name string
for _, field := range nameFields {
value := spaceNormalize(versionResources[field])
if value == "" {
continue
}
name = value
break
}
return name
}
func isMicrosoftVR(versionResources map[string]string) bool {
return strings.Contains(strings.ToLower(versionResources["CompanyName"]), "microsoft") ||
strings.Contains(strings.ToLower(versionResources["ProductName"]), "microsoft")
}
// spaceNormalize trims and normalizes whitespace in a string.
func spaceNormalize(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
// ensure valid UTF-8.
value = strings.ToValidUTF8(value, "")
// consolidate all whitespace.
value = spaceRegex.ReplaceAllString(value, " ")
// remove non-printable characters.
value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "")
// consolidate again and trim.
value = spaceRegex.ReplaceAllString(value, " ")
value = strings.TrimSpace(value)
return value
}
func findVersionFromVR(versionResources map[string]string) string {
productVersion := extractVersionFromResourcesValue(versionResources["ProductVersion"])
fileVersion := extractVersionFromResourcesValue(versionResources["FileVersion"])
if productVersion != "" {
return productVersion
}
return fileVersion
}
func extractVersionFromResourcesValue(version string) string {
version = strings.TrimSpace(version)
out := ""
for i, f := range strings.Fields(version) {
if containsNumber(out) && !containsNumber(f) {
return out
}
if i == 0 {
out = f
} else {
out += " " + f
}
}
return out
}
func containsNumber(s string) bool {
return numberRegex.MatchString(s)
}
@@ -0,0 +1,39 @@
package binary
import (
"context"
"fmt"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pe"
)
// NewPEPackageCataloger returns a cataloger that interprets packages from DLL and EXE files.
func NewPEPackageCataloger() pkg.Cataloger {
return generic.NewCataloger("pe-binary-package-cataloger").
WithParserByGlobs(parsePE, "**/*.dll", "**/*.exe")
}
func parsePE(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
f, err := pe.Read(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse PE file %q: %w", reader.RealPath, err)
}
if f == nil {
return nil, nil, unknown.Newf(reader, "unable to determine packages")
}
if f.CLR.HasEvidenceOfCLR() {
// this is for a .NET application, which is covered by other catalogers already
return nil, nil, nil
}
p := newPEPackage(f.VersionResources, f.Location)
return []pkg.Package{p}, nil, nil
}
@@ -0,0 +1,64 @@
package binary
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_PEPackageCataloger(t *testing.T) {
cases := []struct {
name string
fixture string
expected []pkg.Package
wantErr require.ErrorAssertionFunc
}{
{
name: "non-.NET package",
fixture: "image-jruby",
expected: []pkg.Package{
{
Name: "JRuby",
Version: "9.3.15.0",
Type: pkg.BinaryPkg,
Locations: file.NewLocationSet(
file.NewLocation("/jruby_windows_9_3_15_0.exe"),
),
FoundBy: "pe-binary-package-cataloger",
Metadata: pkg.PEBinary{
VersionResources: pkg.KeyValues{
{Key: "CompanyName", Value: "JRuby Dev Team"},
{Key: "FileDescription", Value: "JRuby"},
{Key: "FileVersion", Value: "9.3.15.0"},
{Key: "InternalName", Value: "jruby"},
{Key: "LegalCopyright", Value: "JRuby Dev Team"},
{Key: "OriginalFilename", Value: "jruby_windows-x32_9_3_15_0.exe"},
{Key: "ProductName", Value: "JRuby"},
{Key: "ProductVersion", Value: "9.3.15.0"},
},
},
},
},
},
{
name: "ignore .NET packages",
fixture: "image-dotnet-app",
expected: nil, // expect nothing!
},
}
for _, v := range cases {
t.Run(v.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
WithImageResolver(t, v.fixture).
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
Expects(v.expected, nil).
TestCataloger(t, NewPEPackageCataloger())
})
}
}
@@ -1,12 +0,0 @@
name: php
offset: unknown
length: unknown
snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f
fileSha256: unknown
### byte snippet to follow ###
%s'
%s,%s
X-Powered-By: PHP/8.2.1
index pointer
PHP_VERSION
@@ -1,12 +0,0 @@
name: php-fpm
offset: unknown
length: unknown
snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f
fileSha256: unknown
### byte snippet to follow ###
%s'
%s,%s
X-Powered-By: PHP/8.2.1
index pointer
PHP_VERSION
@@ -0,0 +1,10 @@
FROM alpine:latest AS builder
RUN apk add --no-cache wget unzip
RUN mkdir -p /app
RUN wget -O /app/minver.nupkg https://www.nuget.org/api/v2/package/minver-cli/4.3.0
RUN unzip /app/minver.nupkg -d /app/minver
from busybox:latest
COPY --from=builder /app/minver /minver
@@ -0,0 +1,7 @@
FROM --platform=linux/amd64 alpine:latest AS builder
RUN wget -O jruby_windows_9_3_15_0.exe https://s3.amazonaws.com/jruby.org/downloads/9.3.15.0/jruby_windows_9_3_15_0.exe
FROM scratch
COPY --from=builder /jruby_windows_9_3_15_0.exe /jruby_windows_9_3_15_0.exe
@@ -473,7 +473,7 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
}
defer internal.CloseAndLogError(reader, loc.RealPath)
ldpe, err := getLogicalDotnetPE(file.NewLocationReadCloser(loc, reader))
ldpe, err := readLogicalPE(file.NewLocationReadCloser(loc, reader))
if err != nil {
return nil, unknown.New(loc, fmt.Errorf("unable to parse PE file: %w", err))
}
@@ -482,7 +482,7 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
return nil, nil
}
if !ldpe.CLR.hasEvidenceOfCLR() {
if !ldpe.CLR.HasEvidenceOfCLR() {
// this is not a .NET binary
return nil, nil
}
+12 -8
View File
@@ -252,8 +252,8 @@ func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
if !bundlingDetected && knownBundlers.Has(name) {
bundlingDetected = true
}
p.AnyChildClaimsDLLs = searchForDLLClaims(packageMap, p.dependencyNameVersions()...)
p.AnyChildHasDLLs = searchForDLLEvidence(packageMap, p.dependencyNameVersions()...)
p.AnyChildClaimsDLLs = searchForDLLClaims(packageMap, strset.New(), p.dependencyNameVersions()...)
p.AnyChildHasDLLs = searchForDLLEvidence(packageMap, strset.New(), p.dependencyNameVersions()...)
packages[p.NameVersion] = *p
}
@@ -286,31 +286,35 @@ func mergeSets(s1, s2 *strset.Set) *strset.Set {
type visitorFunc func(p *logicalDepsJSONPackage) bool
// searchForDLLEvidence recursively searches for executables found for any of the given nameVersions and children recursively.
func searchForDLLEvidence(packageMap map[string]*logicalDepsJSONPackage, nameVersions ...string) bool {
func searchForDLLEvidence(packageMap map[string]*logicalDepsJSONPackage, visited *strset.Set, nameVersions ...string) bool {
return traverseDependencies(packageMap, func(p *logicalDepsJSONPackage) bool {
return p.FoundDLLs(true)
}, nameVersions...)
}, visited, nameVersions...)
}
// searchForDLLClaims recursively searches for DLL claims in the deps.json for any of the given nameVersions and children recursively.
func searchForDLLClaims(packageMap map[string]*logicalDepsJSONPackage, nameVersions ...string) bool {
func searchForDLLClaims(packageMap map[string]*logicalDepsJSONPackage, visited *strset.Set, nameVersions ...string) bool {
return traverseDependencies(packageMap, func(p *logicalDepsJSONPackage) bool {
return p.ClaimsDLLs(true)
}, nameVersions...)
}, visited, nameVersions...)
}
func traverseDependencies(packageMap map[string]*logicalDepsJSONPackage, visitor visitorFunc, nameVersions ...string) bool {
func traverseDependencies(packageMap map[string]*logicalDepsJSONPackage, visitor visitorFunc, visited *strset.Set, nameVersions ...string) bool {
if len(nameVersions) == 0 {
return false
}
for _, nameVersion := range nameVersions {
if visited.Has(nameVersion) {
continue
}
visited.Add(nameVersion)
if p, ok := packageMap[nameVersion]; ok {
if visitor(p) {
return true
}
if traverseDependencies(packageMap, visitor, p.dependencyNameVersions()...) {
if traverseDependencies(packageMap, visitor, visited, p.dependencyNameVersions()...) {
return true
}
}
@@ -149,6 +149,11 @@ func TestGetLogicalDepsJSON_MergeTargets(t *testing.T) {
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll": {},
},
},
"Microsoft.CodeAnalysis.Common/4.0.0": {
Dependencies: map[string]string{
"Microsoft.CodeAnalysis.CSharp": "4.0.0",
},
},
},
},
Libraries: map[string]depsLibrary{
+1 -1
View File
@@ -150,7 +150,7 @@ func newDotnetDepsEntry(lp logicalDepsJSONPackage) pkg.DotnetDepsEntry {
}
}
// newDotnetPortableExecutableEntry creates a portable executable entry from a logicalPE.
// newDotnetPortableExecutableEntry creates a portable executable entry from a File.
func newDotnetPortableExecutableEntry(pe logicalPE) pkg.DotnetPortableExecutableEntry {
return newDotnetPortableExecutableEntryFromMap(pe.VersionResources)
}
+8 -718
View File
@@ -1,740 +1,30 @@
package dotnet
import (
"bytes"
"debug/pe"
"encoding/binary"
"errors"
"fmt"
"io"
"unicode/utf16"
"github.com/scylladb/go-set/strset"
"github.com/scylladb/go-set/u32set"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pe"
)
const peMaxAllowedDirectoryEntries = 0x1000
var imageDirectoryEntryIndexes = []int{
pe.IMAGE_DIRECTORY_ENTRY_RESOURCE, // where version resources are stored
pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // where info about the CLR is stored
}
// logicalPE does not directly represent a binary shape to be parsed, instead it represents the
// information of interest extracted from a PE file.
// logicalPE represents a PE file within the context of a .NET project (considering the deps.json file).
type logicalPE struct {
// Location is where the PE file was found
Location file.Location
pe.File
// TargetPath is the path is the deps.json target entry. This is not present in the PE file
// but instead is used in downstream processing to track associations between the PE file and the deps.json file.
TargetPath string
// CLR is the information about the CLR (common language runtime) version found in the PE file which helps
// understand if this executable is even a .NET application.
CLR *clrEvidence
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
VersionResources map[string]string
}
// clrEvidence is basic info about the CLR (common language runtime) version from the COM descriptor.
// This is not a complete representation of the CLR version, but rather a subset of the information that is
// useful to us.
type clrEvidence struct {
// HasClrResourceNames is true if there are CLR resource names found in the PE file (e.g. "CLRDEBUGINFO").
HasClrResourceNames bool
// MajorVersion is the minimum supported major version of the CLR.
MajorVersion uint16
// MinorVersion is the minimum supported minor version of the CLR.
MinorVersion uint16
}
// hasEvidenceOfCLR returns true if the PE file has evidence of a CLR (common language runtime) version.
func (c *clrEvidence) hasEvidenceOfCLR() bool {
return c != nil && (c.MajorVersion != 0 && c.MinorVersion != 0 || c.HasClrResourceNames)
}
type peDosHeader struct {
Magic [2]byte // "MZ"
Unused [58]byte
AddressOfNewEXEHeader uint32 // offset to PE header
}
// peImageCore20 represents the .NET Core 2.0 header structure.
// Source: https://github.com/dotnet/msbuild/blob/9fa9d800dabce3bfcf8365f651f3a713e01f8a85/src/Tasks/NativeMethods.cs#L761-L775
type peImageCore20 struct {
Cb uint32
MajorRuntimeVersion uint16
MinorRuntimeVersion uint16
}
// peImageResourceDirectory represents the resource directory structure.
type peImageResourceDirectory struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
NumberOfNamedEntries uint16
NumberOfIDEntries uint16
}
// peImageResourceDirectoryEntry represents an entry in the resource directory entries.
type peImageResourceDirectoryEntry struct {
Name uint32
OffsetToData uint32
}
// peImageResourceDataEntry is the unit of raw data in the Resource Data area.
type peImageResourceDataEntry struct {
OffsetToData uint32
Size uint32
CodePage uint32
Reserved uint32
}
// peVsFixedFileInfo represents the fixed file information structure.
type peVsFixedFileInfo struct {
Signature uint32
StructVersion uint32
FileVersionMS uint32
FileVersionLS uint32
ProductVersionMS uint32
ProductVersionLS uint32
FileFlagsMask uint32
FileFlags uint32
FileOS uint32
FileType uint32
FileSubtype uint32
FileDateMS uint32
FileDateLS uint32
}
type peVsVersionInfo peLenValLenType
type peStringFileInfo peLenValLenType
type peStringTable peLenValLenType
type peString peLenValLenType
type peLenValLenType struct {
Length uint16
ValueLength uint16
Type uint16
}
type extractedSection struct {
RVA uint32
BaseRVA uint32
Size uint32
Reader *bytes.Reader
}
func (s extractedSection) exists() bool {
return s.RVA != 0 && s.Size != 0
}
func directoryName(i int) string {
switch i {
case pe.IMAGE_DIRECTORY_ENTRY_RESOURCE:
return "Resource"
case pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
return "COM Descriptor"
}
return fmt.Sprintf("Unknown (%d)", i)
}
func getLogicalDotnetPE(f file.LocationReadCloser) (*logicalPE, error) {
r, err := unionreader.GetUnionReader(f)
func readLogicalPE(reader file.LocationReadCloser) (*logicalPE, error) {
peFile, err := pe.Read(reader)
if err != nil {
return nil, err
}
sections, _, err := parsePEFile(r)
if err != nil {
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
}
dirs := u32set.New() // keep track of the RVAs we have already parsed (prevent infinite recursion edge cases)
versionResources := make(map[string]string) // map of version resource keys to their values
resourceNames := strset.New() // set of resource names found in the PE file
err = parseResourceDirectory(sections[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE], dirs, versionResources, resourceNames)
if err != nil {
return nil, err
}
c, err := parseCLR(sections[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR], resourceNames)
if err != nil {
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
if peFile == nil {
return nil, nil
}
return &logicalPE{
Location: f.Location,
CLR: c,
VersionResources: versionResources,
File: *peFile,
}, nil
}
// parsePEFile creates readers for targeted sections of the binary used by downstream processing.
func parsePEFile(file unionreader.UnionReader) (map[int]*extractedSection, []pe.SectionHeader32, error) {
fileHeader, magic, err := parsePEHeader(file)
if err != nil {
return nil, nil, fmt.Errorf("error parsing PE header: %w", err)
}
soi, headers, err := parseSectionHeaders(file, magic, fileHeader.NumberOfSections)
if err != nil {
return nil, nil, fmt.Errorf("error parsing section headers: %w", err)
}
for i, sec := range soi {
if !sec.exists() {
continue
}
data, err := readDataFromRVA(file, sec.RVA, sec.Size, headers)
if err != nil {
return nil, nil, fmt.Errorf("error reading %q section data: %w", directoryName(i), err)
}
sec.Reader = data
}
return soi, headers, nil
}
// parsePEHeader reads the beginning of a PE formatted file, returning the file header and "magic" indicator
// for downstream logic to determine 32/64 bit parsing.
func parsePEHeader(file unionreader.UnionReader) (*pe.FileHeader, uint16, error) {
var dosHeader peDosHeader
if err := binary.Read(file, binary.LittleEndian, &dosHeader); err != nil {
return nil, 0, fmt.Errorf("error reading DOS header: %w", err)
}
if string(dosHeader.Magic[:]) != "MZ" {
return nil, 0, fmt.Errorf("invalid DOS header magic")
}
peOffset := int64(dosHeader.AddressOfNewEXEHeader)
if _, err := file.Seek(peOffset, io.SeekStart); err != nil {
return nil, 0, fmt.Errorf("error seeking to PE header: %w", err)
}
var signature [4]byte
if err := binary.Read(file, binary.LittleEndian, &signature); err != nil {
return nil, 0, fmt.Errorf("error reading PE signature: %w", err)
}
if !bytes.Equal(signature[:], []byte("PE\x00\x00")) {
return nil, 0, fmt.Errorf("invalid PE signature")
}
var fileHeader pe.FileHeader
if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil {
return nil, 0, fmt.Errorf("error reading file header: %w", err)
}
var magic uint16
if err := binary.Read(file, binary.LittleEndian, &magic); err != nil {
return nil, 0, fmt.Errorf("error reading optional header magic: %w", err)
}
// seek back to before reading magic (since that value is in the header)
if _, err := file.Seek(-2, io.SeekCurrent); err != nil {
return nil, 0, fmt.Errorf("error seeking back to before reading magic: %w", err)
}
return &fileHeader, magic, nil
}
// parseSectionHeaders reads the section headers from the PE file and extracts the virtual addresses + section size
// information for the sections of interest. Additionally, all section headers are returned to aid in downstream processing.
func parseSectionHeaders(file unionreader.UnionReader, magic uint16, numberOfSections uint16) (map[int]*extractedSection, []pe.SectionHeader32, error) {
soi := make(map[int]*extractedSection)
switch magic {
case 0x10B: // PE32
var optHeader pe.OptionalHeader32
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
return nil, nil, fmt.Errorf("error reading optional header (PE32): %w", err)
}
for _, i := range imageDirectoryEntryIndexes {
sectionHeader := optHeader.DataDirectory[i]
if sectionHeader.Size == 0 {
continue
}
soi[i] = &extractedSection{
RVA: sectionHeader.VirtualAddress,
Size: sectionHeader.Size,
}
}
case 0x20B: // PE32+ (64 bit)
var optHeader pe.OptionalHeader64
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
return nil, nil, fmt.Errorf("error reading optional header (PE32+): %w", err)
}
for _, i := range imageDirectoryEntryIndexes {
sectionHeader := optHeader.DataDirectory[i]
if sectionHeader.Size == 0 {
continue
}
soi[i] = &extractedSection{
RVA: sectionHeader.VirtualAddress,
Size: sectionHeader.Size,
}
}
default:
return nil, nil, fmt.Errorf("unknown optional header magic: 0x%x", magic)
}
// read section headers
headers := make([]pe.SectionHeader32, numberOfSections)
for i := 0; i < int(numberOfSections); i++ {
if err := binary.Read(file, binary.LittleEndian, &headers[i]); err != nil {
return nil, nil, fmt.Errorf("error reading section header: %w", err)
}
}
return soi, headers, nil
}
// parseCLR extracts the CLR (common language runtime) version information from the COM descriptor and makes
// present/not-present determination based on the presence of CLR resource names.
func parseCLR(sec *extractedSection, resourceNames *strset.Set) (*clrEvidence, error) {
hasCLRDebugResourceNames := resourceNames.HasAny("CLRDEBUGINFO")
if sec == nil || sec.Reader == nil {
return &clrEvidence{
HasClrResourceNames: hasCLRDebugResourceNames,
}, nil
}
reader := sec.Reader
var c peImageCore20
if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
return nil, fmt.Errorf("error reading CLR header: %w", err)
}
return &clrEvidence{
HasClrResourceNames: hasCLRDebugResourceNames,
MajorVersion: c.MajorRuntimeVersion,
MinorVersion: c.MinorRuntimeVersion,
}, nil
}
// rvaToFileOffset is a helper function to convert RVA to file offset using section headers
func rvaToFileOffset(rva uint32, sections []pe.SectionHeader32) (uint32, error) {
for _, section := range sections {
if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize {
return section.PointerToRawData + (rva - section.VirtualAddress), nil
}
}
return 0, fmt.Errorf("RVA 0x%x not found in any section", rva)
}
// readDataFromRVA will read data from a specific RVA in the PE file
func readDataFromRVA(file io.ReadSeeker, rva, size uint32, sections []pe.SectionHeader32) (*bytes.Reader, error) {
if size == 0 {
return nil, fmt.Errorf("zero size specified")
}
offset, err := rvaToFileOffset(rva, sections)
if err != nil {
return nil, err
}
if _, err := file.Seek(int64(offset), io.SeekStart); err != nil {
return nil, fmt.Errorf("error seeking to data: %w", err)
}
data := make([]byte, size)
if _, err := io.ReadFull(file, data); err != nil {
return nil, fmt.Errorf("error reading data: %w", err)
}
return bytes.NewReader(data), nil
}
// parseResourceDirectory recursively parses a PE resource directory. This takes a relative virtual address (offset of
// a piece of data or code relative to the base address), the size of the resource directory, the set of RVAs already
// parsed, and the map to populate discovered version resource values.
//
// .rsrc Section
// +------------------------------+
// | Resource Directory Table |
// +------------------------------+
// | Resource Directory Entries |
// | +------------------------+ |
// | | Subdirectory or Data | |
// | +------------------------+ |
// +------------------------------+
// | Resource Data Entries |
// | +------------------------+ |
// | | Resource Data | |
// | +------------------------+ |
// +------------------------------+
// | Actual Resource Data |
// +------------------------------+
//
// sources:
// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section
// - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)#pe-file-resources
func parseResourceDirectory(sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
if sec == nil || sec.Size <= 0 {
return nil
}
if sec.Reader == nil {
return errors.New("resource section not found")
}
baseRVA := sec.BaseRVA
if baseRVA == 0 {
baseRVA = sec.RVA
}
offset := int64(sec.RVA - baseRVA)
if _, err := sec.Reader.Seek(offset, io.SeekStart); err != nil {
return fmt.Errorf("error seeking to directory offset: %w", err)
}
var directoryHeader peImageResourceDirectory
if err := readIntoStruct(sec.Reader, &directoryHeader); err != nil {
return fmt.Errorf("error reading directory header: %w", err)
}
numEntries := int(directoryHeader.NumberOfNamedEntries + directoryHeader.NumberOfIDEntries)
switch {
case numEntries > peMaxAllowedDirectoryEntries:
return fmt.Errorf("too many entries in resource directory: %d", numEntries)
case numEntries == 0:
return fmt.Errorf("no entries in resource directory")
case numEntries < 0:
return fmt.Errorf("invalid number of entries in resource directory: %d", numEntries)
}
for i := 0; i < numEntries; i++ {
var entry peImageResourceDirectoryEntry
entryOffset := offset + int64(binary.Size(directoryHeader)) + int64(i*binary.Size(entry))
if _, err := sec.Reader.Seek(entryOffset, io.SeekStart); err != nil {
log.Tracef("error seeking to PE entry offset: %v", err)
continue
}
if err := readIntoStruct(sec.Reader, &entry); err != nil {
continue
}
if err := processResourceEntry(entry, baseRVA, sec, dirs, fields, names); err != nil {
log.Tracef("error processing resource entry: %v", err)
continue
}
}
return nil
}
func processResourceEntry(entry peImageResourceDirectoryEntry, baseRVA uint32, sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
// if the high bit is set, this is a directory entry, otherwise it is a data entry
isDirectory := entry.OffsetToData&0x80000000 != 0
// note: the offset is relative to the beginning of the resource section, not an RVA
entryOffsetToData := entry.OffsetToData & 0x7FFFFFFF
nameIsString := entry.Name&0x80000000 != 0
nameOffset := entry.Name & 0x7FFFFFFF
// read the string name of the resource directory
if nameIsString {
currentPos, err := sec.Reader.Seek(0, io.SeekCurrent)
if err != nil {
return fmt.Errorf("error getting current reader position: %w", err)
}
if _, err := sec.Reader.Seek(int64(nameOffset), io.SeekStart); err != nil {
return fmt.Errorf("error restoring reader position: %w", err)
}
name, err := readUTF16WithLength(sec.Reader)
if err == nil {
names.Add(name)
}
if _, err := sec.Reader.Seek(currentPos, io.SeekStart); err != nil {
return fmt.Errorf("error restoring reader position: %w", err)
}
}
if isDirectory {
subRVA := baseRVA + entryOffsetToData
if dirs.Has(subRVA) {
// some malware uses recursive PE references to evade analysis
return fmt.Errorf("recursive PE reference detected; skipping directory at baseRVA=0x%x subRVA=0x%x", baseRVA, subRVA)
}
dirs.Add(subRVA)
err := parseResourceDirectory(
&extractedSection{
RVA: subRVA,
BaseRVA: baseRVA,
Size: sec.Size - (sec.RVA - baseRVA),
Reader: sec.Reader,
},
dirs, fields, names)
if err != nil {
return err
}
return nil
}
return parseResourceDataEntry(sec.Reader, baseRVA, baseRVA+entryOffsetToData, sec.Size, fields)
}
func parseResourceDataEntry(reader *bytes.Reader, baseRVA, rva, remainingSize uint32, fields map[string]string) error {
var dataEntry peImageResourceDataEntry
offset := int64(rva - baseRVA)
if _, err := reader.Seek(offset, io.SeekStart); err != nil {
return fmt.Errorf("error seeking to data entry offset: %w", err)
}
if err := readIntoStruct(reader, &dataEntry); err != nil {
return fmt.Errorf("error reading resource data entry: %w", err)
}
if remainingSize < dataEntry.Size {
return fmt.Errorf("resource data entry size exceeds remaining size")
}
data := make([]byte, dataEntry.Size)
if _, err := reader.Seek(int64(dataEntry.OffsetToData-baseRVA), io.SeekStart); err != nil {
return fmt.Errorf("error seeking to resource data: %w", err)
}
if _, err := reader.Read(data); err != nil {
return fmt.Errorf("error reading resource data: %w", err)
}
return parseVersionResourceSection(bytes.NewReader(data), fields)
}
// parseVersionResourceSection parses a PE version resource section from within a resource directory.
//
// "The main structure in a version resource is the VS_FIXEDFILEINFO structure. Additional structures include the
// VarFileInfo structure to store language information data, and StringFileInfo for user-defined string information.
// All strings in a version resource are in Unicode format. Each block of information is aligned on a DWORD boundary."
//
// "VS_VERSIONINFO" (utf16)
// +---------------------------------------------------+
// | wLength (2 bytes) |
// | wValueLength (2 bytes) |
// | wType (2 bytes) |
// | szKey ("VS_VERSION_INFO") (utf16) |
// | Padding (to DWORD) |
// +---------------------------------------------------+
// | VS_FIXEDFILEINFO (52 bytes) |
// +---------------------------------------------------+
// | "StringFileInfo" (utf16) |
// +---------------------------------------------------+
// | wLength (2 bytes) |
// | wValueLength (2 bytes) |
// | wType (2 bytes) |
// | szKey ("StringFileInfo") (utf16) |
// | Padding (to DWORD) |
// | StringTable |
// | +--------------------------------------------+ |
// | | wLength (2 bytes) | |
// | | wValueLength (2 bytes) | |
// | | wType (2 bytes) | |
// | | szKey ("040904b0") | |
// | | Padding (to DWORD) | |
// | | String | |
// | | +--------------------------------------+ | |
// | | | wLength (2 bytes) | | |
// | | | wValueLength (2 bytes) | | |
// | | | wType (2 bytes) | | |
// | | | szKey ("FileVersion") | | |
// | | | Padding (to DWORD) | | |
// | | | szValue ("15.00.0913.015") | | |
// | | | Padding (to DWORD) | | |
// | +--------------------------------------------+ |
// +---------------------------------------------------+
// | VarFileInfo (utf16) |
// +---------------------------------------------------+
// | (skip!) |
// +---------------------------------------------------+
//
// sources:
// - https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
// - https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
// - https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
// - https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable
func parseVersionResourceSection(reader *bytes.Reader, fields map[string]string) error {
offset := 0
var info peVsVersionInfo
if szKey, err := readIntoStructAndSzKey(reader, &info, &offset); err != nil {
return fmt.Errorf("error reading PE version info: %v", err)
} else if szKey != "VS_VERSION_INFO" {
// this is a resource section, but not the version resources
return nil
}
if err := alignAndSeek(reader, &offset); err != nil {
return fmt.Errorf("error aligning past PE version info: %w", err)
}
var fixedFileInfo peVsFixedFileInfo
if err := readIntoStruct(reader, &fixedFileInfo, &offset); err != nil {
return fmt.Errorf("error reading PE FixedFileInfo: %v", err)
}
for reader.Len() > 0 {
if err := alignAndSeek(reader, &offset); err != nil {
return fmt.Errorf("error seeking to PE StringFileInfo: %w", err)
}
var sfiHeader peStringFileInfo
if szKey, err := readIntoStructAndSzKey(reader, &sfiHeader, &offset); err != nil {
return fmt.Errorf("error reading PE string file info header: %v", err)
} else if szKey != "StringFileInfo" {
// we only care about extracting strings from any string tables, skip this
offset += int(sfiHeader.ValueLength)
continue
}
var stOffset int
// note: the szKey for the prStringTable is the language
var stHeader peStringTable
if _, err := readIntoStructAndSzKey(reader, &stHeader, &offset, &stOffset); err != nil {
return fmt.Errorf("error reading PE string table header: %v", err)
}
for stOffset < int(stHeader.Length) {
var stringHeader peString
if err := readIntoStruct(reader, &stringHeader, &offset, &stOffset); err != nil {
break
}
key := readUTF16(reader, &offset, &stOffset)
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
return fmt.Errorf("error aligning to next PE string table value: %w", err)
}
var value string
if stringHeader.ValueLength > 0 {
value = readUTF16(reader, &offset, &stOffset)
}
fields[key] = value
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
return fmt.Errorf("error aligning to next PE string table key: %w", err)
}
}
}
if fields["FileVersion"] == "" {
// we can derive the file version from the fixed file info if it is not already specified as a string entry... neat!
fields["FileVersion"] = fmt.Sprintf("%d.%d.%d.%d",
fixedFileInfo.FileVersionMS>>16, fixedFileInfo.FileVersionMS&0xFFFF,
fixedFileInfo.FileVersionLS>>16, fixedFileInfo.FileVersionLS&0xFFFF)
}
return nil
}
// readIntoStructAndSzKey reads a struct from the reader and updates the offsets if provided, returning the szKey value.
// This is only useful in the context of the resource directory parsing in narrow cases (this is invalid to use outside of that context).
func readIntoStructAndSzKey[T any](reader *bytes.Reader, data *T, offsets ...*int) (string, error) {
if err := readIntoStruct(reader, data, offsets...); err != nil {
return "", err
}
return readUTF16(reader, offsets...), nil
}
// readIntoStruct reads a struct from the reader and updates the offsets if provided.
func readIntoStruct[T any](reader io.Reader, data *T, offsets ...*int) error {
if err := binary.Read(reader, binary.LittleEndian, data); err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
for i := range offsets {
*offsets[i] += binary.Size(*data)
}
return nil
}
// alignAndSeek aligns the reader to the next DWORD boundary and seeks to the new offset (updating any provided trackOffsets).
func alignAndSeek(reader io.Seeker, offset *int, trackOffsets ...*int) error {
ogOffset := *offset
*offset = alignToDWORD(*offset)
diff := *offset - ogOffset
for i := range trackOffsets {
*trackOffsets[i] += diff
}
_, err := reader.Seek(int64(*offset), io.SeekStart)
return err
}
// alignToDWORD aligns the offset to the next DWORD boundary (4 byte boundary)
func alignToDWORD(offset int) int {
return (offset + 3) & ^3
}
// readUTF16 is a helper function to read a null-terminated UTF16 string
func readUTF16(reader *bytes.Reader, offsets ...*int) string {
startPos, err := reader.Seek(0, io.SeekCurrent)
if err != nil {
return ""
}
var result []rune
for {
var char uint16
err := binary.Read(reader, binary.LittleEndian, &char)
if err != nil || char == 0 {
break
}
result = append(result, rune(char))
}
// calculate how many bytes we've actually read (including null terminator)
endPos, _ := reader.Seek(0, io.SeekCurrent)
bytesRead := int(endPos - startPos)
for i := range offsets {
*offsets[i] += bytesRead
}
return string(result)
}
// readUTF16WithLength reads a length-prefixed UTF-16 string from reader.
// The first 2 bytes represent the number of UTF-16 code units.
func readUTF16WithLength(reader *bytes.Reader) (string, error) {
var length uint16
if err := binary.Read(reader, binary.LittleEndian, &length); err != nil {
return "", err
}
if length == 0 {
return "", nil
}
// read length UTF-16 code units.
codes := make([]uint16, length)
if err := binary.Read(reader, binary.LittleEndian, &codes); err != nil {
return "", err
}
return string(utf16.Decode(codes)), nil
}
@@ -1,4 +1,4 @@
package binary
package binutils
import (
"bytes"
@@ -34,7 +34,7 @@ type Classifier struct {
// location. If the matcher returns a package, the file will be considered a candidate.
EvidenceMatcher EvidenceMatcher `json:"-"`
// Information below is used to specify the Package information when returned
// The information below is used to specify the Package information when returned
// Package is the name to use for the package
Package string `json:"package"`
@@ -72,16 +72,16 @@ func (cfg Classifier) MarshalJSON() ([]byte, error) {
}
// EvidenceMatcher is a function called to catalog Packages that match some sort of evidence
type EvidenceMatcher func(classifier Classifier, context matcherContext) ([]pkg.Package, error)
type EvidenceMatcher func(classifier Classifier, context MatcherContext) ([]pkg.Package, error)
type matcherContext struct {
resolver file.Resolver
location file.Location
getReader func(resolver matcherContext) (unionreader.UnionReader, error)
type MatcherContext struct {
Resolver file.Resolver
Location file.Location
GetReader func(resolver MatcherContext) (unionreader.UnionReader, error)
}
func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
func EvidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
for _, matcher := range matchers {
match, err := matcher(classifier, context)
if err != nil {
@@ -95,14 +95,26 @@ func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
}
}
func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher {
type ContextualEvidenceMatchers struct {
CatalogerName string
}
func (c ContextualEvidenceMatchers) FileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher {
return FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, c.CatalogerName)
}
func (c ContextualEvidenceMatchers) FileContentsVersionMatcher(pattern string) EvidenceMatcher {
return FileContentsVersionMatcher(pattern, c.CatalogerName)
}
func FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, catalogerName string) EvidenceMatcher {
pat := regexp.MustCompile(fileNamePattern)
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
if !pat.MatchString(context.location.RealPath) {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
if !pat.MatchString(context.Location.RealPath) {
return nil, nil
}
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, context.location.RealPath)
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, context.Location.RealPath)
// versions like 3.5 should not match any character, but explicit dot
for k, v := range filepathNamedGroupValues {
@@ -135,7 +147,7 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
return nil, fmt.Errorf("unable to match version: %w", err)
}
p := newClassifierPackage(classifier, context.location, matchMetadata)
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
if p == nil {
return nil, nil
}
@@ -144,9 +156,9 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
}
}
func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
func FileContentsVersionMatcher(pattern, catalogerName string) EvidenceMatcher {
pat := regexp.MustCompile(pattern)
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
contents, err := getReader(context)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
@@ -173,7 +185,7 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
}
}
p := newClassifierPackage(classifier, context.location, matchMetadata)
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
if p == nil {
return nil, nil
}
@@ -182,14 +194,14 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
}
}
// matchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
// MatchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
// anything that the matcher would otherwise return
func matchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
func MatchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
var nonMatchPatterns []*regexp.Regexp
for _, p := range contentPatternsToExclude {
nonMatchPatterns = append(nonMatchPatterns, regexp.MustCompile(p))
}
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
contents, err := getReader(context)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
@@ -205,9 +217,9 @@ func matchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string)
}
}
func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
pat := regexp.MustCompile(sharedLibraryPattern)
return func(classifier Classifier, context matcherContext) (packages []pkg.Package, _ error) {
return func(classifier Classifier, context MatcherContext) (packages []pkg.Package, _ error) {
libs, err := sharedLibraries(context)
if err != nil {
return nil, err
@@ -217,24 +229,24 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
continue
}
locations, err := context.resolver.FilesByGlob("**/" + lib)
locations, err := context.Resolver.FilesByGlob("**/" + lib)
if err != nil {
return nil, err
}
for _, libraryLocation := range locations {
newResolver := matcherContext{
resolver: context.resolver,
location: libraryLocation,
getReader: context.getReader,
newResolver := MatcherContext{
Resolver: context.Resolver,
Location: libraryLocation,
GetReader: context.GetReader,
}
newResolver.location = libraryLocation
newResolver.Location = libraryLocation
pkgs, err := sharedLibraryMatcher(classifier, newResolver)
if err != nil {
return nil, err
}
for _, p := range pkgs {
// set the source binary as the first location
locationSet := file.NewLocationSet(context.location)
locationSet := file.NewLocationSet(context.Location)
locationSet.Add(p.Locations.ToSlice()...)
p.Locations = locationSet
meta, _ := p.Metadata.(pkg.BinarySignature)
@@ -242,7 +254,7 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
Matches: append([]pkg.ClassifierMatch{
{
Classifier: classifier.Class,
Location: context.location,
Location: context.Location,
},
}, meta.Matches...),
}
@@ -254,19 +266,11 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
}
}
func mustPURL(purl string) packageurl.PackageURL {
p, err := packageurl.FromString(purl)
if err != nil {
panic(fmt.Sprintf("invalid PURL: %s", p))
func getReader(context MatcherContext) (unionreader.UnionReader, error) {
if context.GetReader != nil {
return context.GetReader(context)
}
return p
}
func getReader(context matcherContext) (unionreader.UnionReader, error) {
if context.getReader != nil {
return context.getReader(context)
}
reader, err := context.resolver.FileContentsByLocation(context.location) //nolint:gocritic
reader, err := context.Resolver.FileContentsByLocation(context.Location) //nolint:gocritic
if err != nil {
return nil, err
}
@@ -274,32 +278,20 @@ func getReader(context matcherContext) (unionreader.UnionReader, error) {
return unionreader.GetUnionReader(reader)
}
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
// cpe string cannot be parsed into valid CPE Attributes
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
src := cpe.GeneratedSource
if len(source) > 0 {
src = source[0]
}
return []cpe.CPE{
cpe.Must(cpeString, src),
}
}
// sharedLibraries returns a list of all shared libraries found within a binary, currently
// supporting: elf, macho, and windows pe
func sharedLibraries(context matcherContext) ([]string, error) {
func sharedLibraries(context MatcherContext) ([]string, error) {
contents, err := getReader(context)
if err != nil {
return nil, err
}
defer internal.CloseAndLogError(contents, context.location.RealPath)
defer internal.CloseAndLogError(contents, context.Location.RealPath)
e, _ := elf.NewFile(contents)
if e != nil {
symbols, err := e.ImportedLibraries()
if err != nil {
log.Debugf("unable to read elf binary at: %s -- %s", context.location.RealPath, err)
log.Debugf("unable to read elf binary at: %s -- %s", context.Location.RealPath, err)
}
return symbols, nil
}
@@ -311,7 +303,7 @@ func sharedLibraries(context matcherContext) ([]string, error) {
if m != nil {
symbols, err := m.ImportedLibraries()
if err != nil {
log.Debugf("unable to read macho binary at: %s -- %s", context.location.RealPath, err)
log.Debugf("unable to read macho binary at: %s -- %s", context.Location.RealPath, err)
}
return symbols, nil
}
@@ -323,7 +315,7 @@ func sharedLibraries(context matcherContext) ([]string, error) {
if p != nil {
symbols, err := p.ImportedLibraries()
if err != nil {
log.Debugf("unable to read pe binary at: %s -- %s", context.location.RealPath, err)
log.Debugf("unable to read pe binary at: %s -- %s", context.Location.RealPath, err)
}
return symbols, nil
}
@@ -1,4 +1,4 @@
package binary
package binutils
import (
"reflect"
@@ -11,7 +11,7 @@ import (
var emptyPURL = packageurl.PackageURL{}
func newClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string) *pkg.Package {
func NewClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string, catalogerName string) *pkg.Package {
version, ok := matchMetadata["version"]
if !ok {
return nil
@@ -1,4 +1,4 @@
package binary
package binutils
import (
"bytes"
@@ -27,7 +27,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
CPEs: []cpe.CPE{},
},
cpes: nil,
@@ -38,7 +38,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
@@ -53,7 +53,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
@@ -70,7 +70,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version-parts.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`, "cataloger-name"),
CPEs: []cpe.CPE{},
},
cpes: nil,
@@ -84,7 +84,7 @@ func Test_ClassifierCPEs(t *testing.T) {
require.NoError(t, err)
require.Len(t, ls, 1)
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, matcherContext{resolver: resolver, location: ls[0]})
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, MatcherContext{Resolver: resolver, Location: ls[0]})
require.NoError(t, err)
require.Len(t, pkgs, 1)
@@ -113,7 +113,7 @@ func TestClassifier_MarshalJSON(t *testing.T) {
classifier: Classifier{
Class: "class",
FileGlob: "glob",
EvidenceMatcher: FileContentsVersionMatcher(".thing"),
EvidenceMatcher: FileContentsVersionMatcher(".thing", "cataloger-name"),
Package: "pkg",
PURL: packageurl.PackageURL{
Type: "type",
@@ -165,12 +165,12 @@ func TestFileContentsVersionMatcher(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockGetContent := func(context matcherContext) (unionreader.UnionReader, error) {
mockGetContent := func(context MatcherContext) (unionreader.UnionReader, error) {
return unionreader.GetUnionReader(io.NopCloser(bytes.NewBufferString(tt.data)))
}
fn := FileContentsVersionMatcher(tt.pattern)
p, err := fn(Classifier{}, matcherContext{
getReader: mockGetContent,
fn := FileContentsVersionMatcher(tt.pattern, "cataloger-name")
p, err := fn(Classifier{}, MatcherContext{
GetReader: mockGetContent,
})
if err != nil {
@@ -486,7 +486,6 @@ var defaultCandidateAdditions = buildCandidateLookup(
candidateKey{PkgName: "dnsmasq", Vendor: "dnsmasq"},
candidateAddition{AdditionalVendors: []string{"thekelleys"}},
},
//
// Binary packages
{
pkg.BinaryPkg,
@@ -630,7 +629,38 @@ var defaultCandidateRemovals = buildCandidateRemovalLookup(
candidateKey{PkgName: "grpc"},
candidateRemovals{ProductsToRemove: []string{"grpc"}},
},
// PHP Packages
// Rust packages
{
pkg.RustPkg,
candidateKey{PkgName: "hyper"},
// Avoid matching CVE-2024-23741
candidateRemovals{VendorsToRemove: []string{"vercel"}},
},
{
pkg.RustPkg,
candidateKey{PkgName: "opentelemetry"},
// Avoid matching CVE-2023-45142
candidateRemovals{ProductsToRemove: []string{"opentelemetry"}},
},
{
pkg.RustPkg,
candidateKey{PkgName: "prometheus"},
// Avoid matching CVE-2019-3826
candidateRemovals{VendorsToRemove: []string{"prometheus"}},
},
{
pkg.RustPkg,
candidateKey{PkgName: "phf"},
// Avoid matching CVE-2000-1186
candidateRemovals{VendorsToRemove: []string{"phf"}},
},
{
pkg.RustPkg,
candidateKey{PkgName: "redis"},
// Avoid matching CVE-2022-24735
candidateRemovals{VendorsToRemove: []string{"redis"}},
},
// PHP packages
{
pkg.PhpPearPkg,
candidateKey{PkgName: "redis"},
@@ -3848,6 +3848,9 @@
"localhost-now": [
"cpe:2.3:a:localhost-now_project:localhost-now:*:*:*:*:*:node.js:*:*"
],
"lockfile-lint-api": [
"cpe:2.3:a:lirantal:lockfile-lint-api:*:*:*:*:*:node.js:*:*"
],
"locutus": [
"cpe:2.3:a:locutus:locutus:*:*:*:*:*:node.js:*:*"
],
@@ -7072,6 +7075,9 @@
"versionize": [
"cpe:2.3:a:versionize_project:versionize:*:*:*:*:*:rust:*:*"
],
"wasmi": [
"cpe:2.3:a:wasmi-labs:wasmi:*:*:*:*:*:rust:*:*"
],
"wasmtime": [
"cpe:2.3:a:bytecodealliance:wasmtime:*:*:*:*:*:rust:*:*"
],
@@ -7319,6 +7325,9 @@
"aco-product-labels-for-woocommerce": [
"cpe:2.3:a:acowebs:product_labels_for_woocommerce_\\(sale_badges\\):*:*:*:*:*:wordpress:*:*"
],
"aco-woo-dynamic-pricing": [
"cpe:2.3:a:acowebs:dynamic_pricing_with_discount_rules_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"actionwear-products-sync": [
"cpe:2.3:a:marcoingraiti:actionwear_products_sync:*:*:*:*:*:wordpress:*:*"
],
@@ -7473,6 +7482,10 @@
"admin-side-data-storage-for-contact-form-7": [
"cpe:2.3:a:zestard:admin_side_data_storage_for_contact_form_7:*:*:*:*:*:wordpress:*:*"
],
"admin-site-enhancements": [
"cpe:2.3:a:wpase:admin_and_site_enhancements:*:*:*:*:free:wordpress:*:*",
"cpe:2.3:a:wpase:admin_and_site_enhancements:*:*:*:*:pro:wordpress:*:*"
],
"adminer": [
"cpe:2.3:a:adminer_login_project:adminer_login:*:*:*:*:*:wordpress:*:*"
],
@@ -7539,6 +7552,7 @@
"advanced-custom-fields": [
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:-:wordpress:*:*",
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:free:wordpress:*:*",
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:pro:wordpress:*:*"
],
"advanced-database-cleaner": [
@@ -7668,6 +7682,9 @@
"ajax-bootmodal-login": [
"cpe:2.3:a:ajax_bootmodal_login_project:ajax_bootmodal_login:*:*:*:*:*:wordpress:*:*"
],
"ajax-filter-posts": [
"cpe:2.3:a:addonmaster:post_grid_master:*:*:*:*:*:wordpress:*:*"
],
"ajax-load-more": [
"cpe:2.3:a:connekthq:ajax_load_more:*:*:*:*:*:wordpress:*:*"
],
@@ -7873,6 +7890,9 @@
"animate-it": [
"cpe:2.3:a:eleopard:animate_it\\!:*:*:*:*:*:wordpress:*:*"
],
"animated-al-list": [
"cpe:2.3:a:alexdtn:animated_al_list:*:*:*:*:*:wordpress:*:*"
],
"animated-counters": [
"cpe:2.3:a:eralion:animated_counters:*:*:*:*:*:wordpress:*:*"
],
@@ -8018,6 +8038,9 @@
"arprice-responsive-pricing-table": [
"cpe:2.3:a:reputeinfosystems:arprice_lite:*:*:*:*:*:wordpress:*:*"
],
"artibot": [
"cpe:2.3:a:artibot:artibot:*:*:*:*:*:wordpress:*:*"
],
"article-directory": [
"cpe:2.3:a:article_directory_project:article_directory:*:*:*:*:*:wordpress:*:*"
],
@@ -8109,6 +8132,9 @@
"auto-hyperlink-urls": [
"cpe:2.3:a:auto-hyperlink_urls_project:auto-hyperlink_urls:*:*:*:*:*:wordpress:*:*"
],
"auto-iframe": [
"cpe:2.3:a:toolstack:auto_iframe:*:*:*:*:*:wordpress:*:*"
],
"auto-limit-posts-reloaded": [
"cpe:2.3:a:thefreewindows:auto_limit_posts_reloaded:*:*:*:*:*:wordpress:*:*"
],
@@ -8201,6 +8227,9 @@
"awesome-weather": [
"cpe:2.3:a:awesome_weather_widget_project:awesome_weather_widget:*:*:*:*:*:wordpress:*:*"
],
"awin-advertiser-tracking": [
"cpe:2.3:a:awin:awin_-_advertiser_tracking_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"awin-data-feed": [
"cpe:2.3:a:awin:awin_data_feed:*:*:*:*:*:wordpress:*:*"
],
@@ -8359,6 +8388,9 @@
"cpe:2.3:a:web-x:be-popia-compliant:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:web-x:be_popia_compliant:*:*:*:*:*:wordpress:*:*"
],
"beaf-before-and-after-gallery": [
"cpe:2.3:a:themefic:ultimate_before_after_image_slider_\\\u0026_gallery:*:*:*:*:*:wordpress:*:*"
],
"beam-me-up-scotty": [
"cpe:2.3:a:outtheboxthemes:beam_me_up_scotty:*:*:*:*:*:wordpress:*:*"
],
@@ -8801,6 +8833,9 @@
"button-block": [
"cpe:2.3:a:bplugins:button_block:*:*:*:*:*:wordpress:*:*"
],
"button-contact-vr": [
"cpe:2.3:a:buttonizer:call_\\/_chat_\\/_contact_button:*:*:*:*:*:wordpress:*:*"
],
"button-generation": [
"cpe:2.3:a:wow-company:button_generator:*:*:*:*:*:wordpress:*:*"
],
@@ -8933,6 +8968,9 @@
"canto": [
"cpe:2.3:a:canto:canto:*:*:*:*:*:wordpress:*:*"
],
"canvasflow": [
"cpe:2.3:a:canvasflow:canvasflow:*:*:*:*:*:wordpress:*:*"
],
"canvasio3d-light": [
"cpe:2.3:a:virtuellwerk:canvasio3d_light:*:*:*:*:*:wordpress:*:*"
],
@@ -9080,6 +9118,9 @@
"cf7-store-to-db-lite": [
"cpe:2.3:a:accesspressthemes:form_store_to_db:*:*:*:*:*:wordpress:*:*"
],
"cf7-submission-dom-tracking": [
"cpe:2.3:a:apasionados:submission_dom_tracking_for_contact_form_7:*:*:*:*:*:wordpress:*:*"
],
"cf7-widget-elementor": [
"cpe:2.3:a:voidcoders:void_contact_form_7_widget_for_elementor_page_builder:*:*:*:*:*:wordpress:*:*"
],
@@ -9126,7 +9167,7 @@
"cpe:2.3:a:bluecoral:chat_bubble:*:*:*:*:*:wordpress:*:*"
],
"chatbot": [
"cpe:2.3:a:quantumcloud:ai_chatbot:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:quantumcloud:wpbot:*:*:*:*:free:wordpress:*:*"
],
"chatbot-chatgpt": [
"cpe:2.3:a:kognetics:kognetiks_chatbot:*:*:*:*:*:wordpress:*:*",
@@ -9221,6 +9262,9 @@
"clickdesk-live-support-chat-plugin": [
"cpe:2.3:a:clickdesk:clickdesk:*:*:*:*:*:wordpress:*:*"
],
"clickwhale": [
"cpe:2.3:a:flowdee:clickwhale:*:*:*:*:*:wordpress:*:*"
],
"clictracker": [
"cpe:2.3:a:wp_clictracker_project:wp_clictracker:*:*:*:*:*:wordpress:*:*"
],
@@ -9266,12 +9310,19 @@
"cm-download-manager": [
"cpe:2.3:a:cminds:cm_download_manager:*:*:*:*:*:wordpress:*:*"
],
"cm-email-blacklist": [
"cpe:2.3:a:cminds:cm_e-mail_blacklist:*:*:*:*:*:wordpress:*:*"
],
"cm-on-demand-search-and-replace": [
"cpe:2.3:a:cminds:cm_on_demand_search_and_replace:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:cminds:cm_on_demand_search_and_replace:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:cminds:cm_search_and_replace:*:*:*:*:*:wordpress:*:*"
],
"cm-pop-up-banners": [
"cpe:2.3:a:cminds:cm_popup:*:*:*:*:*:wordpress:*:*"
],
"cm-table-of-content": [
"cpe:2.3:a:cminds:cm_table_of_contents:*:*:*:*:*:wordpress:*:*"
],
"cm-video-lesson-manager": [
"cpe:2.3:a:creativemindssolutions:video_lessons_manager:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:creativemindssolutions:video_lessons_manager_pro:*:*:*:*:*:wordpress:*:*"
@@ -9330,6 +9381,9 @@
"colorful-categories": [
"cpe:2.3:a:gesundheit-bewegt:colorful_categories:*:*:*:*:*:wordpress:*:*"
],
"colorlib-coming-soon-maintenance": [
"cpe:2.3:a:colorlib:coming_soon_\\\u0026_maintenance_mode:*:*:*:*:*:wordpress:*:*"
],
"column-matic": [
"cpe:2.3:a:column-matic_project:column-matic:*:*:*:*:*:wordpress:*:*"
],
@@ -9701,6 +9755,9 @@
"core-web-vitals-pagespeed-booster": [
"cpe:2.3:a:magazine3:core_web_vitals_\\\u0026_pagespeed_booster:*:*:*:*:*:wordpress:*:*"
],
"coreactivity": [
"cpe:2.3:a:dev4press:coreactivity:*:*:*:*:*:wordpress:*:*"
],
"corner-ad": [
"cpe:2.3:a:dwbooster:corner_ad:*:*:*:*:*:wordpress:*:*"
],
@@ -9711,7 +9768,7 @@
"cpe:2.3:a:marcorulicke:coru_lfmember:*:*:*:*:*:wordpress:*:*"
],
"cost-calculator-builder": [
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:free:wordpress:*:*"
],
"countdown-block": [
"cpe:2.3:a:wpdeveloper:countdown_block:*:*:*:*:*:wordpress:*:*"
@@ -10063,6 +10120,9 @@
"cpe:2.3:a:fastlinemedia:customizer_export\\/import:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpbeaverbuilder:customizer_export\\/import:*:*:*:*:*:wordpress:*:*"
],
"cww-companion": [
"cpe:2.3:a:codeworkweb:cww_companion:*:*:*:*:*:wordpress:*:*"
],
"cybersoldier": [
"cpe:2.3:a:webbigt:cybersoldier:*:*:*:*:*:wordpress:*:*"
],
@@ -10159,7 +10219,8 @@
"cpe:2.3:a:webtoffee:decorator:*:*:*:*:*:wordpress:*:*"
],
"defender-security": [
"cpe:2.3:a:wmpudev:defender_security:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpmudev:defender:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpmudev:defender:*:*:*:*:free:wordpress:*:*",
"cpe:2.3:a:wpmudev:defender_security:*:*:*:*:*:wordpress:*:*"
],
"definitive-addons-for-elementor": [
@@ -10287,9 +10348,15 @@
"dmsguestbook": [
"cpe:2.3:a:dmsguestbook_project:dmsguestbook:*:*:*:*:*:wordpress:*:*"
],
"dn-footer-contacts": [
"cpe:2.3:a:digireturn:footer_contacts_bar:*:*:*:*:*:wordpress:*:*"
],
"dn-popup": [
"cpe:2.3:a:digireturn:dn_popup:*:*:*:*:*:wordpress:*:*"
],
"dn-shipping-by-weight": [
"cpe:2.3:a:digireturn:shipping_by_weight_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"document-emberdder": [
"cpe:2.3:a:bplugins:document_embedder:*:*:*:*:*:wordpress:*:*"
],
@@ -10307,7 +10374,7 @@
"cpe:2.3:a:wedevs:dokan:*:*:*:*:*:wordpress:*:*"
],
"dokan-pro": [
"cpe:2.3:a:dokan:dokan_pro_plugin:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:dokan:dokan:*:*:*:*:pro:wordpress:*:*"
],
"dologin": [
"cpe:2.3:a:wpdo:dologin_security:*:*:*:*:*:wordpress:*:*"
@@ -10568,6 +10635,9 @@
"easy-newsletter-signups": [
"cpe:2.3:a:alphabpo:easy_newsletter_signups:*:*:*:*:*:wordpress:*:*"
],
"easy-notify-lite": [
"cpe:2.3:a:ghozylab:popup_builder:*:*:*:*:*:wordpress:*:*"
],
"easy-paypal-donation": [
"cpe:2.3:a:wpplugin:accept_donations_with_paypal:*:*:*:*:*:wordpress:*:*"
],
@@ -10610,6 +10680,9 @@
"easy-table": [
"cpe:2.3:a:easy_table_project:easy_table:*:*:*:*:*:wordpress:*:*"
],
"easy-table-of-contents": [
"cpe:2.3:a:magazine3:easy_table_of_contents:*:*:*:*:*:wordpress:*:*"
],
"easy-testimonial-manager": [
"cpe:2.3:a:easy_testimonial_manager_project:easy_testimonial_manager:*:*:*:*:*:wordpress:*:*"
],
@@ -10718,6 +10791,9 @@
"eg-attachments": [
"cpe:2.3:a:egeorjon:eg-attachments:*:*:*:*:*:wordpress:*:*"
],
"ekc-tournament-manager": [
"cpe:2.3:a:lukashuser:ekc_tournament_manager:*:*:*:*:*:wordpress:*:*"
],
"elastic-email-sender": [
"cpe:2.3:a:elasticemail:elastic_email_sender:*:*:*:*:*:wordpress:*:*"
],
@@ -10952,7 +11028,8 @@
"cpe:2.3:a:estatik:estatik:*:*:*:*:*:wordpress:*:*"
],
"estatik-mortgage-calculator": [
"cpe:2.3:a:estatik:estatik_mortgage_calculator:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:estatik:estatik_mortgage_calculator:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:estatik:mortgage_calculator:*:*:*:*:*:wordpress:*:*"
],
"etsy-shop": [
"cpe:2.3:a:etsy_shop_project:etsy_shop:*:*:*:*:*:wordpress:*:*"
@@ -11209,6 +11286,9 @@
"fast-search-powered-by-solr": [
"cpe:2.3:a:fast-search-powered-by-solr_project:fast-search-powered-by-solr:*:*:*:*:*:wordpress:*:*"
],
"fast-tube": [
"cpe:2.3:a:caspie:fast_tube:*:*:*:*:*:wordpress:*:*"
],
"fast-velocity-minify": [
"cpe:2.3:a:fastvelocity:minify:*:*:*:*:*:wordpress:*:*"
],
@@ -11255,6 +11335,9 @@
"featured-image-from-url": [
"cpe:2.3:a:fifu:featured_image_from_url:*:*:*:*:*:wordpress:*:*"
],
"featured-image-plus": [
"cpe:2.3:a:krasenslavov:featured_image_plus:*:*:*:*:*:wordpress:*:*"
],
"feed-changer": [
"cpe:2.3:a:wp-master:feed_changer_\\\u0026_remover:*:*:*:*:*:wordpress:*:*"
],
@@ -11276,6 +11359,9 @@
"feedzy-rss-feeds": [
"cpe:2.3:a:themeisle:rss_aggregator_by_feedzy:*:*:*:*:*:wordpress:*:*"
],
"fetch-jft": [
"cpe:2.3:a:pjaudiomv:fetch_jft:*:*:*:*:*:wordpress:*:*"
],
"fg-drupal-to-wp": [
"cpe:2.3:a:fredericgilles:fg_drupal:*:*:*:*:*:wordpress:*:*"
],
@@ -11342,6 +11428,7 @@
"cpe:2.3:a:find_my_blocks_project:find_my_blocks:*:*:*:*:*:wordpress:*:*"
],
"first-graders-toolbox": [
"cpe:2.3:a:atakanau:1_click_disable_all:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:atakanau:one_click_disable_all:*:*:*:*:*:wordpress:*:*"
],
"fish-and-ships": [
@@ -11400,6 +11487,9 @@
"floating-button": [
"cpe:2.3:a:wow-company:floating_button:*:*:*:*:*:wordpress:*:*"
],
"floating-contact": [
"cpe:2.3:a:just-a-web-developer:floating_contact_button:*:*:*:*:*:wordpress:*:*"
],
"floating-div": [
"cpe:2.3:a:floating_div_project:floating_div:*:*:*:*:*:wordpress:*:*"
],
@@ -11449,6 +11539,9 @@
"fluid-responsive-slideshow": [
"cpe:2.3:a:tonjoostudio:fluid-responsive-slideshow:*:*:*:*:*:wordpress:*:*"
],
"flynax-bridge": [
"cpe:2.3:a:flynax:flynax_bridge:*:*:*:*:*:wordpress:*:*"
],
"folders": [
"cpe:2.3:a:premio:folders:*:*:*:*:*:wordpress:*:*"
],
@@ -11823,6 +11916,9 @@
"cpe:2.3:a:ghost:ghost:*:*:*:*:*:node.js:*:*",
"cpe:2.3:a:ghost:ghost:*:*:*:*:*:wordpress:*:*"
],
"gianism": [
"cpe:2.3:a:takahashifumiki:gianism:*:*:*:*:*:wordpress:*:*"
],
"gift-certificate-creator": [
"cpe:2.3:a:bobcares:gift-certificate-creator:*:*:*:*:*:wordpress:*:*"
],
@@ -12045,7 +12141,8 @@
"cpe:2.3:a:groundhogg:groundhogg:*:*:*:*:*:wordpress:*:*"
],
"gs-logo-slider": [
"cpe:2.3:a:gsplugins:gs_logo_slider:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:gsplugins:gs_logo_slider:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:gsplugins:logo_slider:*:*:*:*:*:wordpress:*:*"
],
"gs-portfolio": [
"cpe:2.3:a:gsplugins:gs_filterable_portfolio:*:*:*:*:*:wordpress:*:*"
@@ -12399,12 +12496,16 @@
"ideapush": [
"cpe:2.3:a:northernbeacheswebsites:ideapush:*:*:*:*:*:wordpress:*:*"
],
"idonate": [
"cpe:2.3:a:themeatelier:idonate:*:*:*:*:*:wordpress:*:*"
],
"idx-broker-platinum": [
"cpe:2.3:a:idxbroker:idx_broker:*:*:*:*:platinum:wordpress:*:*",
"cpe:2.3:a:idxbroker:impress_for_idx_broker:*:*:*:*:platinum:wordpress:*:*"
],
"if-so": [
"cpe:2.3:a:if-so:dynamic_content_personalization:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:if-so:dynamic_content_personalization:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:if-so:if-so:*:*:*:*:*:wordpress:*:*"
],
"ifeature-slider": [
"cpe:2.3:a:cyberchimps:ifeature_slider:*:*:*:*:*:wordpress:*:*"
@@ -12494,6 +12595,9 @@
"image-vertical-reel-scroll-slideshow": [
"cpe:2.3:a:gopiplus:image_vertical_reel_scroll_slideshow:*:*:*:*:*:wordpress:*:*"
],
"image-widget": [
"cpe:2.3:a:stellarwp:image_widget:*:*:*:*:*:wordpress:*:*"
],
"imageboss": [
"cpe:2.3:a:imageboss:imageboss:*:*:*:*:*:wordpress:*:*"
],
@@ -12650,6 +12754,9 @@
"instant-images": [
"cpe:2.3:a:connekthq:instant_images_-_one_click_unsplash_uploads:*:*:*:*:*:wordpress:*:*"
],
"instantio": [
"cpe:2.3:a:themefic:instantio:*:*:*:*:*:wordpress:*:*"
],
"instawp-connect": [
"cpe:2.3:a:instawp:instawp_connect:*:*:*:*:*:wordpress:*:*"
],
@@ -12782,6 +12889,12 @@
"jetformbuilder": [
"cpe:2.3:a:crocoblock:jetformbuilder:*:*:*:*:*:wordpress:*:*"
],
"jetpack": [
"cpe:2.3:a:automattic:jetpack:*:*:*:*:*:wordpress:*:*"
],
"jetpack-boost": [
"cpe:2.3:a:automattic:jetpack_boost:*:*:*:*:*:wordpress:*:*"
],
"jetwidgets-for-elementor": [
"cpe:2.3:a:crocoblock:jetwidgets_for_elementor:*:*:*:*:*:wordpress:*:*"
],
@@ -12887,6 +13000,9 @@
"just-tables": [
"cpe:2.3:a:hasthemes:justtables:*:*:*:*:*:wordpress:*:*"
],
"justrows-free": [
"cpe:2.3:a:canaveralstudio:justrows_free:*:*:*:*:*:wordpress:*:*"
],
"jvm-rich-text-icons": [
"cpe:2.3:a:jorisvm:jvm_gutenberg_rich_text_icons:*:*:*:*:*:wordpress:*:*"
],
@@ -12920,6 +13036,9 @@
"kb-support": [
"cpe:2.3:a:logon:kb_support:*:*:*:*:*:wordpress:*:*"
],
"kbucket": [
"cpe:2.3:a:optimalaccess:kbucket:*:*:*:*:*:wordpress:*:*"
],
"keep-backup-daily": [
"cpe:2.3:a:androidbubbles:keep_backup_daily:*:*:*:*:*:wordpress:*:*"
],
@@ -12959,6 +13078,9 @@
"kk-star-ratings": [
"cpe:2.3:a:kamalkhan:kk_star_ratings:*:*:*:*:*:wordpress:*:*"
],
"kkprogressbar": [
"cpe:2.3:a:krzysztof-furtak:kkprogressbar2:*:*:*:*:free:wordpress:*:*"
],
"klarna-checkout-for-woocommerce": [
"cpe:2.3:a:klarna:klarna_checkout_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -13122,6 +13244,9 @@
"legal-pages": [
"cpe:2.3:a:wpwax:legal_pages:*:*:*:*:*:wordpress:*:*"
],
"legull": [
"cpe:2.3:a:alwayscurious:legull:*:*:*:*:*:wordpress:*:*"
],
"leira-cron-jobs": [
"cpe:2.3:a:leira:cron_jobs:*:*:*:*:*:wordpress:*:*"
],
@@ -13340,6 +13465,9 @@
"logwpmail": [
"cpe:2.3:a:premierethemes:log_wp_mail:*:*:*:*:*:wordpress:*:*"
],
"lokalyze-call-now": [
"cpe:2.3:a:lokalyze:call_me_now:*:*:*:*:*:wordpress:*:*"
],
"ltl-freight-quotes-unishippers-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:unishippers:wordpress:*:*"
],
@@ -13434,7 +13562,7 @@
"cpe:2.3:a:mailoptin:mailoptin:*:*:*:*:*:wordpress:*:*"
],
"mailpoet": [
"cpe:2.3:a:mailpoet:mailpoet:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:automattic:mailpoet:*:*:*:*:free:wordpress:*:*"
],
"mailtree-log-mail": [
"cpe:2.3:a:oacstudio:mailtree_log_mail:*:*:*:*:*:wordpress:*:*"
@@ -13442,6 +13570,9 @@
"maintenance": [
"cpe:2.3:a:webfactoryltd:maintenance:*:*:*:*:*:wordpress:*:*"
],
"maintenance-notice": [
"cpe:2.3:a:codevibrant:maintenance_notice:*:*:*:*:*:wordpress:*:*"
],
"maintenance-page": [
"cpe:2.3:a:themegrill:maintenance_page:*:*:*:*:*:wordpress:*:*"
],
@@ -13610,6 +13741,9 @@
"media-library-plus": [
"cpe:2.3:a:maxfoundry:media_library_folders:*:*:*:*:*:wordpress:*:*"
],
"media-library-tools": [
"cpe:2.3:a:wptinysolutions:media_library_tools:*:*:*:*:*:wordpress:*:*"
],
"media-list": [
"cpe:2.3:a:drelton:medialist:*:*:*:*:*:wordpress:*:*"
],
@@ -13723,6 +13857,9 @@
"message-ticker": [
"cpe:2.3:a:gopiplus:message_ticker:*:*:*:*:*:wordpress:*:*"
],
"meta-box": [
"cpe:2.3:a:metabox:meta_box:*:*:*:*:*:wordpress:*:*"
],
"meta-store-elements": [
"cpe:2.3:a:mysticalthemes:meta_store_elements:*:*:*:*:*:wordpress:*:*"
],
@@ -13984,6 +14121,9 @@
"cpe:2.3:a:codepeople:music_store:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:musicstore:music_store:*:*:*:*:*:wordpress:*:*"
],
"muslim-prayer-time-bd": [
"cpe:2.3:a:realwebcare:muslim_prayer_time_bd:*:*:*:*:*:wordpress:*:*"
],
"mw-wp-form": [
"cpe:2.3:a:web-soudan:mw_wp_form:*:*:*:*:*:wordpress:*:*"
],
@@ -14165,6 +14305,9 @@
"newsletter-optin-box": [
"cpe:2.3:a:noptin:noptin:*:*:*:*:*:wordpress:*:*"
],
"newsletter-popup": [
"cpe:2.3:a:mndpsingh287:newsletter_popup:*:*:*:*:*:wordpress:*:*"
],
"newsletters-lite": [
"cpe:2.3:a:tribulant:newsletters:*:*:*:*:*:wordpress:*:*"
],
@@ -14426,6 +14569,12 @@
"opening-hours": [
"cpe:2.3:a:designextreme:we\\'re_open\\!:*:*:*:*:*:wordpress:*:*"
],
"openpgp-form-encryption": [
"cpe:2.3:a:arnesonium:openpgp_form_encryption:*:*:*:*:*:wordpress:*:*"
],
"opensheetmusicdisplay": [
"cpe:2.3:a:opensheetmusicdisplay:opensheetmusicdisplay:*:*:*:*:*:wordpress:*:*"
],
"ops-robots-txt": [
"cpe:2.3:a:ops-robots-txt_project:ops-robots-txt:*:*:*:*:*:wordpress:*:*"
],
@@ -14561,6 +14710,9 @@
"pagepost-content-shortcode": [
"cpe:2.3:a:page\\/post_content_shortcode_project:page\\/post_content_shortcode:*:*:*:*:*:wordpress:*:*"
],
"pagerank-tools": [
"cpe:2.3:a:mahype:pagerank_tools:*:*:*:*:*:wordpress:*:*"
],
"pagination": [
"cpe:2.3:a:bestwebsoft:pagination:*:*:*:*:*:wordpress:*:*"
],
@@ -14769,6 +14921,9 @@
"cpe:2.3:a:greentreelabs:gallery_photoblocks:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpchill:gallery_photoblocks:*:*:*:*:*:wordpress:*:*"
],
"photoshow": [
"cpe:2.3:a:codepeople:smart_image_gallery:*:*:*:*:*:wordpress:*:*"
],
"photospace": [
"cpe:2.3:a:photospace_gallery_project:photospace_gallery:*:*:*:*:*:wordpress:*:*"
],
@@ -15012,6 +15167,9 @@
"post-highlights": [
"cpe:2.3:a:post_highlights_projects:post_highlights:*:*:*:*:*:wordpress:*:*"
],
"post-in-page-for-elementor": [
"cpe:2.3:a:migaweb:post_in_page_for_elementor:*:*:*:*:*:wordpress:*:*"
],
"post-index": [
"cpe:2.3:a:post_index_project:post_index:*:*:*:*:*:wordpress:*:*"
],
@@ -15032,8 +15190,7 @@
"cpe:2.3:a:i13websolution:post_sliders_\\\u0026_post_grids:*:*:*:*:*:wordpress:*:*"
],
"post-smtp": [
"cpe:2.3:a:wpexperts:post_smtp:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpexperts:post_smtp_mailer:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:wpexperts:post_smtp:*:*:*:*:*:wordpress:*:*"
],
"post-snippets": [
"cpe:2.3:a:postsnippets:post_snippets:*:*:*:*:*:wordpress:*:*"
@@ -15236,6 +15393,9 @@
"product-designer": [
"cpe:2.3:a:pickplugins:product_designer:*:*:*:*:*:wordpress:*:*"
],
"product-enquiry-for-woocommerce": [
"cpe:2.3:a:wisdmlabs:product_enquiry_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"product-expiry-for-woocommerce": [
"cpe:2.3:a:webcodingplace:product_expiry_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -15379,6 +15539,9 @@
"cpe:2.3:a:quick_chat_project:quick_chat:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:techytalk:quick_chat:*:*:*:*:*:wordpress:*:*"
],
"quick-code": [
"cpe:2.3:a:gwycon:quick_code:*:*:*:*:*:wordpress:*:*"
],
"quick-contact-form": [
"cpe:2.3:a:fullworksplugins:quick_contact_form:*:*:*:*:*:wordpress:*:*"
],
@@ -15410,6 +15573,9 @@
"quickswish": [
"cpe:2.3:a:hasthemes:quickswish:*:*:*:*:*:wordpress:*:*"
],
"quillforms": [
"cpe:2.3:a:mdmag:quill_forms:*:*:*:*:*:wordpress:*:*"
],
"quiz-expert": [
"cpe:2.3:a:wepupil:quiz_expert_-_easy_quiz_maker\\,_exam_and_test_manager:*:*:*:*:*:wordpress:*:*"
],
@@ -15460,6 +15626,7 @@
"cpe:2.3:a:softlabbd:radio_player:*:*:*:*:*:wordpress:*:*"
],
"rafflepress": [
"cpe:2.3:a:rafflepress:rafflepress:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:seedprod:rafflepress:*:*:*:*:*:wordpress:*:*"
],
"random-banner": [
@@ -16082,6 +16249,9 @@
"sendpress": [
"cpe:2.3:a:pressified:sendpress:*:*:*:*:*:wordpress:*:*"
],
"sendpulse-email-marketing-newsletter": [
"cpe:2.3:a:sendpulse:sendpulse_email_marketing_newsletter:*:*:*:*:*:wordpress:*:*"
],
"sendpulse-web-push": [
"cpe:2.3:a:sendpulse:free_web_push:*:*:*:*:*:wordpress:*:*"
],
@@ -16156,6 +16326,9 @@
"cpe:2.3:a:legalweb:wp_dsgvo_tools:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:shapepress:wp_dsgvo_tools:*:*:*:*:*:wordpress:*:*"
],
"share-buttons": [
"cpe:2.3:a:artlosk:social_share_buttons:*:*:*:*:*:wordpress:*:*"
],
"share-on-diaspora": [
"cpe:2.3:a:share_on_diaspora_project:share_on_diaspora:*:*:*:*:*:wordpress:*:*"
],
@@ -16236,7 +16409,8 @@
],
"shortcodes-ultimate": [
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:-:wordpress:*:*"
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:-:wordpress:*:*",
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:pro:wordpress:*:*"
],
"shortcut-macros": [
"cpe:2.3:a:shortcut_macros_project:shortcut_macros:*:*:*:*:*:wordpress:*:*"
@@ -16306,6 +16480,9 @@
"simple-ajax-chat": [
"cpe:2.3:a:plugin-planet:simple_ajax_chat:*:*:*:*:*:wordpress:*:*"
],
"simple-al-slider": [
"cpe:2.3:a:alexdtn:simple_al_slider:*:*:*:*:*:wordpress:*:*"
],
"simple-author-box": [
"cpe:2.3:a:webfactoryltd:simple_author_box:*:*:*:*:*:wordpress:*:*"
],
@@ -16322,6 +16499,9 @@
"simple-bitcoin-faucets": [
"cpe:2.3:a:simple_bitcoin_faucets_project:simple_bitcoin_faucets:*:*:*:*:*:wordpress:*:*"
],
"simple-buttons-creator": [
"cpe:2.3:a:robbychen:simple_buttons_creator:*:*:*:*:*:wordpress:*:*"
],
"simple-calendar-for-elementor": [
"cpe:2.3:a:migaweb:simple_calendar_for_elementor:*:*:*:*:*:wordpress:*:*"
],
@@ -16381,6 +16561,9 @@
"cpe:2.3:a:simplefilelist:simple-file-list:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:simplefilelist:simple_file_list:*:*:*:*:*:wordpress:*:*"
],
"simple-form": [
"cpe:2.3:a:devsabbirahmed:simple_form:*:*:*:*:*:wordpress:*:*"
],
"simple-history": [
"cpe:2.3:a:simple_history_project:simple_history:*:*:*:*:*:wordpress:*:*"
],
@@ -16409,6 +16592,9 @@
"simple-ldap-login": [
"cpe:2.3:a:objectiv:simple_ldap_login:*:*:*:*:*:wordpress:*:*"
],
"simple-lightbox": [
"cpe:2.3:a:archetyped:simple_lightbox:*:*:*:*:*:wordpress:*:*"
],
"simple-link-directory": [
"cpe:2.3:a:quantumcloud:simple_link_directory:*:*:*:*:*:wordpress:*:*"
],
@@ -16494,6 +16680,9 @@
"simple-site-verify": [
"cpe:2.3:a:idoweb:simple_site_verify:*:*:*:*:*:wordpress:*:*"
],
"simple-sitemap": [
"cpe:2.3:a:wpgoplugins:simple_sitemap:*:*:*:*:free:wordpress:*:*"
],
"simple-slug-translate": [
"cpe:2.3:a:simple_slug_translate_project:simple_slug_translate:*:*:*:*:*:wordpress:*:*"
],
@@ -16788,7 +16977,8 @@
"cpe:2.3:a:wpmudev:smartcrawl:*:*:*:*:*:wordpress:*:*"
],
"smartsearchwp": [
"cpe:2.3:a:smartsearchwp:smartsearchwp:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:smartsearchwp:smartsearchwp:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:webdigit:chatbot_with_chatgpt:*:*:*:*:*:wordpress:*:*"
],
"smartsoftbutton-widget-de-botones-de-chat": [
"cpe:2.3:a:smartsoft:button_widget_smartsoft:*:*:*:*:*:wordpress:*:*"
@@ -16862,6 +17052,9 @@
"social-media-feather": [
"cpe:2.3:a:sharethis:social_media_feather:*:*:*:*:*:wordpress:*:*"
],
"social-media-widget": [
"cpe:2.3:a:bmwebproperties:social_media_widget:*:*:*:*:*:wordpress:*:*"
],
"social-networks-auto-poster-facebook-twitter-g": [
"cpe:2.3:a:nextscripts:social_networks_auto_poster:*:*:*:*:*:wordpress:*:*"
],
@@ -16965,6 +17158,9 @@
"spice-starter-sites": [
"cpe:2.3:a:spicethemes:spice_starter_sites:*:*:*:*:*:wordpress:*:*"
],
"spider-contacts": [
"cpe:2.3:a:10web:spidercontacts:*:*:*:*:*:wordpress:*:*"
],
"spider-event-calendar": [
"cpe:2.3:a:web-dorado:spider_calendar:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:web-dorado:spider_event_calendar:*:*:*:*:*:*:*:*"
@@ -17206,6 +17402,9 @@
"support-chat": [
"cpe:2.3:a:ninjateam:click_to_chat:*:*:*:*:*:wordpress:*:*"
],
"support-svg": [
"cpe:2.3:a:sayedulsayem:support_svg:*:*:*:*:*:wordpress:*:*"
],
"supportcandy": [
"cpe:2.3:a:supportcandy:supportcandy:*:*:*:*:*:wordpress:*:*"
],
@@ -17233,12 +17432,18 @@
"surveys": [
"cpe:2.3:a:surveys_project:surveys:*:*:*:*:*:wordpress:*:*"
],
"svg-block": [
"cpe:2.3:a:boldblocks:svg_block:*:*:*:*:*:wordpress:*:*"
],
"svg-support": [
"cpe:2.3:a:benbodhi:svg_support:*:*:*:*:*:wordpress:*:*"
],
"svg-vector-icon-plugin": [
"cpe:2.3:a:wp_svg_icons_project:wp_svg_icons:*:*:*:*:*:wordpress:*:*"
],
"svgator": [
"cpe:2.3:a:svgator:svgator:*:*:*:*:*:wordpress:*:*"
],
"svgmagic": [
"cpe:2.3:a:andibauer:svgmagic:*:*:*:*:*:wordpress:*:*"
],
@@ -17627,6 +17832,9 @@
"tourfic": [
"cpe:2.3:a:themefic:tourfic:*:*:*:*:*:wordpress:*:*"
],
"tournamatch": [
"cpe:2.3:a:tournamatch:tournamatch:*:*:*:*:*:wordpress:*:*"
],
"tp-education": [
"cpe:2.3:a:themepalace:tp_education:*:*:*:*:*:wordpress:*:*"
],
@@ -17642,6 +17850,9 @@
"tracking-code-manager": [
"cpe:2.3:a:data443:tracking_code_manager:*:*:*:*:*:wordpress:*:*"
],
"tradedoubler-affiliate-tracker": [
"cpe:2.3:a:tradedoubler:grow:*:*:*:*:*:wordpress:*:*"
],
"tradetracker-store": [
"cpe:2.3:a:wpaffiliatefeed:tradetracker-store:*:*:*:*:*:wordpress:*:*"
],
@@ -17684,6 +17895,9 @@
"tripetto": [
"cpe:2.3:a:tripetto:tripetto:*:*:*:*:*:wordpress:*:*"
],
"truebooker-appointment-booking": [
"cpe:2.3:a:themetechmount:truebooker:*:*:*:*:*:wordpress:*:*"
],
"ts-webfonts-for-conoha": [
"cpe:2.3:a:gmo:typesquare_webfonts_for_conoha:*:*:*:*:*:wordpress:*:*"
],
@@ -17914,6 +18128,9 @@
"underconstruction": [
"cpe:2.3:a:underconstruction_project:underconstruction:*:*:*:*:*:wordpress:*:*"
],
"ungallery": [
"cpe:2.3:a:mmond:ungallery:*:*:*:*:*:wordpress:*:*"
],
"uninstall": [
"cpe:2.3:a:wordpress_uninstall_project:wordpress_uninstall:*:*:*:*:*:wordpress:*:*"
],
@@ -18493,6 +18710,9 @@
"widget-twitter": [
"cpe:2.3:a:web-dorado:wd_widgettwitter:*:*:*:*:*:wordpress:*:*"
],
"widget4call": [
"cpe:2.3:a:apidaze:widget4call:*:*:*:*:*:wordpress:*:*"
],
"widgetkit-for-elementor": [
"cpe:2.3:a:themesgrove:all-in-one_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
],
@@ -18697,6 +18917,9 @@
"woo-tranzila-gateway": [
"cpe:2.3:a:antonbond:woocommerce_tranzila_payment_gateway:*:*:*:*:*:wordpress:*:*"
],
"woo-ups-pickup": [
"cpe:2.3:a:ashamil:opsi_israel_domestic_shipments:*:*:*:*:*:wordpress:*:*"
],
"woo-variation-gallery": [
"cpe:2.3:a:getwooplugins:additional_variation_images_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -18739,6 +18962,9 @@
"woocommerce-amazon-affiliates-light-version": [
"cpe:2.3:a:wzone_project:wzone:*:*:*:*:lite:wordpress:*:*"
],
"woocommerce-and-wp-emember-integration": [
"cpe:2.3:a:tipsandtricks-hq:wp_emember:*:*:*:*:*:wordpress:*:*"
],
"woocommerce-catalog-enquiry": [
"cpe:2.3:a:multivendorx:product_catalog_mode_for_woocommerce:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wc-marketplace:wc_catalog_enquiry:*:*:*:*:*:wordpress:*:*"
@@ -19022,6 +19248,9 @@
"wp-ban": [
"cpe:2.3:a:wp-ban_project:wp-ban:*:*:*:*:*:wordpress:*:*"
],
"wp-base-booking-of-appointments-services-and-events": [
"cpe:2.3:a:wp-base:wp_base_booking_of_appointments\\,_services_and_events:*:*:*:*:*:wordpress:*:*"
],
"wp-best-quiz": [
"cpe:2.3:a:subina:wp_best_quiz:*:*:*:*:*:wordpress:*:*"
],
@@ -19155,6 +19384,9 @@
"wp-commentnavi": [
"cpe:2.3:a:wp-commentnavi_project:wp-commentnavi:*:*:*:*:*:wordpress:*:*"
],
"wp-compress-image-optimizer": [
"cpe:2.3:a:wpcompress:wp_compress:*:*:*:*:*:wordpress:*:*"
],
"wp-concours": [
"cpe:2.3:a:olyos:wp-concours:*:*:*:*:*:wordpress:*:*"
],
@@ -19327,6 +19559,9 @@
"wp-education": [
"cpe:2.3:a:hasthemes:wp_education:*:*:*:*:*:wordpress:*:*"
],
"wp-eggdrop": [
"cpe:2.3:a:backie:wp-eggdrop:*:*:*:*:*:wordpress:*:*"
],
"wp-email": [
"cpe:2.3:a:wp-email_project:wp-email:*:*:*:*:*:wordpress:*:*"
],
@@ -19554,6 +19789,9 @@
"wp-imageflow2": [
"cpe:2.3:a:spiffyplugins:wp_flow_plus:*:*:*:*:*:wordpress:*:*"
],
"wp-imagezoom": [
"cpe:2.3:a:aueda:wp-imagezoom:*:*:*:*:*:wordpress:*:*"
],
"wp-import-export-lite": [
"cpe:2.3:a:vjinfotech:wp_import_export_lite:*:*:*:*:*:wordpress:*:*"
],
@@ -19971,6 +20209,9 @@
"wp-seo-redirect-301": [
"cpe:2.3:a:wp_seo_redirect_301_project:wp_seo_redirect_301:*:*:*:*:*:wordpress:*:*"
],
"wp-seo-structured-data-schema": [
"cpe:2.3:a:wpsemplugins:wp_seo_structured_data_schema:*:*:*:*:free:wordpress:*:*"
],
"wp-seo-tags": [
"cpe:2.3:a:wp_seo_tags_project:wp_seo_tags:*:*:*:*:*:wordpress:*:*"
],
@@ -20155,7 +20396,7 @@
"cpe:2.3:a:wp-t-wap_project:wp-t-wap:*:*:*:*:*:wordpress:*:*"
],
"wp-table-builder": [
"cpe:2.3:a:wptablebuilder:wp_table_builder:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:dotcamp:wp_table_builder:*:*:*:*:*:wordpress:*:*"
],
"wp-table-reloaded": [
"cpe:2.3:a:wp-table_reloaded_project:wp-table_reloaded:*:*:*:*:*:wordpress:*:*"
@@ -20284,6 +20525,9 @@
"wp-webauthn": [
"cpe:2.3:a:axton:wp-webauthn:*:*:*:*:*:wordpress:*:*"
],
"wp-webinarsystem": [
"cpe:2.3:a:webinarpress:webinarpress:*:*:*:*:*:wordpress:*:*"
],
"wp-whatsapp": [
"cpe:2.3:a:ninjateam:wp_chat_app:*:*:*:*:*:wordpress:*:*"
],
@@ -20305,6 +20549,9 @@
"wp-worthy": [
"cpe:2.3:a:tiggerswelt:worthy:*:*:*:*:*:wordpress:*:*"
],
"wp-yadisk-files": [
"cpe:2.3:a:antongorodezkiy:yadisk_files:*:*:*:*:*:wordpress:*:*"
],
"wp-yelp-review-slider": [
"cpe:2.3:a:ljapps:wp_yelp_review_slider:*:*:*:*:*:wordpress:*:*"
],
@@ -20411,7 +20658,8 @@
"cpe:2.3:a:wpforms:wpforms:*:*:*:*:pro:wordpress:*:*"
],
"wpforms-lite": [
"cpe:2.3:a:wpforms:contact_form:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:wpforms:contact_form:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpforms:wpforms:*:*:*:*:lite:wordpress:*:*"
],
"wpforo": [
"cpe:2.3:a:gvectors:wpforo:*:*:*:*:*:wordpress:*:*",
@@ -20470,6 +20718,9 @@
"wpmarketplace": [
"cpe:2.3:a:wpmarketplace_project:wpmarketplace:*:*:*:*:*:wordpress:*:*"
],
"wpmovielibrary": [
"cpe:2.3:a:caercam:wpmovielibrary:*:*:*:*:*:wordpress:*:*"
],
"wpo365-login": [
"cpe:2.3:a:wpo365:wordpress_\\+_azure_ad_\\/_microsoft_office_365:*:*:*:*:*:wordpress:*:*"
],
@@ -20669,6 +20920,10 @@
"yith-maintenance-mode": [
"cpe:2.3:a:yithemes:yith_maintenance_mode:*:*:*:*:*:wordpress:*:*"
],
"yith-woocommerce-ajax-search": [
"cpe:2.3:a:yithemes:yith_woocommerce_ajax_search:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:yithemes:yith_woocommerce_ajax_search:*:*:*:*:free:wordpress:*:*"
],
"yith-woocommerce-gift-cards": [
"cpe:2.3:a:yithemes:yith_woocommerce_gift_cards:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:yithemes:yith_woocommerce_gift_cards:*:*:*:*:free:wordpress:*:*"
@@ -20750,6 +21005,9 @@
"z-companion": [
"cpe:2.3:a:wpzita:z_companion:*:*:*:*:*:wordpress:*:*"
],
"z-downloads": [
"cpe:2.3:a:urbanbase:z-downloads:*:*:*:*:*:wordpress:*:*"
],
"z-url-preview": [
"cpe:2.3:a:z-url_preview_project:z-url_preview:*:*:*:*:*:wordpress:*:*"
],
+736
View File
@@ -0,0 +1,736 @@
package pe
import (
"bytes"
"debug/pe"
"encoding/binary"
"errors"
"fmt"
"io"
"unicode/utf16"
"github.com/scylladb/go-set/strset"
"github.com/scylladb/go-set/u32set"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader"
)
const peMaxAllowedDirectoryEntries = 0x1000
var imageDirectoryEntryIndexes = []int{
pe.IMAGE_DIRECTORY_ENTRY_RESOURCE, // where version resources are stored
pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // where info about the CLR is stored
}
// File does not directly represent a binary shape to be parsed, instead it represents the
// information of interest extracted from a PE file.
type File struct {
// Location is where the PE file was found
Location file.Location
// CLR is the information about the CLR (common language runtime) version found in the PE file which helps
// understand if this executable is even a .NET application.
CLR *CLREvidence
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
VersionResources map[string]string
}
// CLREvidence is basic info about the CLR (common language runtime) version from the COM descriptor.
// This is not a complete representation of the CLR version, but rather a subset of the information that is
// useful to us.
type CLREvidence struct {
// HasClrResourceNames is true if there are CLR resource names found in the PE file (e.g. "CLRDEBUGINFO").
HasClrResourceNames bool
// MajorVersion is the minimum supported major version of the CLR.
MajorVersion uint16
// MinorVersion is the minimum supported minor version of the CLR.
MinorVersion uint16
}
// HasEvidenceOfCLR returns true if the PE file has evidence of a CLR (common language runtime) version.
func (c *CLREvidence) HasEvidenceOfCLR() bool {
return c != nil && (c.MajorVersion != 0 && c.MinorVersion != 0 || c.HasClrResourceNames)
}
type peDosHeader struct {
Magic [2]byte // "MZ"
Unused [58]byte
AddressOfNewEXEHeader uint32 // offset to PE header
}
// peImageCore20 represents the .NET Core 2.0 header structure.
// Source: https://github.com/dotnet/msbuild/blob/9fa9d800dabce3bfcf8365f651f3a713e01f8a85/src/Tasks/NativeMethods.cs#L761-L775
type peImageCore20 struct {
Cb uint32
MajorRuntimeVersion uint16
MinorRuntimeVersion uint16
}
// peImageResourceDirectory represents the resource directory structure.
type peImageResourceDirectory struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
NumberOfNamedEntries uint16
NumberOfIDEntries uint16
}
// peImageResourceDirectoryEntry represents an entry in the resource directory entries.
type peImageResourceDirectoryEntry struct {
Name uint32
OffsetToData uint32
}
// peImageResourceDataEntry is the unit of raw data in the Resource Data area.
type peImageResourceDataEntry struct {
OffsetToData uint32
Size uint32
CodePage uint32
Reserved uint32
}
// peVsFixedFileInfo represents the fixed file information structure.
type peVsFixedFileInfo struct {
Signature uint32
StructVersion uint32
FileVersionMS uint32
FileVersionLS uint32
ProductVersionMS uint32
ProductVersionLS uint32
FileFlagsMask uint32
FileFlags uint32
FileOS uint32
FileType uint32
FileSubtype uint32
FileDateMS uint32
FileDateLS uint32
}
type peVsVersionInfo peLenValLenType
type peStringFileInfo peLenValLenType
type peStringTable peLenValLenType
type peString peLenValLenType
type peLenValLenType struct {
Length uint16
ValueLength uint16
Type uint16
}
type extractedSection struct {
RVA uint32
BaseRVA uint32
Size uint32
Reader *bytes.Reader
}
func (s extractedSection) exists() bool {
return s.RVA != 0 && s.Size != 0
}
func directoryName(i int) string {
switch i {
case pe.IMAGE_DIRECTORY_ENTRY_RESOURCE:
return "Resource"
case pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
return "COM Descriptor"
}
return fmt.Sprintf("Unknown (%d)", i)
}
func Read(f file.LocationReadCloser) (*File, error) {
r, err := unionreader.GetUnionReader(f)
if err != nil {
return nil, err
}
sections, _, err := parsePEFile(r)
if err != nil {
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
}
dirs := u32set.New() // keep track of the RVAs we have already parsed (prevent infinite recursion edge cases)
versionResources := make(map[string]string) // map of version resource keys to their values
resourceNames := strset.New() // set of resource names found in the PE file
err = parseResourceDirectory(sections[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE], dirs, versionResources, resourceNames)
if err != nil {
return nil, err
}
c, err := parseCLR(sections[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR], resourceNames)
if err != nil {
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
}
return &File{
Location: f.Location,
CLR: c,
VersionResources: versionResources,
}, nil
}
// parsePEFile creates readers for targeted sections of the binary used by downstream processing.
func parsePEFile(file unionreader.UnionReader) (map[int]*extractedSection, []pe.SectionHeader32, error) {
fileHeader, magic, err := parsePEHeader(file)
if err != nil {
return nil, nil, fmt.Errorf("error parsing PE header: %w", err)
}
soi, headers, err := parseSectionHeaders(file, magic, fileHeader.NumberOfSections)
if err != nil {
return nil, nil, fmt.Errorf("error parsing section headers: %w", err)
}
for i, sec := range soi {
if !sec.exists() {
continue
}
data, err := readDataFromRVA(file, sec.RVA, sec.Size, headers)
if err != nil {
return nil, nil, fmt.Errorf("error reading %q section data: %w", directoryName(i), err)
}
sec.Reader = data
}
return soi, headers, nil
}
// parsePEHeader reads the beginning of a PE formatted file, returning the file header and "magic" indicator
// for downstream logic to determine 32/64 bit parsing.
func parsePEHeader(file unionreader.UnionReader) (*pe.FileHeader, uint16, error) {
var dosHeader peDosHeader
if err := binary.Read(file, binary.LittleEndian, &dosHeader); err != nil {
return nil, 0, fmt.Errorf("error reading DOS header: %w", err)
}
if string(dosHeader.Magic[:]) != "MZ" {
return nil, 0, fmt.Errorf("invalid DOS header magic")
}
peOffset := int64(dosHeader.AddressOfNewEXEHeader)
if _, err := file.Seek(peOffset, io.SeekStart); err != nil {
return nil, 0, fmt.Errorf("error seeking to PE header: %w", err)
}
var signature [4]byte
if err := binary.Read(file, binary.LittleEndian, &signature); err != nil {
return nil, 0, fmt.Errorf("error reading PE signature: %w", err)
}
if !bytes.Equal(signature[:], []byte("PE\x00\x00")) {
return nil, 0, fmt.Errorf("invalid PE signature")
}
var fileHeader pe.FileHeader
if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil {
return nil, 0, fmt.Errorf("error reading file header: %w", err)
}
var magic uint16
if err := binary.Read(file, binary.LittleEndian, &magic); err != nil {
return nil, 0, fmt.Errorf("error reading optional header magic: %w", err)
}
// seek back to before reading magic (since that value is in the header)
if _, err := file.Seek(-2, io.SeekCurrent); err != nil {
return nil, 0, fmt.Errorf("error seeking back to before reading magic: %w", err)
}
return &fileHeader, magic, nil
}
// parseSectionHeaders reads the section headers from the PE file and extracts the virtual addresses + section size
// information for the sections of interest. Additionally, all section headers are returned to aid in downstream processing.
func parseSectionHeaders(file unionreader.UnionReader, magic uint16, numberOfSections uint16) (map[int]*extractedSection, []pe.SectionHeader32, error) {
soi := make(map[int]*extractedSection)
switch magic {
case 0x10B: // PE32
var optHeader pe.OptionalHeader32
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
return nil, nil, fmt.Errorf("error reading optional header (PE32): %w", err)
}
for _, i := range imageDirectoryEntryIndexes {
sectionHeader := optHeader.DataDirectory[i]
if sectionHeader.Size == 0 {
continue
}
soi[i] = &extractedSection{
RVA: sectionHeader.VirtualAddress,
Size: sectionHeader.Size,
}
}
case 0x20B: // PE32+ (64 bit)
var optHeader pe.OptionalHeader64
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
return nil, nil, fmt.Errorf("error reading optional header (PE32+): %w", err)
}
for _, i := range imageDirectoryEntryIndexes {
sectionHeader := optHeader.DataDirectory[i]
if sectionHeader.Size == 0 {
continue
}
soi[i] = &extractedSection{
RVA: sectionHeader.VirtualAddress,
Size: sectionHeader.Size,
}
}
default:
return nil, nil, fmt.Errorf("unknown optional header magic: 0x%x", magic)
}
// read section headers
headers := make([]pe.SectionHeader32, numberOfSections)
for i := 0; i < int(numberOfSections); i++ {
if err := binary.Read(file, binary.LittleEndian, &headers[i]); err != nil {
return nil, nil, fmt.Errorf("error reading section header: %w", err)
}
}
return soi, headers, nil
}
// parseCLR extracts the CLR (common language runtime) version information from the COM descriptor and makes
// present/not-present determination based on the presence of CLR resource names.
func parseCLR(sec *extractedSection, resourceNames *strset.Set) (*CLREvidence, error) {
hasCLRDebugResourceNames := resourceNames.HasAny("CLRDEBUGINFO")
if sec == nil || sec.Reader == nil {
return &CLREvidence{
HasClrResourceNames: hasCLRDebugResourceNames,
}, nil
}
reader := sec.Reader
var c peImageCore20
if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
return nil, fmt.Errorf("error reading CLR header: %w", err)
}
return &CLREvidence{
HasClrResourceNames: hasCLRDebugResourceNames,
MajorVersion: c.MajorRuntimeVersion,
MinorVersion: c.MinorRuntimeVersion,
}, nil
}
// rvaToFileOffset is a helper function to convert RVA to file offset using section headers
func rvaToFileOffset(rva uint32, sections []pe.SectionHeader32) (uint32, error) {
for _, section := range sections {
if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize {
return section.PointerToRawData + (rva - section.VirtualAddress), nil
}
}
return 0, fmt.Errorf("RVA 0x%x not found in any section", rva)
}
// readDataFromRVA will read data from a specific RVA in the PE file
func readDataFromRVA(file io.ReadSeeker, rva, size uint32, sections []pe.SectionHeader32) (*bytes.Reader, error) {
if size == 0 {
return nil, fmt.Errorf("zero size specified")
}
offset, err := rvaToFileOffset(rva, sections)
if err != nil {
return nil, err
}
if _, err := file.Seek(int64(offset), io.SeekStart); err != nil {
return nil, fmt.Errorf("error seeking to data: %w", err)
}
data := make([]byte, size)
if _, err := io.ReadFull(file, data); err != nil {
return nil, fmt.Errorf("error reading data: %w", err)
}
return bytes.NewReader(data), nil
}
// parseResourceDirectory recursively parses a PE resource directory. This takes a relative virtual address (offset of
// a piece of data or code relative to the base address), the size of the resource directory, the set of RVAs already
// parsed, and the map to populate discovered version resource values.
//
// .rsrc Section
// +------------------------------+
// | Resource Directory Table |
// +------------------------------+
// | Resource Directory Entries |
// | +------------------------+ |
// | | Subdirectory or Data | |
// | +------------------------+ |
// +------------------------------+
// | Resource Data Entries |
// | +------------------------+ |
// | | Resource Data | |
// | +------------------------+ |
// +------------------------------+
// | Actual Resource Data |
// +------------------------------+
//
// sources:
// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section
// - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)#pe-file-resources
func parseResourceDirectory(sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
if sec == nil || sec.Size <= 0 {
return nil
}
if sec.Reader == nil {
return errors.New("resource section not found")
}
baseRVA := sec.BaseRVA
if baseRVA == 0 {
baseRVA = sec.RVA
}
offset := int64(sec.RVA - baseRVA)
if _, err := sec.Reader.Seek(offset, io.SeekStart); err != nil {
return fmt.Errorf("error seeking to directory offset: %w", err)
}
var directoryHeader peImageResourceDirectory
if err := readIntoStruct(sec.Reader, &directoryHeader); err != nil {
return fmt.Errorf("error reading directory header: %w", err)
}
numEntries := int(directoryHeader.NumberOfNamedEntries + directoryHeader.NumberOfIDEntries)
switch {
case numEntries > peMaxAllowedDirectoryEntries:
return fmt.Errorf("too many entries in resource directory: %d", numEntries)
case numEntries == 0:
return fmt.Errorf("no entries in resource directory")
case numEntries < 0:
return fmt.Errorf("invalid number of entries in resource directory: %d", numEntries)
}
for i := 0; i < numEntries; i++ {
var entry peImageResourceDirectoryEntry
entryOffset := offset + int64(binary.Size(directoryHeader)) + int64(i*binary.Size(entry))
if _, err := sec.Reader.Seek(entryOffset, io.SeekStart); err != nil {
log.Tracef("error seeking to PE entry offset: %v", err)
continue
}
if err := readIntoStruct(sec.Reader, &entry); err != nil {
continue
}
if err := processResourceEntry(entry, baseRVA, sec, dirs, fields, names); err != nil {
log.Tracef("error processing resource entry: %v", err)
continue
}
}
return nil
}
func processResourceEntry(entry peImageResourceDirectoryEntry, baseRVA uint32, sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
// if the high bit is set, this is a directory entry, otherwise it is a data entry
isDirectory := entry.OffsetToData&0x80000000 != 0
// note: the offset is relative to the beginning of the resource section, not an RVA
entryOffsetToData := entry.OffsetToData & 0x7FFFFFFF
nameIsString := entry.Name&0x80000000 != 0
nameOffset := entry.Name & 0x7FFFFFFF
// read the string name of the resource directory
if nameIsString {
currentPos, err := sec.Reader.Seek(0, io.SeekCurrent)
if err != nil {
return fmt.Errorf("error getting current reader position: %w", err)
}
if _, err := sec.Reader.Seek(int64(nameOffset), io.SeekStart); err != nil {
return fmt.Errorf("error restoring reader position: %w", err)
}
name, err := readUTF16WithLength(sec.Reader)
if err == nil {
names.Add(name)
}
if _, err := sec.Reader.Seek(currentPos, io.SeekStart); err != nil {
return fmt.Errorf("error restoring reader position: %w", err)
}
}
if isDirectory {
subRVA := baseRVA + entryOffsetToData
if dirs.Has(subRVA) {
// some malware uses recursive PE references to evade analysis
return fmt.Errorf("recursive PE reference detected; skipping directory at baseRVA=0x%x subRVA=0x%x", baseRVA, subRVA)
}
dirs.Add(subRVA)
err := parseResourceDirectory(
&extractedSection{
RVA: subRVA,
BaseRVA: baseRVA,
Size: sec.Size - (sec.RVA - baseRVA),
Reader: sec.Reader,
},
dirs, fields, names)
if err != nil {
return err
}
return nil
}
return parseResourceDataEntry(sec.Reader, baseRVA, baseRVA+entryOffsetToData, sec.Size, fields)
}
func parseResourceDataEntry(reader *bytes.Reader, baseRVA, rva, remainingSize uint32, fields map[string]string) error {
var dataEntry peImageResourceDataEntry
offset := int64(rva - baseRVA)
if _, err := reader.Seek(offset, io.SeekStart); err != nil {
return fmt.Errorf("error seeking to data entry offset: %w", err)
}
if err := readIntoStruct(reader, &dataEntry); err != nil {
return fmt.Errorf("error reading resource data entry: %w", err)
}
if remainingSize < dataEntry.Size {
return fmt.Errorf("resource data entry size exceeds remaining size")
}
data := make([]byte, dataEntry.Size)
if _, err := reader.Seek(int64(dataEntry.OffsetToData-baseRVA), io.SeekStart); err != nil {
return fmt.Errorf("error seeking to resource data: %w", err)
}
if _, err := reader.Read(data); err != nil {
return fmt.Errorf("error reading resource data: %w", err)
}
return parseVersionResourceSection(bytes.NewReader(data), fields)
}
// parseVersionResourceSection parses a PE version resource section from within a resource directory.
//
// "The main structure in a version resource is the VS_FIXEDFILEINFO structure. Additional structures include the
// VarFileInfo structure to store language information data, and StringFileInfo for user-defined string information.
// All strings in a version resource are in Unicode format. Each block of information is aligned on a DWORD boundary."
//
// "VS_VERSIONINFO" (utf16)
// +---------------------------------------------------+
// | wLength (2 bytes) |
// | wValueLength (2 bytes) |
// | wType (2 bytes) |
// | szKey ("VS_VERSION_INFO") (utf16) |
// | Padding (to DWORD) |
// +---------------------------------------------------+
// | VS_FIXEDFILEINFO (52 bytes) |
// +---------------------------------------------------+
// | "StringFileInfo" (utf16) |
// +---------------------------------------------------+
// | wLength (2 bytes) |
// | wValueLength (2 bytes) |
// | wType (2 bytes) |
// | szKey ("StringFileInfo") (utf16) |
// | Padding (to DWORD) |
// | StringTable |
// | +--------------------------------------------+ |
// | | wLength (2 bytes) | |
// | | wValueLength (2 bytes) | |
// | | wType (2 bytes) | |
// | | szKey ("040904b0") | |
// | | Padding (to DWORD) | |
// | | String | |
// | | +--------------------------------------+ | |
// | | | wLength (2 bytes) | | |
// | | | wValueLength (2 bytes) | | |
// | | | wType (2 bytes) | | |
// | | | szKey ("FileVersion") | | |
// | | | Padding (to DWORD) | | |
// | | | szValue ("15.00.0913.015") | | |
// | | | Padding (to DWORD) | | |
// | +--------------------------------------------+ |
// +---------------------------------------------------+
// | VarFileInfo (utf16) |
// +---------------------------------------------------+
// | (skip!) |
// +---------------------------------------------------+
//
// sources:
// - https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
// - https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
// - https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
// - https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable
func parseVersionResourceSection(reader *bytes.Reader, fields map[string]string) error {
offset := 0
var info peVsVersionInfo
if szKey, err := readIntoStructAndSzKey(reader, &info, &offset); err != nil {
return fmt.Errorf("error reading PE version info: %v", err)
} else if szKey != "VS_VERSION_INFO" {
// this is a resource section, but not the version resources
return nil
}
if err := alignAndSeek(reader, &offset); err != nil {
return fmt.Errorf("error aligning past PE version info: %w", err)
}
var fixedFileInfo peVsFixedFileInfo
if err := readIntoStruct(reader, &fixedFileInfo, &offset); err != nil {
return fmt.Errorf("error reading PE FixedFileInfo: %v", err)
}
for reader.Len() > 0 {
if err := alignAndSeek(reader, &offset); err != nil {
return fmt.Errorf("error seeking to PE StringFileInfo: %w", err)
}
var sfiHeader peStringFileInfo
if szKey, err := readIntoStructAndSzKey(reader, &sfiHeader, &offset); err != nil {
return fmt.Errorf("error reading PE string file info header: %v", err)
} else if szKey != "StringFileInfo" {
// we only care about extracting strings from any string tables, skip this
offset += int(sfiHeader.ValueLength)
continue
}
var stOffset int
// note: the szKey for the prStringTable is the language
var stHeader peStringTable
if _, err := readIntoStructAndSzKey(reader, &stHeader, &offset, &stOffset); err != nil {
return fmt.Errorf("error reading PE string table header: %v", err)
}
for stOffset < int(stHeader.Length) {
var stringHeader peString
if err := readIntoStruct(reader, &stringHeader, &offset, &stOffset); err != nil {
break
}
key := readUTF16(reader, &offset, &stOffset)
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
return fmt.Errorf("error aligning to next PE string table value: %w", err)
}
var value string
if stringHeader.ValueLength > 0 {
value = readUTF16(reader, &offset, &stOffset)
}
fields[key] = value
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
return fmt.Errorf("error aligning to next PE string table key: %w", err)
}
}
}
if fields["FileVersion"] == "" {
// we can derive the file version from the fixed file info if it is not already specified as a string entry... neat!
fields["FileVersion"] = fmt.Sprintf("%d.%d.%d.%d",
fixedFileInfo.FileVersionMS>>16, fixedFileInfo.FileVersionMS&0xFFFF,
fixedFileInfo.FileVersionLS>>16, fixedFileInfo.FileVersionLS&0xFFFF)
}
return nil
}
// readIntoStructAndSzKey reads a struct from the reader and updates the offsets if provided, returning the szKey value.
// This is only useful in the context of the resource directory parsing in narrow cases (this is invalid to use outside of that context).
func readIntoStructAndSzKey[T any](reader *bytes.Reader, data *T, offsets ...*int) (string, error) {
if err := readIntoStruct(reader, data, offsets...); err != nil {
return "", err
}
return readUTF16(reader, offsets...), nil
}
// readIntoStruct reads a struct from the reader and updates the offsets if provided.
func readIntoStruct[T any](reader io.Reader, data *T, offsets ...*int) error {
if err := binary.Read(reader, binary.LittleEndian, data); err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
for i := range offsets {
*offsets[i] += binary.Size(*data)
}
return nil
}
// alignAndSeek aligns the reader to the next DWORD boundary and seeks to the new offset (updating any provided trackOffsets).
func alignAndSeek(reader io.Seeker, offset *int, trackOffsets ...*int) error {
ogOffset := *offset
*offset = alignToDWORD(*offset)
diff := *offset - ogOffset
for i := range trackOffsets {
*trackOffsets[i] += diff
}
_, err := reader.Seek(int64(*offset), io.SeekStart)
return err
}
// alignToDWORD aligns the offset to the next DWORD boundary (4 byte boundary)
func alignToDWORD(offset int) int {
return (offset + 3) & ^3
}
// readUTF16 is a helper function to read a null-terminated UTF16 string
func readUTF16(reader *bytes.Reader, offsets ...*int) string {
startPos, err := reader.Seek(0, io.SeekCurrent)
if err != nil {
return ""
}
var result []rune
for {
var char uint16
err := binary.Read(reader, binary.LittleEndian, &char)
if err != nil || char == 0 {
break
}
result = append(result, rune(char))
}
// calculate how many bytes we've actually read (including null terminator)
endPos, _ := reader.Seek(0, io.SeekCurrent)
bytesRead := int(endPos - startPos)
for i := range offsets {
*offsets[i] += bytesRead
}
return string(result)
}
// readUTF16WithLength reads a length-prefixed UTF-16 string from reader.
// The first 2 bytes represent the number of UTF-16 code units.
func readUTF16WithLength(reader *bytes.Reader) (string, error) {
var length uint16
if err := binary.Read(reader, binary.LittleEndian, &length); err != nil {
return "", err
}
if length == 0 {
return "", nil
}
// read length UTF-16 code units.
codes := make([]uint16, length)
if err := binary.Read(reader, binary.LittleEndian, &codes); err != nil {
return "", err
}
return string(utf16.Decode(codes)), nil
}
@@ -1,4 +1,4 @@
package dotnet
package pe
import (
"testing"
@@ -13,7 +13,7 @@ import (
"github.com/anchore/syft/syft/source/stereoscopesource"
)
func Test_getLogicalDotnetPE(t *testing.T) {
func Test_Read_DotNetDetection(t *testing.T) {
tests := []struct {
name string
fixture string
@@ -126,7 +126,7 @@ func Test_getLogicalDotnetPE(t *testing.T) {
reader := fixtureFile(t, tt.fixture, tt.path)
got, err := getLogicalDotnetPE(reader)
got, err := Read(reader)
tt.wantErr(t, err)
if err != nil {
return
@@ -136,7 +136,7 @@ func Test_getLogicalDotnetPE(t *testing.T) {
t.Errorf("unexpected version resources (-want +got): %s", d)
}
assert.Equal(t, tt.wantCLR, got.CLR.hasEvidenceOfCLR())
assert.Equal(t, tt.wantCLR, got.CLR.HasEvidenceOfCLR())
})
}
}
@@ -0,0 +1 @@
../../../dotnet/test-fixtures/image-net8-app
@@ -0,0 +1 @@
../../../dotnet/test-fixtures/image-net8-app-single-file
@@ -27,6 +27,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/directorysource"
"github.com/anchore/syft/syft/source/filesource"
"github.com/anchore/syft/syft/source/stereoscopesource"
)
@@ -96,6 +97,10 @@ func (p *CatalogTester) WithContext(ctx context.Context) *CatalogTester {
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
t.Helper()
if path == "" {
return p
}
s, err := directorysource.NewFromPath(path)
require.NoError(t, err)
@@ -106,9 +111,25 @@ func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester
return p
}
func (p *CatalogTester) FromFileSource(t *testing.T, path string) *CatalogTester {
t.Helper()
s, err := filesource.NewFromPath(path)
require.NoError(t, err)
resolver, err := s.FileResolver(source.AllLayersScope)
require.NoError(t, err)
p.resolver = resolver
return p
}
func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester {
t.Helper()
if path == "" {
return p
}
fixture, err := os.Open(path)
require.NoError(t, err)
@@ -157,6 +178,11 @@ func (p *CatalogTester) WithResolver(r file.Resolver) *CatalogTester {
func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *CatalogTester {
t.Helper()
if fixtureName == "" {
return p
}
img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName)
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
@@ -327,6 +353,11 @@ func TestCataloger(t *testing.T, fixtureDir string, cataloger pkg.Cataloger, exp
NewCatalogTester().FromDirectory(t, fixtureDir).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
}
func TestCatalogerFromFileSource(t *testing.T, fixturePath string, cataloger pkg.Cataloger, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
NewCatalogTester().FromFileSource(t, fixturePath).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
}
func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Parser, env *generic.Environment, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
+108
View File
@@ -215,3 +215,111 @@ func TestJvmDistributionCataloger(t *testing.T) {
}
}
func TestJvmDistributionCatalogerFromFile(t *testing.T) {
cases := []struct {
name string
fixture string
expected pkg.Package
}{
{
name: "valid 1.8.0",
fixture: "test-fixtures/jvm-installs/oracle-jdk-se-8/usr/lib/jvm/jdk-1.8-oracle-x64/release",
expected: pkg.Package{
Name: "jdk",
Version: "1.8.0_411-b25",
FoundBy: "java-jvm-cataloger",
Locations: file.NewLocationSet(file.NewLocation("/release")),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:java_se:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
cpe.Must("cpe:2.3:a:oracle:jre:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
cpe.Must("cpe:2.3:a:oracle:jdk:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
},
PURL: "pkg:generic/oracle/jdk@1.8.0_411-b25",
Metadata: pkg.JavaVMInstallation{
Release: pkg.JavaVMRelease{
JavaRuntimeVersion: "1.8.0_411-b25",
JavaVersion: "1.8.0_411",
OsArch: "amd64",
OsName: "Linux",
OsVersion: "2.6",
Source: ".:git:71ec2089cf8c+",
BuildType: "commercial",
},
Files: []string{
"/release",
},
},
},
},
{
name: "valid post-jep223",
fixture: "test-fixtures/jvm-installs/valid-post-jep223/jvm/openjdk/release",
expected: pkg.Package{
Name: "openjdk",
Version: "21.0.4+7-LTS",
FoundBy: "java-jvm-cataloger",
Locations: file.NewLocationSet(file.NewLocation("/release")),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:oracle:openjdk:21.0.4:*:*:*:*:*:*:*", cpe.DeclaredSource)},
PURL: "pkg:generic/oracle/openjdk@21.0.4%2B7-LTS?repository_url=https%3A%2F%2Fgithub.com%2Fadoptium%2Fjdk21u.git",
Metadata: pkg.JavaVMInstallation{
Release: pkg.JavaVMRelease{
Implementor: "Eclipse Adoptium",
ImplementorVersion: "Temurin-21.0.4+7",
JavaRuntimeVersion: "21.0.4+7-LTS",
JavaVersion: "21.0.4",
JavaVersionDate: "2024-07-16",
Libc: "gnu",
Modules: []string{
"java.base", "java.compiler", "java.datatransfer", "java.xml", "java.prefs",
"java.desktop", "java.instrument", "java.logging", "java.management",
"java.security.sasl", "java.naming", "java.rmi", "java.management.rmi",
"java.net.http", "java.scripting", "java.security.jgss",
"java.transaction.xa", "java.sql", "java.sql.rowset", "java.xml.crypto", "java.se",
"java.smartcardio", "jdk.accessibility", "jdk.internal.jvmstat", "jdk.attach",
"jdk.charsets", "jdk.internal.opt", "jdk.zipfs", "jdk.compiler", "jdk.crypto.ec",
"jdk.crypto.cryptoki", "jdk.dynalink", "jdk.internal.ed", "jdk.editpad", "jdk.hotspot.agent",
"jdk.httpserver", "jdk.incubator.vector", "jdk.internal.le", "jdk.internal.vm.ci",
"jdk.internal.vm.compiler", "jdk.internal.vm.compiler.management", "jdk.jartool",
"jdk.javadoc", "jdk.jcmd", "jdk.management", "jdk.management.agent", "jdk.jconsole",
"jdk.jdeps", "jdk.jdwp.agent", "jdk.jdi", "jdk.jfr", "jdk.jlink", "jdk.jpackage", "jdk.jshell",
"jdk.jsobject", "jdk.jstatd", "jdk.localedata", "jdk.management.jfr", "jdk.naming.dns",
"jdk.naming.rmi", "jdk.net", "jdk.nio.mapmode", "jdk.random", "jdk.sctp", "jdk.security.auth",
"jdk.security.jgss", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom",
},
OsArch: "aarch64",
OsName: "Linux",
Source: ".:git:13710926b798",
BuildSource: "git:1271f10a26c47e1489a814dd2731f936a588d621",
BuildSourceRepo: "https://github.com/adoptium/temurin-build.git",
SourceRepo: "https://github.com/adoptium/jdk21u.git",
FullVersion: "21.0.4+7-LTS",
SemanticVersion: "21.0.4+7",
BuildInfo: "OS: Linux Version: 5.4.0-150-generic",
JvmVariant: "Hotspot",
JvmVersion: "21.0.4+7-LTS",
ImageType: "JDK",
},
Files: []string{
"/release",
},
},
},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
p := tt.expected
p.SetID()
pkgtest.TestCatalogerFromFileSource(t, tt.fixture, NewJvmDistributionCataloger(), []pkg.Package{p}, nil)
})
}
}
+4 -1
View File
@@ -88,7 +88,10 @@ func parseJVMRelease(_ context.Context, resolver file.Resolver, _ *generic.Envir
installDir := path.Dir(reader.Path())
files, hasJdk := findJvmFiles(resolver, installDir)
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, reader.Path(), ri.ImageType, hasJdk)
// the reason we use the reference to get the real path is in cases where the file cataloger is involved
// (thus the real path is not available except for in the original file reference). This is important
// since the path is critical for distinguishing between different JVM vendors.
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, string(reader.Reference().RealPath), ri.ImageType, hasJdk)
p := pkg.Package{
Name: product,
+1 -1
View File
@@ -11,5 +11,5 @@ import (
// NewOpamPackageManagerCataloger returns a new cataloger object for OCaml opam.
func NewOpamPackageManagerCataloger() pkg.Cataloger {
return generic.NewCataloger("opam-cataloger").
WithParserByGlobs(parseOpamPackage, "*opam")
WithParserByGlobs(parseOpamPackage, "**/*opam")
}
@@ -0,0 +1,240 @@
package php
import (
"context"
"fmt"
"path"
"strings"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
type interpreterCataloger struct {
name string
extensionsGlob string
interpreterClassifiers []binutils.Classifier
}
// NewInterpreterCataloger returns a new cataloger for PHP interpreters (php and php-fpm) as well as any installed C extensions.
func NewInterpreterCataloger() pkg.Cataloger { //nolint:funlen
name := "php-interpreter-cataloger"
m := binutils.ContextualEvidenceMatchers{CatalogerName: name}
return interpreterCataloger{
name: name,
// example matches:
// - as found in php-fpm docker library images: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so
// - as found in alpine images: /usr/lib/php83/modules/bcmath.so
extensionsGlob: "**/php*/**/*.so",
interpreterClassifiers: []binutils.Classifier{
{
Class: "php-cli-binary",
FileGlob: "**/php*",
EvidenceMatcher: m.FileNameTemplateVersionMatcher(
`(.*/|^)php[0-9]*$`,
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-cli",
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: "php-cli",
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "php",
Product: "php",
},
Source: cpe.NVDDictionaryLookupSource,
},
},
},
{
Class: "php-fpm-binary",
FileGlob: "**/php-fpm*",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-fpm",
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: "php-fpm",
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "php",
Product: "php",
},
Source: cpe.NVDDictionaryLookupSource,
},
},
},
{
Class: "php-apache-binary",
FileGlob: "**/apache*/**/libphp*.so",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "libphp",
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: "php",
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "php",
Product: "php",
},
Source: cpe.NVDDictionaryLookupSource,
},
},
},
},
}
}
func (p interpreterCataloger) Name() string {
return p.name
}
func (p interpreterCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
interpreterPkgs, intErrs := p.catalogInterpreters(resolver)
extensionPkgs, extErrs := p.catalogExtensions(resolver)
// TODO: a future iteration of this cataloger could be to read all php.ini / php/conf.d/*.ini files and indicate which extensions are enabled
// and attempt to resolve the extension_dir. This can be tricky as it is a #define in the php source code and not always available
// in configuration. For the meantime we report all extensions present
// create a relationship for each interpreter package to the extensions
var relationships []artifact.Relationship
for _, interpreter := range interpreterPkgs {
for _, extension := range extensionPkgs {
relationships = append(relationships, artifact.Relationship{
From: extension,
To: interpreter,
Type: artifact.DependencyOfRelationship,
})
}
}
var allPkgs []pkg.Package
allPkgs = append(allPkgs, interpreterPkgs...)
allPkgs = append(allPkgs, extensionPkgs...)
return allPkgs, relationships, unknown.Join(intErrs, extErrs)
}
func (p interpreterCataloger) catalogInterpreters(resolver file.Resolver) ([]pkg.Package, error) {
var errs error
var packages []pkg.Package
for _, cls := range p.interpreterClassifiers {
locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil {
// convert any file.Resolver path errors to unknowns with locations
errs = unknown.Join(errs, unknown.ProcessPathErrors(err))
continue
}
for _, location := range locations {
pkgs, err := cls.EvidenceMatcher(cls, binutils.MatcherContext{Resolver: resolver, Location: location})
if err != nil {
errs = unknown.Append(errs, location, err)
continue
}
packages = append(packages, pkgs...)
}
}
return packages, errs
}
func (p interpreterCataloger) catalogExtensions(resolver file.Resolver) ([]pkg.Package, error) {
locations, err := resolver.FilesByGlob(p.extensionsGlob)
if err != nil {
// convert any file.Resolver path errors to unknowns with locations
return nil, unknown.ProcessPathErrors(err)
}
var packages []pkg.Package
var errs error
for _, location := range locations {
pkgs, err := p.catalogExtension(resolver, location)
if err != nil {
errs = unknown.Append(errs, location, err)
continue
}
packages = append(packages, pkgs...)
}
return packages, errs
}
func (p interpreterCataloger) catalogExtension(resolver file.Resolver, location file.Location) ([]pkg.Package, error) {
reader, err := resolver.FileContentsByLocation(location)
defer internal.CloseAndLogError(reader, location.RealPath)
if err != nil {
return nil, unknown.ProcessPathErrors(err)
}
name, cls := p.getClassifier(location.RealPath)
if name == "" || cls == nil {
return nil, nil
}
pkgs, err := cls.EvidenceMatcher(*cls, binutils.MatcherContext{Resolver: resolver, Location: location})
if err != nil {
return nil, unknown.New(location, err)
}
return pkgs, err
}
func (p interpreterCataloger) getClassifier(realPath string) (string, *binutils.Classifier) {
if !strings.HasSuffix(realPath, ".so") {
return "", nil
}
base := path.Base(realPath)
name := strings.TrimSuffix(base, ".so")
var match string
switch name {
case "mysqli":
match = `(mysqlnd|mysqli)?\s*\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+API`
case "opcache":
match = `(?m)\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+`
case "zip":
match = `\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+Zip`
default:
match = fmt.Sprintf(`(?m)(\x00+%s)?\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+API`, name)
}
return name, &binutils.Classifier{
Class: fmt.Sprintf("php-ext-%s-binary", name),
EvidenceMatcher: binutils.FileContentsVersionMatcher(match, p.name),
Package: name,
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: name,
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: fmt.Sprintf("php-%s", name),
Product: fmt.Sprintf("php-%s", name),
},
Source: cpe.GeneratedSource,
},
},
}
}
@@ -0,0 +1,149 @@
package php
import (
"testing"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_InterpreterCataloger(t *testing.T) {
tests := []struct {
name string
fixture string
expectedPkgs []string
expectedRels []string
}{
{
name: "native installation with extensions",
fixture: "image-extensions",
expectedPkgs: []string{
// interpreters
"php-cli @ 8.3.21 (/usr/local/bin/php)",
"php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
// extensions
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so)",
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so)",
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so)",
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so)",
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so)",
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so)",
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so)",
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so)",
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so)",
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so)",
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so)",
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so)",
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so)",
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so)",
},
expectedRels: []string{
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
},
},
{
name: "apache installation with libphp and extensions",
fixture: "image-apache",
expectedPkgs: []string{
// interpreters
"libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
// extensions
"calendar @ 8.2.28 (/usr/lib/php/20220829/calendar.so)",
"ctype @ 8.2.28 (/usr/lib/php/20220829/ctype.so)",
"exif @ 8.2.28 (/usr/lib/php/20220829/exif.so)",
"ffi @ 8.2.28 (/usr/lib/php/20220829/ffi.so)",
"fileinfo @ 8.2.28 (/usr/lib/php/20220829/fileinfo.so)",
"ftp @ 8.2.28 (/usr/lib/php/20220829/ftp.so)",
"gettext @ 8.2.28 (/usr/lib/php/20220829/gettext.so)",
"iconv @ 8.2.28 (/usr/lib/php/20220829/iconv.so)",
"mysqli @ 8.2.28 (/usr/lib/php/20220829/mysqli.so)",
"opcache @ 8.2.28 (/usr/lib/php/20220829/opcache.so)",
"pdo @ 8.2.28 (/usr/lib/php/20220829/pdo.so)",
"pdo_mysql @ 8.2.28 (/usr/lib/php/20220829/pdo_mysql.so)",
"phar @ 8.2.28 (/usr/lib/php/20220829/phar.so)",
"posix @ 8.2.28 (/usr/lib/php/20220829/posix.so)",
"readline @ 8.2.28 (/usr/lib/php/20220829/readline.so)",
"shmop @ 8.2.28 (/usr/lib/php/20220829/shmop.so)",
"simplexml @ 8.2.28 (/usr/lib/php/20220829/simplexml.so)",
"sockets @ 8.2.28 (/usr/lib/php/20220829/sockets.so)",
"sysvmsg @ 8.2.28 (/usr/lib/php/20220829/sysvmsg.so)",
"sysvsem @ 8.2.28 (/usr/lib/php/20220829/sysvsem.so)",
"sysvshm @ 8.2.28 (/usr/lib/php/20220829/sysvshm.so)",
"tokenizer @ 8.2.28 (/usr/lib/php/20220829/tokenizer.so)",
"xml @ 8.2.28 (/usr/lib/php/20220829/xml.so)",
"xmlreader @ 8.2.28 (/usr/lib/php/20220829/xmlreader.so)",
"xmlwriter @ 8.2.28 (/usr/lib/php/20220829/xmlwriter.so)",
"xsl @ 8.2.28 (/usr/lib/php/20220829/xsl.so)",
},
expectedRels: []string{
"calendar @ 8.2.28 (/usr/lib/php/20220829/calendar.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"ctype @ 8.2.28 (/usr/lib/php/20220829/ctype.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"exif @ 8.2.28 (/usr/lib/php/20220829/exif.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"ffi @ 8.2.28 (/usr/lib/php/20220829/ffi.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"fileinfo @ 8.2.28 (/usr/lib/php/20220829/fileinfo.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"ftp @ 8.2.28 (/usr/lib/php/20220829/ftp.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"gettext @ 8.2.28 (/usr/lib/php/20220829/gettext.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"iconv @ 8.2.28 (/usr/lib/php/20220829/iconv.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"mysqli @ 8.2.28 (/usr/lib/php/20220829/mysqli.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"opcache @ 8.2.28 (/usr/lib/php/20220829/opcache.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"pdo @ 8.2.28 (/usr/lib/php/20220829/pdo.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"pdo_mysql @ 8.2.28 (/usr/lib/php/20220829/pdo_mysql.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"phar @ 8.2.28 (/usr/lib/php/20220829/phar.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"posix @ 8.2.28 (/usr/lib/php/20220829/posix.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"readline @ 8.2.28 (/usr/lib/php/20220829/readline.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"shmop @ 8.2.28 (/usr/lib/php/20220829/shmop.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"simplexml @ 8.2.28 (/usr/lib/php/20220829/simplexml.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sockets @ 8.2.28 (/usr/lib/php/20220829/sockets.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sysvmsg @ 8.2.28 (/usr/lib/php/20220829/sysvmsg.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sysvsem @ 8.2.28 (/usr/lib/php/20220829/sysvsem.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sysvshm @ 8.2.28 (/usr/lib/php/20220829/sysvshm.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"tokenizer @ 8.2.28 (/usr/lib/php/20220829/tokenizer.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xml @ 8.2.28 (/usr/lib/php/20220829/xml.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xmlreader @ 8.2.28 (/usr/lib/php/20220829/xmlreader.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xmlwriter @ 8.2.28 (/usr/lib/php/20220829/xmlwriter.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xsl @ 8.2.28 (/usr/lib/php/20220829/xsl.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewInterpreterCataloger()
pkgtest.NewCatalogTester().
WithImageResolver(t, tt.fixture).
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
//Expects(tt.expected, nil).
ExpectsPackageStrings(tt.expectedPkgs).
ExpectsRelationshipStrings(tt.expectedRels).
TestCataloger(t, c)
})
}
}
@@ -0,0 +1,11 @@
FROM --platform=linux/amd64 httpd:2.4.63-bookworm AS builder
RUN apt update -y && apt install -y libapache2-mod-php php8.2-memcache php8.2-memcache php8.2-xml php8.2-mysqli php8.2-opcache
FROM busybox:latest
# phplib.so
COPY --from=builder /usr/lib/apache2/ /usr/lib/apache2/
# php extensions
COPY --from=builder /usr/lib/php/ /usr/lib/php/
@@ -0,0 +1,105 @@
# source https://github.com/nextcloud/docker/blob/master/30/fpm-alpine/Dockerfile#L1
FROM --platform=linux/amd64 php:8.3-fpm-alpine3.21 AS builder
# entrypoint.sh and cron.sh dependencies
RUN set -ex; \
\
apk add --no-cache \
imagemagick \
imagemagick-pdf \
imagemagick-jpeg \
imagemagick-raw \
imagemagick-tiff \
imagemagick-heic \
imagemagick-webp \
imagemagick-svg \
rsync \
; \
\
rm /var/spool/cron/crontabs/root; \
echo '*/5 * * * * php -f /var/www/html/cron.php' > /var/spool/cron/crontabs/www-data
# install the PHP extensions we need
# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html
RUN set -ex; \
\
apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
autoconf \
freetype-dev \
gmp-dev \
icu-dev \
imagemagick-dev \
libevent-dev \
libjpeg-turbo-dev \
libmcrypt-dev \
libmemcached-dev \
libpng-dev \
libwebp-dev \
libxml2-dev \
libzip-dev \
openldap-dev \
pcre-dev \
postgresql-dev \
; \
\
docker-php-ext-configure ftp --with-openssl-dir=/usr; \
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \
docker-php-ext-configure ldap; \
docker-php-ext-install -j "$(nproc)" \
bcmath \
exif \
ftp \
gd \
gmp \
intl \
ldap \
opcache \
pcntl \
pdo_mysql \
pdo_pgsql \
sysvsem \
zip \
; \
\
# pecl will claim success even if one install fails, so we need to perform each install separately
pecl install APCu-5.1.24; \
pecl install igbinary-3.2.16; \
pecl install imagick-3.8.0; \
pecl install memcached-3.3.0 \
--configureoptions 'enable-memcached-igbinary="yes"'; \
pecl install redis-6.2.0 \
--configureoptions 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"'; \
\
docker-php-ext-enable \
apcu \
igbinary \
imagick \
memcached \
redis \
; \
rm -r /tmp/pear; \
\
runDeps="$( \
scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)"; \
apk add --no-network --virtual .nextcloud-phpext-rundeps $runDeps; \
apk del --no-network .build-deps
FROM busybox:latest
# interpreters + process manager
COPY --from=builder /usr/local/sbin/php-fpm /usr/local/sbin/php-fpm
COPY --from=builder /usr/local/bin/php /usr/local/bin/php
# extensions
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
# configs
COPY --from=builder /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.conf
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=builder /usr/local/etc/php-fpm.d /usr/local/etc/php-fpm.d
COPY --from=builder /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.conf
+12 -1
View File
@@ -716,10 +716,15 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
"certifi @ 2020.12.5 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
"certifi @ 2020.12.5 (/usr/local/lib/python3.9/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
"chardet @ 3.0.4 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
"distlib @ 0.3.9 (/usr/local/lib/python3.9/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)",
"filelock @ 3.18.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)",
"idna @ 2.10 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
"idna @ 2.10 (/usr/local/lib/python3.9/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
"platformdirs @ 4.3.8 (/usr/local/lib/python3.9/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)",
"six @ 1.16.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages)",
"soupsieve @ 2.2.1 (/usr/local/lib/python3.9/dist-packages) [dependency-of] beautifulsoup4 @ 4.9.3 (/usr/local/lib/python3.9/dist-packages)",
"urllib3 @ 1.26.18 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
"virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages) [dependency-of] filelock @ 3.18.0 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "testing", but we are installing it
"wcwidth @ 0.2.13 (/usr/local/lib/python3.9/dist-packages) [dependency-of] blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages)",
// purely python 3.8 dist-packages
@@ -736,9 +741,14 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
"certifi @ 2020.12.5 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
"certifi @ 2020.12.5 (/usr/local/lib/python3.8/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.8/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
"chardet @ 3.0.4 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
"distlib @ 0.3.9 (/usr/local/lib/python3.8/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages)",
"filelock @ 3.16.1 (/usr/local/lib/python3.8/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages)",
"idna @ 2.10 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
"idna @ 2.10 (/usr/local/lib/python3.8/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.8/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
"platformdirs @ 4.3.6 (/usr/local/lib/python3.8/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages)",
"soupsieve @ 2.2 (/usr/local/lib/python3.8/dist-packages) [dependency-of] beautifulsoup4 @ 4.9.2 (/usr/local/lib/python3.8/dist-packages)",
"urllib3 @ 1.26.18 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
"virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages) [dependency-of] filelock @ 3.16.1 (/usr/local/lib/python3.8/dist-packages)", // available when extra == "testing", but we are installing it
"xmod @ 1.8.1 (/usr/local/lib/python3.8/dist-packages) [dependency-of] runs @ 1.2.2 (/usr/local/lib/python3.8/dist-packages)",
// project 1 virtual env
@@ -758,7 +768,8 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
// Note: we'll only see new relationships, so any relationship where there is at least one new player (in FROM or TO)
"blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)", // note: depends on global site package!
"python-editor @ 1.0.4 (/usr/local/lib/python3.9/dist-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)", // note: depends on global site package!
"readchar @ 4.2.0 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)",
"readchar @ 4.2.1 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)",
"setuptools @ 44.0.0 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "test", but we are installing it
"soupsieve @ 2.3 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] beautifulsoup4 @ 4.10.0 (/app/project1/venv/lib/python3.9/site-packages)",
// project 2 virtual env
+6 -4
View File
@@ -115,14 +115,16 @@ func wheelEggDependencySpecifier(p pkg.Package) dependency.Specification {
}
}
// extractPackageName removes any extras or version constraints from a given Requires-Dist field value (and
// extractPackageName removes any extras, version constraints or environment markers from a given Requires-Dist field value (and
// semantically similar fields), leaving only the package name.
func extractPackageName(s string) string {
// examples:
// html5lib ; extra == 'html5lib' --> html5lib
// soupsieve (>1.2) --> soupsieve
// requests [security,tests] --> requests
// requests >= 2.8.1 --> requests
// requests (>= 2.8.1) --> requests
// requests ; python_version < "2.7" --> requests
return strings.TrimSpace(internal.SplitAny(s, "(;")[0])
return strings.TrimSpace(internal.SplitAny(s, "[(<!=>~;")[0])
}
func extractPackageNames(ss []string) []string {
var names []string
@@ -13,10 +13,10 @@ RUN python3.9 -m pip install click==8.0.3 beautifulsoup4==4.9.3 soupsieve==2.2.1
RUN python3.9 -m pip install six==1.16.0 wcwidth==0.2.13 blessed==1.20.0 python-editor==1.0.4 # total dependencies for inquirer in project1 (which is linked)
RUN python3.9 -m pip install requests==2.25.0 certifi==2020.12.5 chardet==3.0.4 idna==2.10 urllib3==1.26.18 # total dependencies for requests
RUN python3.8 -m pip install readchar==4.2.1
RUN python3.8 -m pip install click==8.0.2 beautifulsoup4==4.9.2 soupsieve==2.2.0 requests==2.25.0
RUN python3.8 -m pip install runs==1.2.2 xmod==1.8.1 # partial dependencies for inquirer in project2 (which is a red herring)
RUN python3.8 -m pip install requests==2.25.0 certifi==2020.12.5 chardet==3.0.4 idna==2.10 urllib3==1.26.18 # total dependencies for requests
RUN python3.8 -m pip install readchar==4.2.0
# create directories for the two projects
RUN mkdir -p /app/project1 /app/project2
+18 -8
View File
@@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
_ "modernc.org/sqlite"
"github.com/anchore/syft/syft/artifact"
@@ -29,14 +30,15 @@ func Test_DBCataloger(t *testing.T) {
FoundBy: "rpm-db-cataloger",
PURL: "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm",
Metadata: pkg.RpmDBEntry{
Name: "basesystem",
Version: "11",
Arch: "noarch",
Release: "13.el9",
SourceRpm: "basesystem-11-13.el9.src.rpm",
Size: 0,
Vendor: "Rocky Enterprise Software Foundation",
Provides: []string{"basesystem"},
Name: "basesystem",
Version: "11",
Arch: "noarch",
Release: "13.el9",
SourceRpm: "basesystem-11-13.el9.src.rpm",
Size: 0,
Vendor: "Rocky Enterprise Software Foundation",
Signatures: mustParseSignatures(t, "RSA/SHA256, Wed May 11 11:12:32 2022, Key ID 702d426d350d275d"),
Provides: []string{"basesystem"},
Requires: []string{
"filesystem",
"rpmlib(CompressedFileNames)",
@@ -65,6 +67,7 @@ func Test_DBCataloger(t *testing.T) {
Release: "6.el9_1",
SourceRpm: "bash-5.1.8-6.el9_1.src.rpm",
Size: 7738634,
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon Jan 23 22:49:22 2023, Key ID 702d426d350d275d"),
ModularityLabel: strRef(""),
Vendor: "Rocky Enterprise Software Foundation",
Provides: []string{
@@ -117,6 +120,7 @@ func Test_DBCataloger(t *testing.T) {
Release: "2.el9",
SourceRpm: "filesystem-3.16-2.el9.src.rpm",
Size: 106,
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"),
ModularityLabel: strRef(""),
Vendor: "Rocky Enterprise Software Foundation",
Provides: []string{
@@ -334,3 +338,9 @@ func Test_denySelfReferences(t *testing.T) {
})
}
}
func mustParseSignatures(t testing.TB, sigs ...string) []pkg.RpmSignature {
signatures, err := parseSignatures(sigs...)
require.NoError(t, err)
return signatures
}
+171 -11
View File
@@ -1,13 +1,18 @@
package redhat
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"sort"
"strconv"
"time"
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
"github.com/sassoftware/go-rpmutils"
rpmdb "github.com/anchore/go-rpmdb/pkg"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
@@ -15,6 +20,42 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type pgpSig struct {
_ [3]byte
Date int32
KeyID [8]byte
PubKeyAlgo uint8
HashAlgo uint8
}
type textSig struct {
_ [2]byte
PubKeyAlgo uint8
HashAlgo uint8
_ [4]byte
Date int32
_ [4]byte
KeyID [8]byte
}
type pgp4Sig struct {
_ [2]byte
PubKeyAlgo uint8
HashAlgo uint8
_ [17]byte
KeyID [8]byte
_ [2]byte
Date int32
}
var pubKeyLookup = map[uint8]string{
0x01: "RSA",
}
var hashLookup = map[uint8]string{
0x02: "SHA1",
0x08: "SHA256",
}
// parseRpmArchive parses a single RPM
func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
rpm, err := rpmutils.ReadRpm(reader)
@@ -44,21 +85,140 @@ func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen
files, err := rpm.Header.GetFiles()
logRpmArchiveErr(reader.Location, "files", err)
rsa, err := rpm.Header.GetBytes(rpmutils.SIG_RSA)
logRpmArchiveErr(reader.Location, "rsa signature", err)
pgp, err := rpm.Header.GetBytes(rpmutils.SIG_PGP)
logRpmArchiveErr(reader.Location, "pgp signature", err)
var allSigs [][]byte
allSigs = append(allSigs, rsa)
allSigs = append(allSigs, pgp)
sigs, err := parseSignatureHeaders(allSigs)
logRpmArchiveErr(reader.Location, "signature", err)
metadata := pkg.RpmArchive{
Name: nevra.Name,
Version: nevra.Version,
Epoch: parseEpoch(nevra.Epoch),
Arch: nevra.Arch,
Release: nevra.Release,
SourceRpm: sourceRpm,
Vendor: vendor,
Size: int(size),
Files: mapFiles(files, digestAlgorithm),
Name: nevra.Name,
Version: nevra.Version,
Epoch: parseEpoch(nevra.Epoch),
Arch: nevra.Arch,
Release: nevra.Release,
SourceRpm: sourceRpm,
Signatures: sigs,
Vendor: vendor,
Size: int(size),
Files: mapFiles(files, digestAlgorithm),
}
return []pkg.Package{newArchivePackage(ctx, reader.Location, metadata, licenses)}, nil, nil
}
func parseSignatureHeaders(data [][]byte) ([]pkg.RpmSignature, error) {
sigMap := make(map[string]pkg.RpmSignature)
var keys []string
for _, sig := range data {
if len(sig) == 0 {
continue
}
s, err := parsePGP(sig)
if err != nil {
log.WithFields("error", err).Trace("unable to parse RPM archive signature")
return nil, err
}
k := s.String()
if _, ok := sigMap[k]; ok {
// if we have a duplicate signature, just skip it
continue
}
sigMap[k] = *s
keys = append(keys, k)
}
var signatures []pkg.RpmSignature
sort.Strings(keys)
for _, k := range keys {
signatures = append(signatures, sigMap[k])
}
return signatures, nil
}
func parsePGP(data []byte) (*pkg.RpmSignature, error) {
var tag, signatureType, version uint8
r := bytes.NewReader(data)
err := binary.Read(r, binary.BigEndian, &tag)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &signatureType)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &version)
if err != nil {
return nil, err
}
switch signatureType {
case 0x01:
switch version {
case 0x1c:
sig := textSig{}
err = binary.Read(r, binary.BigEndian, &sig)
if err != nil {
return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
}
return &pkg.RpmSignature{
PublicKeyAlgorithm: pubKeyLookup[sig.PubKeyAlgo],
HashAlgorithm: hashLookup[sig.HashAlgo],
Created: time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006"),
IssuerKeyID: fmt.Sprintf("%x", sig.KeyID),
}, nil
default:
return decodePGPSig(version, r)
}
case 0x02:
return decodePGPSig(version, r)
}
return nil, fmt.Errorf("unknown signature type: %d", signatureType)
}
func decodePGPSig(version uint8, r io.Reader) (*pkg.RpmSignature, error) {
var pubKeyAlgo, hashAlgo, pkgDate string
var keyID [8]byte
switch {
case version > 0x15:
sig := pgp4Sig{}
err := binary.Read(r, binary.BigEndian, &sig)
if err != nil {
return nil, fmt.Errorf("invalid PGP v4 signature on decode: %w", err)
}
pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
hashAlgo = hashLookup[sig.HashAlgo]
pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
keyID = sig.KeyID
default:
sig := pgpSig{}
err := binary.Read(r, binary.BigEndian, &sig)
if err != nil {
return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
}
pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
hashAlgo = hashLookup[sig.HashAlgo]
pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
keyID = sig.KeyID
}
return &pkg.RpmSignature{
PublicKeyAlgorithm: pubKeyAlgo,
HashAlgorithm: hashAlgo,
Created: pkgDate,
IssuerKeyID: fmt.Sprintf("%x", keyID),
}, nil
}
func getDigestAlgorithm(location file.Location, header *rpmutils.RpmHeader) string {
digestAlgorithm, err := header.GetString(rpmutils.FILEDIGESTALGO)
logRpmArchiveErr(location, "file digest algo", err)
@@ -109,6 +269,6 @@ func parseEpoch(epoch string) *int {
func logRpmArchiveErr(location file.Location, operation string, err error) {
if err != nil {
log.Debugf("ERROR in parse_rpm_archive %s file: %s: %v", operation, location.RealPath, err)
log.WithFields("error", err, "operation", operation, "path", location.RealPath).Trace("unable to parse RPM archive")
}
}
@@ -2,8 +2,14 @@ package redhat
import (
"context"
"encoding/hex"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@@ -14,11 +20,15 @@ func TestParseRpmFiles(t *testing.T) {
abcRpmLocation := file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm")
zorkRpmLocation := file.NewLocation("zork-1.0.3-1.el7.x86_64.rpm")
tests := []struct {
fixture string
expected []pkg.Package
name string
fixtureDir string
fixtureImage string
skipFiles bool
expected []pkg.Package
}{
{
fixture: "test-fixtures/rpms",
name: "go case",
fixtureDir: "test-fixtures/rpms",
expected: []pkg.Package{
{
Name: "abc",
@@ -37,8 +47,16 @@ func TestParseRpmFiles(t *testing.T) {
Release: "9.hg20160905.el7",
Version: "1.01",
SourceRpm: "abc-1.01-9.hg20160905.el7.src.rpm",
Size: 17396,
Vendor: "Fedora Project",
Signatures: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Wed Sep 21 07:09:44 2016",
IssuerKeyID: "6a2faea2352c64e5",
},
},
Size: 17396,
Vendor: "Fedora Project",
Files: []pkg.RpmFileRecord{
{"/usr/bin/abc", 33261, 7120, file.Digest{"sha256", "8f8495a65c66762b60afa0c3949d81b275ca6fa0601696caba5af762f455d0b9"}, "root", "root", ""},
{"/usr/share/doc/abc-1.01", 16877, 4096, file.Digest{}, "root", "root", ""},
@@ -66,7 +84,15 @@ func TestParseRpmFiles(t *testing.T) {
Version: "1.0.3",
SourceRpm: "zork-1.0.3-1.el7.src.rpm",
Size: 262367,
Vendor: "Fedora Project",
Signatures: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Tue Mar 2 17:32:21 2021",
IssuerKeyID: "6a2faea2352c64e5",
},
},
Vendor: "Fedora Project",
Files: []pkg.RpmFileRecord{
{"/usr/bin/zork", 33261, 115440, file.Digest{"sha256", "31b2ffc20b676a8fff795a45308f584273b9c47e8f7e196b4f36220b2734b472"}, "root", "root", ""},
{"/usr/share/doc/zork-1.0.3", 16877, 38, file.Digest{}, "root", "root", ""},
@@ -83,20 +109,128 @@ func TestParseRpmFiles(t *testing.T) {
},
},
{
fixture: "test-fixtures/bad",
name: "bad rpms",
fixtureDir: "test-fixtures/bad",
},
{
name: "rpms with signatures from RSA header",
fixtureImage: "image-rpm-archive",
skipFiles: true,
expected: []pkg.Package{
{
Name: "postgresql14-server",
Version: "0:14.10-1PGDG.rhel9",
PURL: "pkg:rpm/postgresql14-server@14.10-1PGDG.rhel9?arch=x86_64&epoch=0&upstream=postgresql14-14.10-1PGDG.rhel9.src.rpm",
Locations: file.NewLocationSet(file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm")),
FoundBy: "rpm-archive-cataloger",
Type: pkg.RpmPkg,
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("PostgreSQL", file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm"))),
Language: "",
CPEs: nil,
Metadata: pkg.RpmArchive{
Name: "postgresql14-server",
Version: "14.10",
Epoch: ref(0),
Arch: "x86_64",
Release: "1PGDG.rhel9",
SourceRpm: "postgresql14-14.10-1PGDG.rhel9.src.rpm",
Size: 24521699,
Signatures: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Tue Jan 2 16:45:56 2024",
IssuerKeyID: "40bca2b408b40d20",
},
},
Vendor: "PostgreSQL Global Development Group",
// note: files are not asserted in this test
},
},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var opts []cmp.Option
if test.skipFiles {
opts = append(opts, cmpopts.IgnoreFields(pkg.RpmArchive{}, "Files"))
}
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
WithCompareOptions(opts...).
FromDirectory(t, test.fixtureDir).
WithImageResolver(t, test.fixtureImage).
IgnoreLocationLayer().
Expects(test.expected, nil).
TestCataloger(t, NewArchiveCataloger())
})
}
}
func Test_parseRSA(t *testing.T) {
tests := []struct {
name string
data string
want *pkg.RpmSignature
wantErr assert.ErrorAssertionFunc
}{
{
name: "older RSA header",
data: "89021503050058d3e39b0946fca2c105b9de0102b12a1000a2b3d347b51142e83b2de5e03ba9096f6330b72c140e46200d662b01c78534d14fab2ad4f07325119386830dd590219f27a22e420680283c500c40e6fba95404884b0a0abca8f198030ddc03653b7db2883b8230687e9e73d43eb5a24dbabfa48bbb3d1151ed264744e5e8ca169b0c4673a1440a9b99e53e693c9722f6423833cd7795e3044227fb922e21b7c007f03e923fae3f04d1ac2e8581e68c6790115b6dccfc02c8cb41681ed84785df086d6e26008c257d088a524ba2e7a7a5f41ad26b106c67b87fe48118b69662db612c23d2140059286f1ba7764627def6867ad0e11fe3a01fb1422dabe6f5cdf4cd876dc4fadfd2364bc3ba3758db94aaf3b82368cba65cf762287f713eb7ddc773acf93b083c739577a7eaf1f99e7dcbb8db1da050490e9fb67c838448db060a9e619d318c96f03e4363808d84ce29e8c102c290cc2bfab5746f3d9ddc9eb8b428f3ad2678abb2d46e846ddca7fc41322d76a97be6d416b4750f23320ec725e082be4496483b4cd3a3d2c515b3c8a6e27541139d809245140303877b84842ed2dd0454a78b2dfb7d6213784697077a8167942ebda5995a28d8256957e33e301706c35944ae05c7a54a4dd89be654d26cefa5cf0f616bbeaf317138371b09c5bbd5531f716020e553354ce5dbce3d9bb72f21e1857408dfd5a35250ff40f61ae1e25409ae9d21db76b8878341f4762a22be2189",
// RSA/SHA1, Thu Mar 23 15:02:51 2017, Key ID 0946fca2c105b9de
want: &pkg.RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA1",
Created: "Thu Mar 23 15:02:51 2017",
IssuerKeyID: "0946fca2c105b9de",
},
},
{
name: "newer RSA header",
data: "89024a04000108003416210421cb256ae16fc54c6e652949702d426d350d275d050262804369161c72656c656e6740726f636b796c696e75782e6f7267000a0910702d426d350d275dc8910ffd14f0f80297481fea648e7ba5a74bce10c5faccc2bbe588caece04be34d304a6a445538afc97a7033d43c983d27cc8f5ee515b2dd92f3e03354c413e55372a4d19386eb0f2354f9a26ee5fc2e56dfda49555e4a58b49279b70cd2036b04f28125f85942f640f2984e29e079f26bf6f76831d83d95983aa084a3e7b6327be2e23d0d799c4b4d1cfb36147ddfb782bf9df7b331d97f4f46b38f968b6130d87b0ef6bb0d424390fe34e38092babed37440569a93f55f50a2bdb58be0259f35badf7e728bd49824ed47f69fa53b6e26736bde4d8358d959b090e88054c3e179745dc7377e41b54b4e10223f4859e88162c7c5ec64b78d36cf8a914c1c2deb8c4f19a70d406e70756a89195d6aee488a9b40b9dbb76b2c38e528eb88d08ec35774a48ed9ce4e0dfac45cb7613ad5921f54c61d3aae5d7b3ab0e2e6ff867ac8f395b37af78b5c01022a4a4e62f7a99425fccb7439880cd6b393a3050b2e9512693bc36f6fe9de2921dda59710a1508965065244cf9f0f8cfc5bd554777f1a84d2249339234d62f2441249f617ad7df4fb01367a91d3a880e86fdb84bc6d03a127b44a28c6ceadef89e438db9640aa59b8a3f460b07272511f8187a5f3b163c8fd1caa61667401bce2ccdb1c176c46be10ef8033903132cca5889fa3661b2fba590c41fa1c104c08426677bdbf745a52ccd28f581960cf9d7e4ede3b9584aacb2f20ef93",
// RSA/SHA256, Sun May 15 00:03:53 2022, Key ID 702d426d350d275d
want: &pkg.RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Sun May 15 00:03:53 2022",
IssuerKeyID: "702d426d350d275d",
},
},
{
name: "example from rocky9",
data: "8901b304000108001d162104d4bf08ae67a0b4c7a1dbccd240bca2b408b40d20050265943dc4000a091040bca2b408b40d203b270bff71678ffeb190833a19a82112f59eee64cba186ab454d4526e0b3c8797e723f6916daff1b1f18cbf53c0da5d398a3a42065e79e5ca939f721652f38400dd4cac1107a902b1dae880649437ad0242444f3f07115172cae0a207b7cf8340af2f4a94976325f1dc165d5c2a564be322c4e130adb6217e7138b689f08898c407b223aa1ff8f8d592f31eba2256c02fae70ce4022d688a487972646b8bf1b518b5d6549c1e60fd812134422d9fdb41cf799f5eab80e48b4ab7cff84362dc867ed1af1416dd78e92bcc59217de7064b9a015d94a5097788689b9b6fbdeea679cfe4a6947f73dc3a6c810f2cb999d279b01564422d1500fc1bd8bd1eefa2d60660127ffef24067354660f93c0faf81f4edd599dd7e4b77fe4bff6c7a0ea83530c817c38d1f2364175883c6ef7b6dec86ad282bdd5138b8597567db96810c4ed6454a4ab1d98f0425dcd8892a5d46ed9289cb3ae3e1f1e2663d3e8188e873428f6cf7163563ed3860edc4fee81522389508847e692e2d13310eb4b40f7fdd7eb364a0b2dc",
// RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20
want: &pkg.RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Tue Jan 2 16:45:56 2024",
IssuerKeyID: "40bca2b408b40d20",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
data, err := hex.DecodeString(tt.data)
require.NoError(t, err)
got, err := parsePGP(data)
if !tt.wantErr(t, err) {
return
}
assert.Equal(t, tt.want, got)
})
}
}
func ref[T any](v T) *T {
return &v
}
func Test_corruptRpmArchive(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/bad/bad.rpm").
+76 -2
View File
@@ -2,12 +2,13 @@ package redhat
import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
rpmdb "github.com/anchore/go-rpmdb/pkg"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
@@ -69,6 +70,14 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
files, err := extractRpmFileRecords(resolver, *entry)
errs = unknown.Join(errs, err)
// there is a period of time when RPM DB entries contain both PGP and RSA signatures that are the same.
// This appears to be a holdover, where nowadays only the RSA Header is used.
sigs, err := parseSignatures(strings.TrimSpace(entry.PGP), strings.TrimSpace(entry.RSAHeader))
if err != nil {
log.WithFields("error", err, "location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)).Trace("unable to parse signatures for package %s", entry.Name)
sigs = nil
}
metadata := pkg.RpmDBEntry{
Name: entry.Name,
Version: entry.Version,
@@ -76,6 +85,7 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
Arch: entry.Arch,
Release: entry.Release,
SourceRpm: entry.SourceRpm,
Signatures: sigs,
Vendor: entry.Vendor,
Size: entry.Size,
ModularityLabel: &entry.Modularitylabel,
@@ -110,6 +120,70 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
return allPkgs, nil, errs
}
func parseSignatures(sigs ...string) ([]pkg.RpmSignature, error) {
var parsedSigs []pkg.RpmSignature
var errs error
for _, sig := range sigs {
if sig == "" {
continue
}
parts := strings.Split(sig, ",")
if len(parts) != 3 {
errs = errors.Join(fmt.Errorf("invalid signature format: %s", sig))
continue
}
methodParts := strings.SplitN(strings.TrimSpace(parts[0]), "/", 2)
if len(methodParts) != 2 {
errs = errors.Join(fmt.Errorf("invalid signature method format: %s", parts[0]))
continue
}
pka := strings.TrimSpace(methodParts[0])
hash := strings.TrimSpace(methodParts[1])
if pka == "" || hash == "" {
errs = errors.Join(fmt.Errorf("invalid signature method values: public-key=%q hash=%q", pka, hash))
continue
}
created := strings.TrimSpace(parts[1])
if created == "" {
errs = errors.Join(fmt.Errorf("invalid signature created value: %q", parts[1]))
continue
}
issuerFields := strings.Split(strings.TrimSpace(parts[2]), " ")
var issuer string
switch len(issuerFields) {
case 0:
errs = errors.Join(fmt.Errorf("no signature issuer value: %q", parts[2]))
case 1:
issuer = issuerFields[0]
default:
issuer = issuerFields[len(issuerFields)-1]
if issuer == "" {
errs = errors.Join(fmt.Errorf("invalid signature issuer value: %q", parts[2]))
continue
}
}
if len(issuer) < 5 {
errs = errors.Join(fmt.Errorf("invalid signature issuer length: %q", parts[2]))
continue
}
parsedSig := pkg.RpmSignature{
PublicKeyAlgorithm: pka,
HashAlgorithm: hash,
Created: created,
IssuerKeyID: issuer,
}
parsedSigs = append(parsedSigs, parsedSig)
}
return parsedSigs, errs
}
// The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version].
// RPM version comparison depends on comparing at least the version and release fields together as a subset of the
// naming scheme. This toELVersion function takes a RPM DB package information and converts it into a minimally comparable
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
@@ -222,6 +223,132 @@ func Test_corruptRpmDbEntry(t *testing.T) {
TestParser(t, parseRpmDB)
}
func TestParseSignatures(t *testing.T) {
tests := []struct {
name string
sigs []string
expected []pkg.RpmSignature
expectedError require.ErrorAssertionFunc
}{
{
name: "valid signature",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
},
},
{
name: "multiple valid signatures",
sigs: []string{
"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
"DSA/SHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789",
},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
{
PublicKeyAlgorithm: "DSA",
HashAlgorithm: "SHA1",
Created: "Tue Jun 14 09:45:12 2023",
IssuerKeyID: "123abc456def789",
},
},
},
{
name: "no signatures",
sigs: []string{},
expected: nil,
},
{
name: "empty signatures",
sigs: []string{"", "", ""},
expected: nil,
},
{
name: "invalid parts count",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022"},
expected: nil,
expectedError: require.Error,
},
{
name: "invalid method format",
sigs: []string{"RSASHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
expected: nil,
expectedError: require.Error,
},
{
name: "empty method values",
sigs: []string{"/, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
expected: nil,
expectedError: require.Error,
},
{
name: "empty created value",
sigs: []string{"RSA/SHA256, , Key ID 702d426d350d275d"},
expected: nil,
expectedError: require.Error,
},
{
name: "empty issuer value",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID "},
expected: nil,
expectedError: require.Error,
},
{
name: "issuer without prefix",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, 702d426d350d275d"},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
},
},
{
name: "mixed valid and invalid signatures",
sigs: []string{
"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
"DSASHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789",
},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
},
expectedError: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.expectedError == nil {
tt.expectedError = require.NoError
}
got, err := parseSignatures(tt.sigs...)
tt.expectedError(t, err)
if err != nil {
return
}
require.Equal(t, tt.expected, got)
})
}
}
func intRef(i int) *int {
return &i
}
@@ -0,0 +1,26 @@
FROM --platform=linux/amd64 rockylinux:9 AS rpm-downloader
# download a signed RPM (PostgreSQL in this example, known to be signed)
# using PostgreSQL official RPM which is signed with their GPG key
# $ rpm -Kv postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
# postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm:
# Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY
# Header SHA256 digest: OK
# Header SHA1 digest: OK
# Payload SHA256 digest: OK
# MD5 digest: OK
#
# $ rpm -ivh --nodeps --force postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
# warning: postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm: Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY
# Verifying... ################################# [100%]
# Preparing... ################################# [100%]
#
# $ rpm -q --qf '%{NAME}-%{VERSION}-%{RELEASE} %{RSAHEADER:pgpsig}\n' postgresql14-server-14.10-1PGDG.rhel9.x86_64
# postgresql14-server-14.10-1PGDG.rhel9 RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20
RUN curl -O https://download.postgresql.org/pub/repos/yum/14/redhat/rhel-9-x86_64/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
FROM scratch
COPY --from=rpm-downloader /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
@@ -59,7 +59,7 @@ func TestTerraformCataloger(t *testing.T) {
Metadata: pkg.TerraformLockProviderEntry{
URL: "registry.terraform.io/hashicorp/google",
Version: "6.8.0",
Constraints: "6.8.0",
Constraints: "",
Hashes: []string{
"h1:GlCaVPk6eKMg2ZbRY7C5tUeHGNIABT+qFtMl8+XWZHM=",
"zh:1b78f4451f1617092eb6891c9c13eda79671060601c40947feea6794c732157a",
@@ -26,7 +26,6 @@ provider "registry.terraform.io/hashicorp/aws" {
provider "registry.terraform.io/hashicorp/google" {
version = "6.8.0"
constraints = "6.8.0"
hashes = [
"h1:GlCaVPk6eKMg2ZbRY7C5tUeHGNIABT+qFtMl8+XWZHM=",
"zh:1b78f4451f1617092eb6891c9c13eda79671060601c40947feea6794c732157a",
+1 -1
View File
@@ -365,7 +365,7 @@ func (b *licenseBuilder) licensesFromEvidenceAndContent(evidence []licenses.Evid
func (b *licenseBuilder) licenseFromContentHash(content string) License {
hash := sha256HexFromString(content)
value := "LicenseRef-sha256:" + hash
value := "sha256:" + hash
return License{
Value: value,
+1 -1
View File
@@ -149,7 +149,7 @@ func TestLicenseSet_Add(t *testing.T) {
Locations: file.NewLocationSet(),
},
{
Value: "LicenseRef-sha256:eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
Value: "sha256:eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
Contents: readFileAsString("../../internal/licenses/test-fixtures/nvidia-software-and-cuda-supplement"),
Type: license.Declared,
},

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