Merge branch 'main' into 4184-gguf-parser

* main:
  chore(deps): update tools to latest versions (#4302)
  chore(deps): bump github.com/github/go-spdx/v2 from 2.3.3 to 2.3.4 (#4301)
  chore(deps): bump github/codeql-action from 4.30.8 to 4.30.9 (#4299)
  support universal (fat) mach-o binary files (#4278)
  chore(deps): bump sigstore/cosign-installer from 3.10.0 to 4.0.0 (#4296)
  chore(deps): bump anchore/sbom-action from 0.20.7 to 0.20.8 (#4297)
  convert posix path back to windows (#4285)
  Remove duplicate image source providers (#4289)
  chore(deps): bump anchore/sbom-action from 0.20.6 to 0.20.7 (#4293)
  feat: add option to fetch remote licenses for pnpm-lock.yaml files (#4286)
  Add PDM parser (#4234)
  chore(deps): update tools to latest versions (#4291)
  fix: panic during java archive maven resolution (#4290)
  Extract zip archive with multiple entries (#4283)
  chore: update to use old configuration on new cosign (#4287)
  chore(deps): update anchore dependencies (#4282)
  chore(deps): bump github.com/mholt/archives from 0.1.3 to 0.1.5 (#4280)
  add docs to configs (#4281)
This commit is contained in:
Christopher Phillips 2025-10-22 13:21:59 -04:00
commit 3326ae44fa
No known key found for this signature in database
58 changed files with 5428 additions and 122 deletions

View File

@ -58,7 +58,7 @@ tools:
# used to release all artifacts
- name: goreleaser
version:
want: v2.12.5
want: v2.12.6
method: github-release
with:
repo: goreleaser/goreleaser
@ -98,7 +98,7 @@ tools:
# used for triggering a release
- name: gh
version:
want: v2.81.0
want: v2.82.1
method: github-release
with:
repo: cli/cli

View File

@ -9,6 +9,9 @@ permit:
- Unlicense
ignore-packages:
# https://github.com/sorairolake/lzip-go/blob/34a2615d2abf740175c6b0a835baa08364e09430/go.sum.license#L3
# has `SPDX-License-Identifier: Apache-2.0 OR MIT`, both of which are acceptable
- github.com/sorairolake/lzip-go
# packageurl-go is released under the MIT license located in the root of the repo at /mit.LICENSE
- github.com/anchore/packageurl-go

View File

@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 #v3.29.5
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 #v3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -58,7 +58,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@f443b600d91635bebf5b0d9ebc620189c0d6fba5 #v3.29.5
uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 #v3.29.5
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -72,4 +72,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 #v3.29.5
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 #v3.29.5

View File

@ -161,7 +161,7 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@f8bdd1d8ac5e901a77a92f111440fdb1b593736b #v0.20.6
- uses: anchore/sbom-action@aa0e114b2e19480f157109b9922bda359bd98b90 #v0.20.8
continue-on-error: true
with:
file: go.mod

View File

@ -210,7 +210,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:

3
.gitignore vendored
View File

@ -2,6 +2,7 @@
go.work
go.work.sum
.tool-versions
.python-version
# app configuration
/.syft.yaml
@ -16,6 +17,8 @@ bin/
/snapshot
/.tool
/.task
/generate
/specs
# changelog generation
CHANGELOG.md

View File

@ -337,6 +337,7 @@ signs:
certificate: "${artifact}.pem"
args:
- "sign-blob"
- "--use-signing-config=false"
- "--oidc-issuer=https://token.actions.githubusercontent.com"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"

24
go.mod
View File

@ -15,7 +15,7 @@ require (
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716
github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c
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
@ -24,7 +24,7 @@ require (
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.10
github.com/anchore/stereoscope v0.1.11
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
@ -40,7 +40,7 @@ require (
github.com/dustin/go-humanize v1.0.1
github.com/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.3.3
github.com/github/go-spdx/v2 v2.3.4
github.com/gkampitakis/go-snaps v0.5.15
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.3
@ -62,7 +62,7 @@ require (
github.com/jinzhu/copier v0.4.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/magiconair/properties v1.8.10
github.com/mholt/archives v0.1.3
github.com/mholt/archives v0.1.5
github.com/moby/sys/mountinfo v0.7.2
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
github.com/olekukonko/tablewriter v1.0.9
@ -110,11 +110,11 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
@ -122,7 +122,7 @@ require (
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
@ -144,9 +144,9 @@ require (
github.com/containerd/typeurl/v2 v2.2.0 // 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.4.0+incompatible // indirect
github.com/docker/cli v28.5.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.4.0+incompatible // indirect
github.com/docker/docker v28.5.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
@ -194,7 +194,7 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@ -210,7 +210,7 @@ require (
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
@ -232,7 +232,7 @@ require (
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.9 // indirect

48
go.sum
View File

@ -94,8 +94,8 @@ 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.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.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/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
@ -116,8 +116,8 @@ github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716 h1:2sIdYJlQESEnyk3Y0W
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716/go.mod h1:Utb9i4kwiCWvqAIxZaJeMIXFO9uOgQXlvH2BfbfO/zI=
github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2 h1:GC2QaO0YsmjpsZ4rtVKv9DnproIxqqn+qkskpc+i8MA=
github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2/go.mod h1:XUbUECwVKuD3qYRUj+QZIOHjyyXua2gFmVjKA40iHXA=
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q=
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c h1:eoJXyC0n7DZ4YvySG/ETdYkTar2Due7eH+UmLK6FbrA=
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9AsdpOfqxbRwtxcNnSZ1zg4aKy2BevO3ZBdc=
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50=
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM=
@ -138,11 +138,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.10 h1:BogafIMaW/L1lOUoVS96Hu1jTSP2JktxIayVqcxvcBI=
github.com/anchore/stereoscope v0.1.10/go.mod h1:RWFAkQE8tp8yyaf4V83Kq1bO6hX3bzi8gpLCcKgZLIk=
github.com/anchore/stereoscope v0.1.11 h1:YP/XUNcJyMbOOPAWPkeZNCVlKKTRO2cnBTEeUW6I40Y=
github.com/anchore/stereoscope v0.1.11/go.mod h1:G3PZlzPbxFhylj9pQwtqfVPaahuWmy/UCtv5FTIIMvg=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
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/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
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=
@ -217,8 +217,8 @@ github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
@ -322,12 +322,12 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY=
github.com/docker/cli v28.5.1+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.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
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.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
@ -385,8 +385,8 @@ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
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/github/go-spdx/v2 v2.3.4 h1:6VNAsYWvQge+SOeoubTlH81MY21d5uekXNIRGfXMNXo=
github.com/github/go-spdx/v2 v2.3.4/go.mod h1:7LYNCshU2Gj17qZ0heJ5CQUKWWmpd98K7o93K8fJSMk=
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=
@ -686,15 +686,15 @@ 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.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458=
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
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=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=
github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -750,8 +750,8 @@ github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1a
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
@ -860,8 +860,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d h1:3VwvTjiRPA7cqtgOWddEL+JrcijMlXUmj99c/6YyZoY=
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d/go.mod h1:tAG61zBM1DYRaGIPloumExGvScf08oHuo0kFoOqdbT0=
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=

View File

@ -52,6 +52,7 @@ func AllTypes() []any {
pkg.PhpPeclEntry{},
pkg.PortageEntry{},
pkg.PythonPackage{},
pkg.PythonPdmLockEntry{},
pkg.PythonPipfileLockEntry{},
pkg.PythonPoetryLockEntry{},
pkg.PythonRequirementsEntry{},

View File

@ -102,6 +102,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.PhpPearEntry{}, "php-pear-entry"),
jsonNames(pkg.PortageEntry{}, "portage-db-entry", "PortageMetadata"),
jsonNames(pkg.PythonPackage{}, "python-package", "PythonPackageMetadata"),
jsonNames(pkg.PythonPdmLockEntry{}, "python-pdm-lock-entry"),
jsonNames(pkg.PythonPipfileLockEntry{}, "python-pipfile-lock-entry", "PythonPipfileLockMetadata"),
jsonNames(pkg.PythonPoetryLockEntry{}, "python-poetry-lock-entry", "PythonPoetryLockMetadata"),
jsonNames(pkg.PythonRequirementsEntry{}, "python-pip-requirements-entry", "PythonRequirementsMetadata"),

File diff suppressed because it is too large Load Diff

View File

@ -2616,6 +2616,9 @@
{
"$ref": "#/$defs/PythonPackage"
},
{
"$ref": "#/$defs/PythonPdmLockEntry"
},
{
"$ref": "#/$defs/PythonPipRequirementsEntry"
},
@ -3198,6 +3201,35 @@
],
"description": "PythonPackage represents all captured data for a python egg or wheel package (specifically as outlined in the PyPA core metadata specification https://packaging.python.org/en/latest/specifications/core-metadata/)."
},
"PythonPdmLockEntry": {
"properties": {
"summary": {
"type": "string",
"description": "Summary provides a description of the package"
},
"files": {
"items": {
"$ref": "#/$defs/PythonFileRecord"
},
"type": "array",
"description": "Files are the package files with their paths and hash digests"
},
"dependencies": {
"items": {
"type": "string"
},
"type": "array",
"description": "Dependencies are the dependency specifications, without environment qualifiers"
}
},
"type": "object",
"required": [
"summary",
"files",
"dependencies"
],
"description": "PythonPdmLockEntry represents a single package entry within a pdm.lock file."
},
"PythonPipRequirementsEntry": {
"properties": {
"name": {

View File

@ -3,6 +3,7 @@ package executable
import (
"debug/macho"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader"
)
@ -19,20 +20,38 @@ const (
func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) error {
// TODO: support security features
// TODO: support multi-architecture binaries
f, err := macho.NewFile(reader)
// a universal binary may have multiple architectures, so we need to check each one
readers, err := unionreader.GetReaders(reader)
if err != nil {
return err
}
libs, err := f.ImportedLibraries()
if err != nil {
return err
var libs []string
for _, r := range readers {
f, err := macho.NewFile(r)
if err != nil {
return err
}
rLibs, err := f.ImportedLibraries()
if err != nil {
return err
}
libs = append(libs, rLibs...)
// TODO handle only some having entrypoints/exports? If that is even practical
// only check for entrypoint if we don't already have one
if !data.HasEntrypoint {
data.HasEntrypoint = machoHasEntrypoint(f)
}
// only check for exports if we don't already have them
if !data.HasExports {
data.HasExports = machoHasExports(f)
}
}
data.ImportedLibraries = libs
data.HasEntrypoint = machoHasEntrypoint(f)
data.HasExports = machoHasExports(f)
// de-duplicate libraries
data.ImportedLibraries = internal.NewSet(libs...).ToSlice()
return nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader"
)
@ -83,3 +84,39 @@ func Test_machoHasExports(t *testing.T) {
})
}
}
func Test_machoUniversal(t *testing.T) {
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
t.Helper()
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
require.NoError(t, err)
return f
}
tests := []struct {
name string
fixture string
want file.Executable
}{
{
name: "universal lib",
fixture: "bin/libhello_universal.dylib",
want: file.Executable{HasExports: true, HasEntrypoint: false},
},
{
name: "universal application",
fixture: "bin/hello_mac_universal",
want: file.Executable{HasExports: false, HasEntrypoint: true},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var data file.Executable
err := findMachoFeatures(&data, readerForFixture(t, tt.fixture))
require.NoError(t, err)
assert.Equal(t, tt.want.HasEntrypoint, data.HasEntrypoint)
assert.Equal(t, tt.want.HasExports, data.HasExports)
})
}
}

View File

@ -2,13 +2,13 @@
BIN=../../bin
all: $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac
all: $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac $(BIN)/hello_mac_universal
linux: $(BIN)/libhello.so
windows: $(BIN)/libhello.dll
mac: $(BIN)/libhello.dylib
mac: $(BIN)/libhello.dylib $(BIN)/hello_mac_universal
$(BIN)/hello_linux:
gcc hello.c -o $(BIN)/hello_linux
@ -19,5 +19,8 @@ $(BIN)/hello.exe:
$(BIN)/hello_mac:
o64-clang hello.c -o $(BIN)/hello_mac
$(BIN)/hello_mac_universal:
o64-clang -arch arm64 -arch x86_64 hello.c -o $(BIN)/hello_mac_universal
clean:
rm -f $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac
rm -f $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac $(BIN)/hello_mac_universal

View File

@ -2,13 +2,13 @@
BIN=../../bin
all: $(BIN)/libhello.so $(BIN)/libhello.dll $(BIN)/libhello.dylib
all: $(BIN)/libhello.so $(BIN)/libhello.dll $(BIN)/libhello.dylib $(BIN)/libhello_universal.dylib
linux: $(BIN)/libhello.so
windows: $(BIN)/libhello.dll
mac: $(BIN)/libhello.dylib
mac: $(BIN)/libhello.dylib $(BIN)/libhello_universal.dylib
$(BIN)/libhello.so:
gcc -shared -fPIC -o $(BIN)/libhello.so hello.c
@ -19,5 +19,8 @@ $(BIN)/libhello.dll:
$(BIN)/libhello.dylib:
o64-clang -dynamiclib -o $(BIN)/libhello.dylib hello.c
$(BIN)/libhello_universal.dylib:
o64-clang -dynamiclib -arch arm64 -arch x86_64 hello.c -o $(BIN)/libhello_universal.dylib
clean:
rm -f $(BIN)/libhello.so $(BIN)/hello.dll $(BIN)/libhello.dylib $(BIN)/libhello.a
rm -f $(BIN)/libhello.so $(BIN)/hello.dll $(BIN)/libhello.dylib $(BIN)/libhello.a $(BIN)/libhello_universal.dylib

View File

@ -42,6 +42,7 @@ func Test_OriginatorSupplier(t *testing.T) {
pkg.PhpPeclEntry{},
pkg.PortageEntry{},
pkg.PythonPipfileLockEntry{},
pkg.PythonPdmLockEntry{},
pkg.PythonRequirementsEntry{},
pkg.PythonPoetryLockEntry{},
pkg.PythonUvLockEntry{},
@ -343,6 +344,25 @@ func Test_OriginatorSupplier(t *testing.T) {
originator: "Person: auth (auth@auth.gov)",
supplier: "Person: auth (auth@auth.gov)",
},
{
name: "from python PDM lock",
input: pkg.Package{
Metadata: pkg.PythonPdmLockEntry{
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651",
},
},
},
Summary: "A test package",
},
},
originator: "",
supplier: "",
},
{
name: "from r -- maintainer > author",
input: pkg.Package{

View File

@ -0,0 +1,37 @@
package syft
import (
"testing"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/syft/source/sourceproviders"
)
func TestGetProviders_DefaultImagePullSource(t *testing.T) {
userInput := ""
cfg := &GetSourceConfig{DefaultImagePullSource: stereoscope.RegistryTag}
allSourceProviders := sourceproviders.All(userInput, cfg.SourceProviderConfig)
providers, err := cfg.getProviders(userInput)
if err != nil {
t.Errorf("Expected no error for DefaultImagePullSource parameter, got: %v", err)
}
if len(providers) != len(allSourceProviders) {
t.Errorf("Expected %d providers, got %d", len(allSourceProviders), len(providers))
}
}
func TestGetProviders_Sources(t *testing.T) {
userInput := ""
cfg := &GetSourceConfig{Sources: []string{stereoscope.RegistryTag}}
providers, err := cfg.getProviders(userInput)
if err != nil {
t.Errorf("Expected no error for Sources parameter, got: %v", err)
}
if len(providers) != 1 {
t.Errorf("Expected 1 providers, got %d", len(providers))
}
}

View File

@ -322,7 +322,7 @@ func (r directoryIndexer) addDirectoryToIndex(p string, info os.FileInfo) error
return err
}
metadata := file.NewMetadataFromPath(p, info)
metadata := NewMetadataFromPath(p, info)
r.index.Add(*ref, metadata)
return nil
@ -334,7 +334,7 @@ func (r directoryIndexer) addFileToIndex(p string, info os.FileInfo) error {
return err
}
metadata := file.NewMetadataFromPath(p, info)
metadata := NewMetadataFromPath(p, info)
r.index.Add(*ref, metadata)
return nil
@ -416,7 +416,7 @@ func (r directoryIndexer) addSymlinkToIndex(p string, info os.FileInfo) (string,
targetAbsPath = filepath.Clean(filepath.Join(path.Dir(p), linkTarget))
}
metadata := file.NewMetadataFromPath(p, info)
metadata := NewMetadataFromPath(p, info)
metadata.LinkDestination = linkTarget
r.index.Add(*ref, metadata)

View File

@ -173,7 +173,7 @@ func (r *fileIndexer) addDirectoryToIndex(path string, info os.FileInfo) error {
return err
}
metadata := file.NewMetadataFromPath(path, info)
metadata := NewMetadataFromPath(path, info)
r.index.Add(*ref, metadata)
return nil
@ -185,7 +185,7 @@ func (r *fileIndexer) addFileToIndex(path string, info os.FileInfo) error {
return err
}
metadata := file.NewMetadataFromPath(path, info)
metadata := NewMetadataFromPath(path, info)
r.index.Add(*ref, metadata)
return nil

View File

@ -0,0 +1,20 @@
//go:build !windows
package fileresolver
import (
"os"
"syscall"
)
// getXid is the UID GID system info for unix
func getXid(info os.FileInfo) (uid, gid int) {
uid = -1
gid = -1
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
uid = int(stat.Uid)
gid = int(stat.Gid)
}
return uid, gid
}

View File

@ -0,0 +1,12 @@
//go:build windows
package fileresolver
import (
"os"
)
// getXid is a placeholder for windows file information
func getXid(info os.FileInfo) (uid, gid int) {
return -1, -1
}

View File

@ -0,0 +1,44 @@
package fileresolver
import (
"os"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/internal/windows"
)
func NewMetadataFromPath(path string, info os.FileInfo) file.Metadata {
var mimeType string
uid, gid := getXid(info)
ty := file.TypeFromMode(info.Mode())
if ty == file.TypeRegular {
usablePath := path
// denormalize the path back to windows so we can open the file
if windows.HostRunningOnWindows() {
usablePath = windows.FromPosix(usablePath)
}
f, err := os.Open(usablePath)
if err != nil {
// TODO: it may be that the file is inaccessible, however, this is not an error or a warning. In the future we need to track these as known-unknowns
f = nil
} else {
defer internal.CloseAndLogError(f, usablePath)
}
mimeType = file.MIMEType(f)
}
return file.Metadata{
FileInfo: info,
Path: path,
Type: ty,
// unsupported across platforms
UserID: uid,
GroupID: gid,
MIMEType: mimeType,
}
}

View File

@ -0,0 +1,50 @@
package fileresolver
import (
"os"
"testing"
"github.com/anchore/stereoscope/pkg/file"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFileMetadataFromPath(t *testing.T) {
tests := []struct {
path string
expectedType file.Type
expectedMIMEType string
}{
{
path: "test-fixtures/symlinks-simple/readme",
expectedType: file.TypeRegular,
expectedMIMEType: "text/plain",
},
{
path: "test-fixtures/symlinks-simple/link_to_new_readme",
expectedType: file.TypeSymLink,
expectedMIMEType: "",
},
{
path: "test-fixtures/symlinks-simple/link_to_link_to_new_readme",
expectedType: file.TypeSymLink,
expectedMIMEType: "",
},
{
path: "test-fixtures/symlinks-simple",
expectedType: file.TypeDirectory,
expectedMIMEType: "",
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
info, err := os.Lstat(test.path)
require.NoError(t, err)
actual := NewMetadataFromPath(test.path, info)
assert.Equal(t, test.expectedMIMEType, actual.MIMEType, "unexpected MIME type for %s", test.path)
assert.Equal(t, test.expectedType, actual.Type, "unexpected type for %s", test.path)
})
}
}

View File

@ -7,7 +7,7 @@ import (
"slices"
"strings"
version "github.com/bitnami/go-version/pkg/version"
"github.com/bitnami/go-version/pkg/version"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/artifact"

View File

@ -2,17 +2,21 @@ package dotnet
type CatalogerConfig struct {
// DepPackagesMustHaveDLL allows for deps.json packages to be included only if there is a DLL on disk for that package.
// app-config: dotnet.dep-packages-must-have-dll
DepPackagesMustHaveDLL bool `mapstructure:"dep-packages-must-have-dll" json:"dep-packages-must-have-dll" yaml:"dep-packages-must-have-dll"`
// DepPackagesMustClaimDLL allows for deps.json packages to be included only if there is a runtime/resource DLL claimed in the deps.json targets section.
// This does not require such claimed DLLs to exist on disk. The behavior of this
// app-config: dotnet.dep-packages-must-claim-dll
DepPackagesMustClaimDLL bool `mapstructure:"dep-packages-must-claim-dll" json:"dep-packages-must-claim-dll" yaml:"dep-packages-must-claim-dll"`
// PropagateDLLClaimsToParents allows for deps.json packages to be included if any child (transitive) package claims a DLL. This applies to both the claims configuration and evidence-on-disk configurations.
// app-config: dotnet.propagate-dll-claims-to-parents
PropagateDLLClaimsToParents bool `mapstructure:"propagate-dll-claims-to-parents" json:"propagate-dll-claims-to-parents" yaml:"propagate-dll-claims-to-parents"`
// RelaxDLLClaimsWhenBundlingDetected will look for indications of IL bundle tooling via deps.json package names
// and, if found (and this config option is enabled), will relax the DepPackagesMustClaimDLL value to `false` only in those cases.
// app-config: dotnet.relax-dll-claims-when-bundling-detected
RelaxDLLClaimsWhenBundlingDetected bool `mapstructure:"relax-dll-claims-when-bundling-detected" json:"relax-dll-claims-when-bundling-detected" yaml:"relax-dll-claims-when-bundling-detected"`
}

View File

@ -19,19 +19,48 @@ var (
)
type CatalogerConfig struct {
SearchLocalModCacheLicenses bool `yaml:"search-local-mod-cache-licenses" json:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
LocalModCacheDir string `yaml:"local-mod-cache-dir" json:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
SearchLocalVendorLicenses bool `yaml:"search-local-vendor-licenses" json:"search-local-vendor-licenses" mapstructure:"search-local-vendor-licenses"`
LocalVendorDir string `yaml:"local-vendor-dir" json:"local-vendor-dir" mapstructure:"local-vendor-dir"`
SearchRemoteLicenses bool `yaml:"search-remote-licenses" json:"search-remote-licenses" mapstructure:"search-remote-licenses"`
Proxies []string `yaml:"proxies,omitempty" json:"proxies,omitempty" mapstructure:"proxies"`
NoProxy []string `yaml:"no-proxy,omitempty" json:"no-proxy,omitempty" mapstructure:"no-proxy"`
MainModuleVersion MainModuleVersionConfig `yaml:"main-module-version" json:"main-module-version" mapstructure:"main-module-version"`
// SearchLocalModCacheLicenses enables searching for go package licenses in the local GOPATH mod cache.
// app-config: golang.search-local-mod-cache-licenses
SearchLocalModCacheLicenses bool `yaml:"search-local-mod-cache-licenses" json:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
// LocalModCacheDir specifies the location of the local go module cache directory. When not set, syft will attempt to discover the GOPATH env or default to $HOME/go.
// app-config: golang.local-mod-cache-dir
LocalModCacheDir string `yaml:"local-mod-cache-dir" json:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
// SearchLocalVendorLicenses enables searching for go package licenses in the local vendor directory relative to the go.mod file.
// app-config: golang.search-local-vendor-licenses
SearchLocalVendorLicenses bool `yaml:"search-local-vendor-licenses" json:"search-local-vendor-licenses" mapstructure:"search-local-vendor-licenses"`
// LocalVendorDir specifies the location of the local vendor directory. When not set, syft will search for a vendor directory relative to the go.mod file.
// app-config: golang.local-vendor-dir
LocalVendorDir string `yaml:"local-vendor-dir" json:"local-vendor-dir" mapstructure:"local-vendor-dir"`
// SearchRemoteLicenses enables downloading go package licenses from the upstream go proxy (typically proxy.golang.org).
// app-config: golang.search-remote-licenses
SearchRemoteLicenses bool `yaml:"search-remote-licenses" json:"search-remote-licenses" mapstructure:"search-remote-licenses"`
// Proxies is a list of go module proxies to use when fetching go module metadata and licenses. When not set, syft will use the GOPROXY env or default to https://proxy.golang.org,direct.
// app-config: golang.proxy
Proxies []string `yaml:"proxies,omitempty" json:"proxies,omitempty" mapstructure:"proxies"`
// NoProxy is a list of glob patterns that match go module names that should not be fetched from the go proxy. When not set, syft will use the GOPRIVATE and GONOPROXY env vars.
// app-config: golang.no-proxy
NoProxy []string `yaml:"no-proxy,omitempty" json:"no-proxy,omitempty" mapstructure:"no-proxy"`
MainModuleVersion MainModuleVersionConfig `yaml:"main-module-version" json:"main-module-version" mapstructure:"main-module-version"`
}
type MainModuleVersionConfig struct {
FromLDFlags bool `yaml:"from-ld-flags" json:"from-ld-flags" mapstructure:"from-ld-flags"`
FromContents bool `yaml:"from-contents" json:"from-contents" mapstructure:"from-contents"`
// FromLDFlags enables parsing the main module version from the -ldflags build settings.
// app-config: golang.main-module-version.from-ld-flags
FromLDFlags bool `yaml:"from-ld-flags" json:"from-ld-flags" mapstructure:"from-ld-flags"`
// FromContents enables parsing the main module version from the binary contents. This is useful when the version is embedded in the binary but not in the build settings.
// app-config: golang.main-module-version.from-contents
FromContents bool `yaml:"from-contents" json:"from-contents" mapstructure:"from-contents"`
// FromBuildSettings enables parsing the main module version from the go build settings.
// app-config: golang.main-module-version.from-build-settings
FromBuildSettings bool `yaml:"from-build-settings" json:"from-build-settings" mapstructure:"from-build-settings"`
}

View File

@ -336,6 +336,9 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
opts = append(opts, p.compareOptions...)
opts = append(opts, cmp.Reporter(&r))
// ignore the "FoundBy" field on relationships as it is set in the generic cataloger before it's presence on the relationship
opts = append(opts, cmpopts.IgnoreFields(pkg.Package{}, "FoundBy"))
// order should not matter
relationship.Sort(p.expectedRelationships)
relationship.Sort(relationships)

View File

@ -263,7 +263,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
}
var pkgPomProject *pkg.JavaPomProject
if parsedPom != nil {
pkgPomProject = newPomProject(ctx, nil, parsedPom.path, parsedPom.project)
pkgPomProject = newPomProject(ctx, j.maven, parsedPom.path, parsedPom.project)
}
return &pkg.Package{

View File

@ -1632,3 +1632,26 @@ func Test_corruptJarArchive(t *testing.T) {
WithError().
TestParser(t, ap.parseJavaArchive)
}
func Test_jarPomPropertyResolutionDoesNotPanic(t *testing.T) {
jarName := generateJavaMetadataJarFixture(t, "commons-lang3-3.12.0", "jar")
fixture, err := os.Open(jarName)
require.NoError(t, err)
ctx := context.TODO()
// setup parser
ap, cleanupFn, err := newJavaArchiveParser(
ctx,
file.LocationReadCloser{
Location: file.NewLocation(fixture.Name()),
ReadCloser: fixture,
}, false, ArchiveCatalogerConfig{
UseMavenLocalRepository: true,
MavenLocalRepositoryDir: "internal/maven/test-fixtures/maven-repo",
})
defer cleanupFn()
require.NoError(t, err)
_, _, err = ap.parse(ctx, nil)
require.NoError(t, err)
}

View File

@ -41,11 +41,14 @@ func NewPomCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
// Note: Older versions of lockfiles aren't supported yet
func NewGradleLockfileCataloger() pkg.Cataloger {
return generic.NewCataloger("java-gradle-lockfile-cataloger").
WithParserByGlobs(parseGradleLockfile, gradleLockfileGlob)
WithParserByGlobs(parseGradleLockfile, "**/gradle.lockfile*")
}
// NewJvmDistributionCataloger returns packages representing JDK/JRE installations (of multiple distribution types).
func NewJvmDistributionCataloger() pkg.Cataloger {
return generic.NewCataloger("java-jvm-cataloger").
WithParserByGlobs(parseJVMRelease, jvmReleaseGlob)
// this is a very permissive glob that will match more than just the JVM release file.
// we started with "**/{java,jvm}/*/release", but this prevents scanning JVM archive contents (e.g. jdk8u402.zip).
// this approach lets us check more files for JVM release info, but be rather silent about errors.
WithParserByGlobs(parseJVMRelease, "**/release")
}

View File

@ -9,12 +9,30 @@ import (
type ArchiveCatalogerConfig struct {
cataloging.ArchiveSearchConfig `yaml:",inline" json:"" mapstructure:",squash"`
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
UseMavenLocalRepository bool `yaml:"use-maven-localrepository" json:"use-maven-localrepository" mapstructure:"use-maven-localrepository"`
MavenLocalRepositoryDir string `yaml:"maven-localrepository-dir" json:"maven-localrepository-dir" mapstructure:"maven-localrepository-dir"`
MavenBaseURL string `yaml:"maven-base-url" json:"maven-base-url" mapstructure:"maven-base-url"`
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
ResolveTransitiveDependencies bool `yaml:"resolve-transitive-dependencies" json:"resolve-transitive-dependencies" mapstructure:"resolve-transitive-dependencies"`
// UseNetwork enables network operations for java package metadata enrichment, such as fetching parent POMs and license information.
// app-config: java.use-network
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
// UseMavenLocalRepository enables searching the local maven repository (~/.m2/repository by default) for parent POMs and other metadata.
// app-config: java.use-maven-local-repository
UseMavenLocalRepository bool `yaml:"use-maven-localrepository" json:"use-maven-localrepository" mapstructure:"use-maven-localrepository"`
// MavenLocalRepositoryDir specifies the location of the local maven repository. When not set, defaults to ~/.m2/repository.
// app-config: java.maven-local-repository-dir
MavenLocalRepositoryDir string `yaml:"maven-localrepository-dir" json:"maven-localrepository-dir" mapstructure:"maven-localrepository-dir"`
// MavenBaseURL specifies the base URL(s) to use for fetching POMs and metadata from maven central or other repositories. When not set, defaults to https://repo1.maven.org/maven2.
// app-config: java.maven-url
MavenBaseURL string `yaml:"maven-base-url" json:"maven-base-url" mapstructure:"maven-base-url"`
// MaxParentRecursiveDepth limits how many parent POMs will be fetched recursively before stopping. This prevents infinite loops or excessively deep parent chains.
// app-config: java.max-parent-recursive-depth
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
// ResolveTransitiveDependencies enables resolving transitive dependencies for java packages found within archives.
// app-config: java.resolve-transitive-dependencies
ResolveTransitiveDependencies bool `yaml:"resolve-transitive-dependencies" json:"resolve-transitive-dependencies" mapstructure:"resolve-transitive-dependencies"`
}
func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {

View File

@ -11,8 +11,6 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const gradleLockfileGlob = "**/gradle.lockfile*"
// lockfileDependency represents a single dependency in the gradle.lockfile file
type lockfileDependency struct {
Group string

View File

@ -22,10 +22,6 @@ import (
)
const (
// this is a very permissive glob that will match more than just the JVM release file.
// we started with "**/{java,jvm}/*/release", but this prevents scanning JVM archive contents (e.g. jdk8u402.zip).
// this approach lets us check more files for JVM release info, but be rather silent about errors.
jvmReleaseGlob = "**/release"
oracleVendor = "oracle"
openJdkProduct = "openjdk"
jre = "jre"

View File

@ -14,7 +14,7 @@ SPRING_INSTRUMENTATION = spring-instrumentation-4.3.0-1.0
MULTIPLE_MATCHING = multiple-matching-2.11.5
ORG_MULTIPLE_THENAME = org.multiple-thename
MICRONAUT_AOP = micronaut-aop-4.9.11
COMMONS_LANG3 = commons-lang3-3.12.0
.DEFAULT_GOAL := fixtures
@ -24,7 +24,7 @@ fixtures: $(CACHE_DIR)
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
fingerprint: $(FINGERPRINT_FILE)
$(CACHE_DIR): $(CACHE_DIR)/$(JACKSON_CORE).jar $(CACHE_DIR)/$(SBT_JACKSON_CORE).jar $(CACHE_DIR)/$(OPENSAML_CORE).jar $(CACHE_DIR)/$(API_ALL_SOURCES).jar $(CACHE_DIR)/$(SPRING_INSTRUMENTATION).jar $(CACHE_DIR)/$(MULTIPLE_MATCHING).jar $(CACHE_DIR)/$(MICRONAUT_AOP).jar
$(CACHE_DIR): $(CACHE_DIR)/$(JACKSON_CORE).jar $(CACHE_DIR)/$(SBT_JACKSON_CORE).jar $(CACHE_DIR)/$(OPENSAML_CORE).jar $(CACHE_DIR)/$(API_ALL_SOURCES).jar $(CACHE_DIR)/$(SPRING_INSTRUMENTATION).jar $(CACHE_DIR)/$(MULTIPLE_MATCHING).jar $(CACHE_DIR)/$(MICRONAUT_AOP).jar $(CACHE_DIR)/$(COMMONS_LANG3).jar
$(CACHE_DIR)/$(JACKSON_CORE).jar:
mkdir -p $(CACHE_DIR)
@ -58,6 +58,10 @@ $(CACHE_DIR)/$(MICRONAUT_AOP).jar:
mkdir -p $(CACHE_DIR)
cd $(MICRONAUT_AOP) && zip -r $(CACHE_PATH)/$(MICRONAUT_AOP).jar .
$(CACHE_DIR)/$(COMMONS_LANG3).jar:
mkdir -p $(CACHE_DIR)
cd $(COMMONS_LANG3) && zip -r $(CACHE_PATH)/$(COMMONS_LANG3).jar .
# Jenkins plugins typically do not have the version included in the archive name,
# so it is important to not include it in the generated test fixture
$(CACHE_DIR)/gradle.hpi:

View File

@ -0,0 +1 @@
Manifest-Version: 1.0

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-parent</artifactId>
<version>54</version>
</parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.release.version}</version>
<packaging>pom</packaging>
<name>JUnit 5 (Bill of Materials)</name>
<licenses>
<license>
<name>Eclipse Public License v2.0</name>
<url>https://www.eclipse.org/legal/epl-v20.html</url>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/junit-team/junit5.git</connection>
<developerConnection>scm:git:git://github.com/junit-team/junit5.git</developerConnection>
<url>https://github.com/junit-team/junit5</url>
</scm>
<dependencies>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${commons.release.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -18,8 +18,9 @@ func NewPackageCataloger() pkg.Cataloger {
func NewLockCataloger(cfg CatalogerConfig) pkg.Cataloger {
yarnLockAdapter := newGenericYarnLockAdapter(cfg)
packageLockAdapter := newGenericPackageLockAdapter(cfg)
pnpmLockAdapter := newGenericPnpmLockAdapter(cfg)
return generic.NewCataloger("javascript-lock-cataloger").
WithParserByGlobs(packageLockAdapter.parsePackageLock, "**/package-lock.json").
WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock").
WithParserByGlobs(parsePnpmLock, "**/pnpm-lock.yaml")
WithParserByGlobs(pnpmLockAdapter.parsePnpmLock, "**/pnpm-lock.yaml")
}

View File

@ -3,9 +3,15 @@ package javascript
const npmBaseURL = "https://registry.npmjs.org"
type CatalogerConfig struct {
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
NPMBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
IncludeDevDependencies bool `json:"include-dev-dependencies" yaml:"include-dev-dependencies" mapstructure:"include-dev-dependencies"`
// SearchRemoteLicenses enables querying the NPM registry API to retrieve license information for packages that are missing license data in their local metadata.
// app-config: javascript.search-remote-licenses
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
// NPMBaseURL specifies the base URL for the NPM registry API used when searching for remote license information.
// app-config: javascript.npm-base-url
NPMBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
// IncludeDevDependencies controls whether development dependencies should be included in the catalog results, in addition to production dependencies.
// app-config: javascript.include-dev-dependencies
IncludeDevDependencies bool `json:"include-dev-dependencies" yaml:"include-dev-dependencies" mapstructure:"include-dev-dependencies"`
}
func DefaultCatalogerConfig() CatalogerConfig {

View File

@ -107,7 +107,7 @@ func newPackageLockV1Package(ctx context.Context, cfg CatalogerConfig, resolver
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
log.Debugf("unable to extract licenses from javascript yarn.lock for package %s:%s: %+v", name, version, err)
log.Debugf("unable to extract licenses from javascript package-lock.json for package %s:%s: %+v", name, version, err)
}
}
@ -140,7 +140,7 @@ func newPackageLockV2Package(ctx context.Context, cfg CatalogerConfig, resolver
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
log.Debugf("unable to extract licenses from javascript yarn.lock for package %s:%s: %+v", name, u.Version, err)
log.Debugf("unable to extract licenses from javascript package-lock.json for package %s:%s: %+v", name, u.Version, err)
}
}
@ -161,7 +161,19 @@ func newPackageLockV2Package(ctx context.Context, cfg CatalogerConfig, resolver
)
}
func newPnpmPackage(ctx context.Context, resolver file.Resolver, location file.Location, name, version string) pkg.Package {
func newPnpmPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string) pkg.Package {
var licenseSet pkg.LicenseSet
if cfg.SearchRemoteLicenses {
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, version)
if err == nil && license != "" {
licenses := pkg.NewLicensesFromValuesWithContext(ctx, license)
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
log.Debugf("unable to extract licenses from javascript pnpm-lock.yaml for package %s:%s: %+v", name, version, err)
}
}
return finalizeLockPkg(
ctx,
resolver,
@ -169,6 +181,7 @@ func newPnpmPackage(ctx context.Context, resolver file.Resolver, location file.L
pkg.Package{
Name: name,
Version: version,
Licenses: licenseSet,
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
PURL: packageURL(name, version),
Language: pkg.JavaScript,

View File

@ -18,9 +18,6 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
// integrity check
var _ generic.Parser = parsePnpmLock
// pnpmPackage holds the raw name and version extracted from the lockfile.
type pnpmPackage struct {
Name string
@ -45,6 +42,16 @@ type pnpmV9LockYaml struct {
Packages map[string]interface{} `yaml:"packages"`
}
type genericPnpmLockAdapter struct {
cfg CatalogerConfig
}
func newGenericPnpmLockAdapter(cfg CatalogerConfig) genericPnpmLockAdapter {
return genericPnpmLockAdapter{
cfg: cfg,
}
}
// Parse implements the pnpmLockfileParser interface for v6-v8 lockfiles.
func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, error) {
if err := yaml.Unmarshal(data, p); err != nil {
@ -116,7 +123,7 @@ func newPnpmLockfileParser(version float64) pnpmLockfileParser {
}
// parsePnpmLock is the main parser function for pnpm-lock.yaml files.
func parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err)
@ -142,7 +149,7 @@ func parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Envir
packages := make([]pkg.Package, len(pnpmPkgs))
for i, p := range pnpmPkgs {
packages[i] = newPnpmPackage(ctx, resolver, reader.Location, p.Name, p.Version)
packages[i] = newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version)
}
return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")

View File

@ -1,6 +1,11 @@
package javascript
import (
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -50,7 +55,8 @@ func TestParsePnpmLock(t *testing.T) {
},
}
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
}
func TestParsePnpmV6Lock(t *testing.T) {
@ -142,7 +148,8 @@ func TestParsePnpmV6Lock(t *testing.T) {
},
}
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
}
func TestParsePnpmLockV9(t *testing.T) {
@ -184,14 +191,101 @@ func TestParsePnpmLockV9(t *testing.T) {
Type: pkg.NpmPkg,
},
}
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
// TODO: no relationships are under test
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expected, expectedRelationships)
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expected, expectedRelationships)
}
func TestSearchPnpmForLicenses(t *testing.T) {
ctx := context.TODO()
fixture := "test-fixtures/pnpm-remote/pnpm-lock.yaml"
locations := file.NewLocationSet(file.NewLocation(fixture))
mux, url, teardown := setupNpmRegistry()
defer teardown()
tests := []struct {
name string
fixture string
config CatalogerConfig
requestHandlers []handlerPath
expectedPackages []pkg.Package
}{
{
name: "search remote licenses returns the expected licenses when search is set to true",
config: CatalogerConfig{SearchRemoteLicenses: true},
requestHandlers: []handlerPath{
{
// https://registry.npmjs.org/nanoid/3.3.4
path: "/nanoid/3.3.4",
handler: generateMockNpmRegistryHandler("test-fixtures/pnpm-remote/registry_response.json"),
},
},
expectedPackages: []pkg.Package{
{
Name: "nanoid",
Version: "3.3.4",
Locations: locations,
PURL: "pkg:npm/nanoid@3.3.4",
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")),
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// set up the mock server
for _, handler := range tc.requestHandlers {
mux.HandleFunc(handler.path, handler.handler)
}
tc.config.NPMBaseURL = url
adapter := newGenericPnpmLockAdapter(tc.config)
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, tc.expectedPackages, nil)
})
}
}
func Test_corruptPnpmLock(t *testing.T) {
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/pnpm-lock.yaml").
WithError().
TestParser(t, parsePnpmLock)
TestParser(t, adapter.parsePnpmLock)
}
func generateMockNpmRegistryHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
// Copy the file's content to the response writer
file, err := os.Open(responseFixture)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
// setup sets up a test HTTP server for mocking requests to a particular registry.
// The returned url is injected into the Config so the client uses the test server.
// Tests should register handlers on mux to simulate the expected request/response structure
func setupNpmRegistry() (mux *http.ServeMux, serverURL string, teardown func()) {
// mux is the HTTP request multiplexer used with the test server.
mux = http.NewServeMux()
// We want to ensure that tests catch mistakes where the endpoint URL is
// specified as absolute rather than relative. It only makes a difference
// when there's a non-empty base URL path. So, use that. See issue #752.
apiHandler := http.NewServeMux()
apiHandler.Handle("/", mux)
// server is a test HTTP server used to provide mock API responses.
server := httptest.NewServer(apiHandler)
return mux, server.URL, server.Close
}

View File

@ -239,7 +239,7 @@ func TestSearchYarnForLicenses(t *testing.T) {
ctx := context.TODO()
fixture := "test-fixtures/yarn-remote/yarn.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
mux, url, teardown := setup()
mux, url, teardown := setupYarnRegistry()
defer teardown()
tests := []struct {
name string
@ -255,7 +255,7 @@ func TestSearchYarnForLicenses(t *testing.T) {
{
// https://registry.yarnpkg.com/@babel/code-frame/7.10.4
path: "/@babel/code-frame/7.10.4",
handler: generateMockNPMHandler("test-fixtures/yarn-remote/registry_response.json"),
handler: generateMockYarnRegistryHandler("test-fixtures/yarn-remote/registry_response.json"),
},
},
expectedPackages: []pkg.Package{
@ -445,7 +445,7 @@ func TestParseYarnFindPackageVersions(t *testing.T) {
}
}
func generateMockNPMHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
func generateMockYarnRegistryHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
// Copy the file's content to the response writer
@ -464,10 +464,10 @@ func generateMockNPMHandler(responseFixture string) func(w http.ResponseWriter,
}
}
// setup sets up a test HTTP server for mocking requests to maven central.
// setup sets up a test HTTP server for mocking requests to a particular registry.
// The returned url is injected into the Config so the client uses the test server.
// Tests should register handlers on mux to simulate the expected request/response structure
func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
func setupYarnRegistry() (mux *http.ServeMux, serverURL string, teardown func()) {
// mux is the HTTP request multiplexer used with the test server.
mux = http.NewServeMux()

View File

@ -0,0 +1,11 @@
lockfileVersion: 5.4
specifiers:
nanoid: ^3.3.4
dependencies:
nanoid: 3.3.4
packages:
/nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}

View File

@ -0,0 +1,106 @@
{
"name": "nanoid",
"version": "3.3.4",
"keywords": [
"uuid",
"random",
"id",
"url"
],
"author": {
"name": "Andrey Sitnik",
"email": "andrey@sitnik.ru"
},
"license": "MIT",
"_id": "nanoid@3.3.4",
"maintainers": [
{
"name": "ai",
"email": "andrey@sitnik.ru"
}
],
"homepage": "https://github.com/ai/nanoid#readme",
"bugs": {
"url": "https://github.com/ai/nanoid/issues"
},
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"dist": {
"shasum": "730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab",
"tarball": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"fileCount": 24,
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"signatures": [
{
"sig": "MEQCIEXG2ta5bIaT6snvQFKV+m1KjuF4DaCpp186tcPo8vsRAiB2Eg9/6nKRi4lZOfwQC1fgq4EzrFjU8T+uqwGxWEQE8A==",
"keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"
}
],
"unpackedSize": 21583,
"npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJicQqNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp6rw/+IRvv2zOtwi8goF3h1VctIQVWtTtYrobDIVC2W++jyxdbgZoP\r\n2CDj1YWjrr+eM6O6sI1Bj+bF+yoqQ+z8ojtfW3vtRPpjzUf/7Sgs4F2ANshp\r\ne3rqdaQLjpHPriHf6HmPJy3YNJ+7n5TPPGoTEGXAe4eCZdko3XidCMWZdHlf\r\nYQU9CVYiG6mjjORkWw1sYctt8exdcGFMh0QoQq7BEp04QWm04JwvHjUiAgvf\r\nmEQLrNrf9nwzjpnubAJD+1z6fKOc9vUE44MOj2PkPoOr6a+iBBBgwBf45cnj\r\ng8R2G5xzxsRRB0a8XZdp67y3WA8rIaYaUuBFtEWYp7QFoA/tp6AGmHEAhjLa\r\nQKTquG7ejBu21ZsQaxpGc/3WWLEm+7F78GF8CXpQdtg0Kg1eugRotSNnU0SO\r\nPLiyYV4Mw6kXnbVchS5Y+HmcDVEcSBMTve/f1KpmIhJueJ20RCg4MGYZWgI9\r\nNJ1KgH2h4djX4XuoXpcsKnX3oVfinHEMke8sLWXHsMAtOxDipEWgW9cE9hk0\r\n71Y6LAAPBu34pmaj73B0qZiIY7wXxoGWQOCl2STS/VyDG/K9w1T+WiYROu+8\r\nE9Gd+f4qXmdi7Jw6May86DDfauCwBP3gnrB5aeOktCjWsgrrdClN3Hv2pIAN\r\noJcjS3IURf6oeV4+Yw1B5GoJu1Y/6U75fOU=\r\n=IMnM\r\n-----END PGP SIGNATURE-----\r\n"
},
"main": "index.cjs",
"type": "module",
"types": "./index.d.ts",
"module": "index.js",
"browser": {
"./index.js": "./index.browser.js",
"./index.cjs": "./index.browser.cjs",
"./async/index.js": "./async/index.browser.js",
"./async/index.cjs": "./async/index.browser.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
},
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js",
"browser": "./index.browser.js",
"default": "./index.js",
"require": "./index.cjs"
},
"./async": {
"import": "./async/index.js",
"browser": "./async/index.browser.js",
"default": "./async/index.js",
"require": "./async/index.cjs"
},
"./index.d.ts": "./index.d.ts",
"./non-secure": {
"import": "./non-secure/index.js",
"default": "./non-secure/index.js",
"require": "./non-secure/index.cjs"
},
"./package.json": "./package.json",
"./url-alphabet": {
"import": "./url-alphabet/index.js",
"default": "./url-alphabet/index.js",
"require": "./url-alphabet/index.cjs"
},
"./async/package.json": "./async/package.json",
"./non-secure/package.json": "./non-secure/package.json",
"./url-alphabet/package.json": "./url-alphabet/package.json"
},
"gitHead": "fc5bd0dbba830b1e6f3e572da8e2bc9ddc1b4b44",
"_npmUser": {
"name": "ai",
"email": "andrey@sitnik.ru"
},
"repository": {
"url": "git+https://github.com/ai/nanoid.git",
"type": "git"
},
"_npmVersion": "8.6.0",
"description": "A tiny (116 bytes), secure URL-friendly unique string ID generator",
"directories": {},
"sideEffects": false,
"_nodeVersion": "18.0.0",
"react-native": "index.js",
"_hasShrinkwrap": false,
"_npmOperationalInternal": {
"tmp": "tmp/nanoid_3.3.4_1651575437375_0.2288595018362154",
"host": "s3://npm-registry-packages"
}
}

View File

@ -17,6 +17,8 @@ import (
var _ pkg.Cataloger = (*linuxKernelCataloger)(nil)
type LinuxKernelCatalogerConfig struct {
// CatalogModules enables cataloging linux kernel modules (*.ko files) in addition to the kernel itself.
// app-config: linux-kernel.catalog-modules
CatalogModules bool `yaml:"catalog-modules" json:"catalog-modules" mapstructure:"catalog-modules"`
}

View File

@ -10,6 +10,8 @@ import (
)
type Config struct {
// CaptureOwnedFiles determines whether to record the list of files owned by each Nix package discovered in the store. Recording owned files provides more detailed information but increases processing time and memory usage.
// app-config: nix.capture-owned-files
CaptureOwnedFiles bool `json:"capture-owned-files" yaml:"capture-owned-files" mapstructure:"capture-owned-files"`
}

View File

@ -11,6 +11,8 @@ import (
const eggInfoGlob = "**/*.egg-info"
type CatalogerConfig struct {
// GuessUnpinnedRequirements attempts to infer package versions from version constraints when no explicit version is specified in requirements files.
// app-config: python.guess-unpinned-requirements
GuessUnpinnedRequirements bool `yaml:"guess-unpinned-requirements" json:"guess-unpinned-requirements" mapstructure:"guess-unpinned-requirements"`
}
@ -28,7 +30,8 @@ func NewPackageCataloger(cfg CatalogerConfig) pkg.Cataloger {
WithParserByGlobs(parsePoetryLock, "**/poetry.lock").
WithParserByGlobs(parsePipfileLock, "**/Pipfile.lock").
WithParserByGlobs(parseSetup, "**/setup.py").
WithParserByGlobs(parseUvLock, "**/uv.lock")
WithParserByGlobs(parseUvLock, "**/uv.lock").
WithParserByGlobs(parsePdmLock, "**/pdm.lock")
}
// NewInstalledPackageCataloger returns a new cataloger for python packages within egg or wheel installation directories.

View File

@ -454,6 +454,7 @@ func Test_IndexCataloger_Globs(t *testing.T) {
"src/poetry.lock",
"src/Pipfile.lock",
"src/uv.lock",
"src/pdm.lock",
},
},
}

View File

@ -0,0 +1,140 @@
package python
import (
"context"
"fmt"
"strings"
"github.com/BurntSushi/toml"
"github.com/scylladb/go-set/strset"
"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"
)
type pdmLock struct {
Metadata struct {
Groups []string `toml:"groups"`
Strategy []string `toml:"strategy"`
LockVersion string `toml:"lock_version"`
ContentHash string `toml:"content_hash"`
} `toml:"metadata"`
Package []pdmLockPackage `toml:"package"`
}
type pdmLockPackage struct {
Name string `toml:"name"`
Version string `toml:"version"`
RequiresPython string `toml:"requires_python"`
Summary string `toml:"summary"`
Dependencies []string `toml:"dependencies"`
Files []pdmLockPackageFile `toml:"files"`
}
type pdmLockPackageFile struct {
File string `toml:"file"`
Hash string `toml:"hash"`
}
var _ generic.Parser = parsePdmLock
// parsePdmLock is a parser function for pdm.lock contents, returning python packages discovered.
func parsePdmLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var lock pdmLock
_, err := toml.NewDecoder(reader).Decode(&lock)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse pdm.lock file: %w", err)
}
var pkgs []pkg.Package
for _, p := range lock.Package {
var files []pkg.PythonFileRecord
for _, f := range p.Files {
if colonIndex := strings.Index(f.Hash, ":"); colonIndex != -1 {
algorithm := f.Hash[:colonIndex]
value := f.Hash[colonIndex+1:]
files = append(files, pkg.PythonFileRecord{
Path: f.File,
Digest: &pkg.PythonFileDigest{
Algorithm: algorithm,
Value: value,
},
})
}
}
// only store used part of the dependency information
var deps []string
for _, dep := range p.Dependencies {
// remove environment markers (after semicolon)
dep = strings.Split(dep, ";")[0]
dep = strings.TrimSpace(dep)
if dep != "" {
deps = append(deps, dep)
}
}
pythonPkgMetadata := pkg.PythonPdmLockEntry{
Files: files,
Summary: p.Summary,
Dependencies: deps,
}
pkgs = append(pkgs, newPackageForIndexWithMetadata(
p.Name,
p.Version,
pythonPkgMetadata,
reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
))
}
relationships := buildPdmRelationships(pkgs)
return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
}
func buildPdmRelationships(pkgs []pkg.Package) []artifact.Relationship {
pkgMap := make(map[string]pkg.Package, len(pkgs))
for _, p := range pkgs {
pkgMap[p.Name] = p
}
var relationships []artifact.Relationship
for _, p := range pkgs {
meta, ok := p.Metadata.(pkg.PythonPdmLockEntry)
if !ok {
continue
}
// collect unique dependencies
added := strset.New()
for _, depName := range meta.Dependencies {
// Handle version specifiers
depName = strings.Split(depName, "<")[0]
depName = strings.Split(depName, ">")[0]
depName = strings.Split(depName, "=")[0]
depName = strings.Split(depName, "~")[0]
depName = strings.TrimSpace(depName)
if depName == "" || added.Has(depName) {
continue
}
added.Add(depName)
if dep, exists := pkgMap[depName]; exists {
relationships = append(relationships, artifact.Relationship{
From: dep,
To: p,
Type: artifact.DependencyOfRelationship,
})
}
}
}
return relationships
}

View File

@ -0,0 +1,363 @@
package python
import (
"testing"
"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/pkgtest"
)
func TestParsePdmLock(t *testing.T) {
fixture := "test-fixtures/pdm-lock/pdm.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
expectedPkgs := []pkg.Package{
{
Name: "certifi",
Version: "2025.1.31",
PURL: "pkg:pypi/certifi@2025.1.31",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "Python package for providing Mozilla's CA Bundle.",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe",
},
},
},
},
},
{
Name: "chardet",
Version: "3.0.4",
PURL: "pkg:pypi/chardet@3.0.4",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "Universal encoding detector for Python 2 and 3",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
},
},
},
},
},
{
Name: "charset-normalizer",
Version: "2.0.12",
PURL: "pkg:pypi/charset-normalizer@2.0.12",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet.",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
},
},
},
},
},
{
Name: "colorama",
Version: "0.3.9",
PURL: "pkg:pypi/colorama@0.3.9",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "Cross-platform colored terminal text.",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1",
},
},
},
},
},
{
Name: "idna",
Version: "2.7",
PURL: "pkg:pypi/idna@2.7",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "Internationalized Domain Names in Applications (IDNA)",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16",
},
},
},
},
},
{
Name: "py",
Version: "1.4.34",
PURL: "pkg:pypi/py@1.4.34",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "library with cross-python path, ini-parsing, io, code, log facilities",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3",
},
},
},
},
},
{
Name: "pytest",
Version: "3.2.5",
PURL: "pkg:pypi/pytest@3.2.5",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "pytest: simple powerful testing with Python",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "6d5bd4f7113b444c55a3bbb5c738a3dd80d43563d063fc42dcb0aaefbdd78b81",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "241d7e7798d79192a123ceaf64c602b4d233eacf6d6e42ae27caa97f498b7dc6",
},
},
},
Dependencies: []string{
"argparse",
"colorama",
"ordereddict",
"py>=1.4.33",
"setuptools",
},
},
},
{
Name: "requests",
Version: "2.27.1",
PURL: "pkg:pypi/requests@2.27.1",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "Python HTTP for Humans.",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
},
},
},
Dependencies: []string{
"certifi>=2017.4.17",
"chardet<5,>=3.0.2",
"charset-normalizer~=2.0.0",
"idna<3,>=2.5",
"idna<4,>=2.5",
"urllib3<1.27,>=1.21.1",
},
},
},
{
Name: "setuptools",
Version: "39.2.0",
PURL: "pkg:pypi/setuptools@39.2.0",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "Easily download, build, install, upgrade, and uninstall Python packages",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926",
},
},
},
},
},
{
Name: "urllib3",
Version: "1.26.20",
PURL: "pkg:pypi/urllib3@1.26.20",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPdmLockEntry{
Summary: "HTTP library with thread-safe connection pooling, file post, and more.",
Files: []pkg.PythonFileRecord{
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e",
},
},
{
Path: "",
Digest: &pkg.PythonFileDigest{
Algorithm: "sha256",
Value: "40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32",
},
},
},
},
},
}
// Create a map for easy lookup of packages by name
pkgMap := make(map[string]pkg.Package)
for _, p := range expectedPkgs {
pkgMap[p.Name] = p
}
expectedRelationships := []artifact.Relationship{
// pytest dependencies
{
From: pkgMap["colorama"],
To: pkgMap["pytest"],
Type: artifact.DependencyOfRelationship,
},
{
From: pkgMap["py"],
To: pkgMap["pytest"],
Type: artifact.DependencyOfRelationship,
},
{
From: pkgMap["setuptools"],
To: pkgMap["pytest"],
Type: artifact.DependencyOfRelationship,
},
// requests dependencies
{
From: pkgMap["certifi"],
To: pkgMap["requests"],
Type: artifact.DependencyOfRelationship,
},
{
From: pkgMap["chardet"],
To: pkgMap["requests"],
Type: artifact.DependencyOfRelationship,
},
{
From: pkgMap["charset-normalizer"],
To: pkgMap["requests"],
Type: artifact.DependencyOfRelationship,
},
{
From: pkgMap["urllib3"],
To: pkgMap["requests"],
Type: artifact.DependencyOfRelationship,
},
{
From: pkgMap["idna"],
To: pkgMap["requests"],
Type: artifact.DependencyOfRelationship,
},
}
pkgtest.TestFileParser(t, fixture, parsePdmLock, expectedPkgs, expectedRelationships)
}
func Test_corruptPdmLock(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/glob-paths/src/pdm.lock").
WithError().
TestParser(t, parsePdmLock)
}

View File

@ -0,0 +1 @@
bogus

View File

@ -0,0 +1,137 @@
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
groups = ["default", "security", "tests"]
strategy = ["inherit_metadata", "static_urls"]
lock_version = "4.5.0"
content_hash = "sha256:2584886ac58a0ae70aa36bc0318b62c3e2c89acc9c21ebb9aee74147c0a9dc06"
[[metadata.targets]]
requires_python = ">=3.3"
[[package]]
name = "certifi"
version = "2025.1.31"
requires_python = ">=3.6"
summary = "Python package for providing Mozilla's CA Bundle."
groups = ["security"]
marker = "python_version >= \"3.6\""
files = [
{url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
{url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
]
[[package]]
name = "chardet"
version = "3.0.4"
summary = "Universal encoding detector for Python 2 and 3"
groups = ["default"]
marker = "os_name == \"nt\""
files = [
{url = "https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{url = "https://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
[[package]]
name = "charset-normalizer"
version = "2.0.12"
requires_python = ">=3.5.0"
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
groups = ["security"]
marker = "python_version >= \"3.6\""
files = [
{url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
{url = "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
]
[[package]]
name = "colorama"
version = "0.3.9"
summary = "Cross-platform colored terminal text."
groups = ["tests"]
marker = "sys_platform == \"win32\""
files = [
{url = "https://files.pythonhosted.org/packages/db/c8/7dcf9dbcb22429512708fe3a547f8b6101c0d02137acbd892505aee57adf/colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"},
{url = "https://files.pythonhosted.org/packages/e6/76/257b53926889e2835355d74fec73d82662100135293e17d382e2b74d1669/colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"},
]
[[package]]
name = "idna"
version = "2.7"
summary = "Internationalized Domain Names in Applications (IDNA)"
groups = ["default", "security"]
files = [
{url = "https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl", hash = "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e"},
{url = "https://files.pythonhosted.org/packages/65/c4/80f97e9c9628f3cac9b98bfca0402ede54e0563b56482e3e6e45c43c4935/idna-2.7.tar.gz", hash = "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"},
]
[[package]]
name = "py"
version = "1.4.34"
summary = "library with cross-python path, ini-parsing, io, code, log facilities"
groups = ["tests"]
files = [
{url = "https://files.pythonhosted.org/packages/53/67/9620edf7803ab867b175e4fd23c7b8bd8eba11cb761514dcd2e726ef07da/py-1.4.34-py2.py3-none-any.whl", hash = "sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a"},
{url = "https://files.pythonhosted.org/packages/68/35/58572278f1c097b403879c1e9369069633d1cbad5239b9057944bb764782/py-1.4.34.tar.gz", hash = "sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3"},
]
[[package]]
name = "pytest"
version = "3.2.5"
summary = "pytest: simple powerful testing with Python"
groups = ["tests"]
dependencies = [
"argparse; python_version == \"2.6\"",
"colorama; sys_platform == \"win32\"",
"ordereddict; python_version == \"2.6\"",
"py>=1.4.33",
"setuptools",
]
files = [
{url = "https://files.pythonhosted.org/packages/1f/f8/8cd74c16952163ce0db0bd95fdd8810cbf093c08be00e6e665ebf0dc3138/pytest-3.2.5.tar.gz", hash = "sha256:6d5bd4f7113b444c55a3bbb5c738a3dd80d43563d063fc42dcb0aaefbdd78b81"},
{url = "https://files.pythonhosted.org/packages/ef/41/d8a61f1b2ba308e96b36106e95024977e30129355fd12087f23e4b9852a1/pytest-3.2.5-py2.py3-none-any.whl", hash = "sha256:241d7e7798d79192a123ceaf64c602b4d233eacf6d6e42ae27caa97f498b7dc6"},
]
[[package]]
name = "requests"
version = "2.27.1"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
summary = "Python HTTP for Humans."
groups = ["security"]
marker = "python_version >= \"3.6\""
dependencies = [
"certifi>=2017.4.17",
"chardet<5,>=3.0.2; python_version < \"3\"",
"charset-normalizer~=2.0.0; python_version >= \"3\"",
"idna<3,>=2.5; python_version < \"3\"",
"idna<4,>=2.5; python_version >= \"3\"",
"urllib3<1.27,>=1.21.1",
]
files = [
{url = "https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{url = "https://files.pythonhosted.org/packages/60/f3/26ff3767f099b73e0efa138a9998da67890793bfa475d8278f84a30fec77/requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
[[package]]
name = "setuptools"
version = "39.2.0"
requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
groups = ["tests"]
files = [
{url = "https://files.pythonhosted.org/packages/1a/04/d6f1159feaccdfc508517dba1929eb93a2854de729fa68da9d5c6b48fa00/setuptools-39.2.0.zip", hash = "sha256:f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2"},
{url = "https://files.pythonhosted.org/packages/7f/e1/820d941153923aac1d49d7fc37e17b6e73bfbd2904959fffbad77900cf92/setuptools-39.2.0-py2.py3-none-any.whl", hash = "sha256:8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926"},
]
[[package]]
name = "urllib3"
version = "1.26.20"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
summary = "HTTP library with thread-safe connection pooling, file post, and more."
groups = ["security"]
marker = "python_version >= \"3.6\""
files = [
{url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"},
{url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"},
]

View File

@ -9,8 +9,6 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const cargoAuditBinaryCatalogerName = "cargo-auditable-binary-cataloger"
// NewCargoLockCataloger returns a new Rust Cargo lock file cataloger object.
func NewCargoLockCataloger() pkg.Cataloger {
return generic.NewCataloger("rust-cargo-lock-cataloger").
@ -20,6 +18,6 @@ func NewCargoLockCataloger() pkg.Cataloger {
// NewAuditBinaryCataloger returns a new Rust auditable binary cataloger object that can detect dependencies
// in binaries produced with https://github.com/Shnatsel/rust-audit
func NewAuditBinaryCataloger() pkg.Cataloger {
return generic.NewCataloger(cargoAuditBinaryCatalogerName).
return generic.NewCataloger("cargo-auditable-binary-cataloger").
WithParserByMimeTypes(parseAuditBinary, mimetype.ExecutableMIMETypeSet.List()...)
}

View File

@ -33,7 +33,6 @@ func newPackageFromAudit(dep *rustaudit.Package, locations ...file.Location) pkg
Language: pkg.Rust,
Type: pkg.RustPkg,
Locations: file.NewLocationSet(locations...),
FoundBy: cargoAuditBinaryCatalogerName,
Metadata: pkg.RustBinaryAuditEntry{
Name: dep.Name,
Version: dep.Version,

View File

@ -79,6 +79,16 @@ func (m PythonPackage) OwnedFiles() (result []string) {
return result
}
// PythonPdmLockEntry represents a single package entry within a pdm.lock file.
type PythonPdmLockEntry struct {
// Summary provides a description of the package
Summary string `mapstructure:"summary" json:"summary" toml:"summary"`
// Files are the package files with their paths and hash digests
Files []PythonFileRecord `mapstructure:"files" json:"files" toml:"files"`
// Dependencies are the dependency specifications, without environment qualifiers
Dependencies []string `mapstructure:"dependencies" json:"dependencies" toml:"dependencies"`
}
// PythonPipfileLockEntry represents a single package entry within a Pipfile.lock file.
type PythonPipfileLockEntry struct {
// Hashes are the package file hash values in the format "algorithm:digest" for integrity verification.

View File

@ -231,11 +231,14 @@ func fileAnalysisPath(path string, skipExtractArchive bool) (string, func() erro
// unarchived.
envelopedUnarchiver, err := archiver.ByExtension(path)
if unarchiver, ok := envelopedUnarchiver.(archiver.Unarchiver); err == nil && ok {
if tar, ok := unarchiver.(*archiver.Tar); ok {
// when tar files are extracted, if there are multiple entries at the same
// location, the last entry wins
// NOTE: this currently does not display any messages if an overwrite happens
tar.OverwriteExisting = true
// when tar/zip files are extracted, if there are multiple entries at the same
// location, the last entry wins
// NOTE: this currently does not display any messages if an overwrite happens
switch v := unarchiver.(type) {
case *archiver.Tar:
v.OverwriteExisting = true
case *archiver.Zip:
v.OverwriteExisting = true
}
analysisPath, cleanupFn, err = unarchiveToTmp(path, unarchiver)