mirror of
https://github.com/anchore/syft.git
synced 2026-10-15 15:47:23 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be463911c | ||
|
|
44b7b0947c | ||
|
|
675075e882 | ||
|
|
31b2c4c090 | ||
|
|
07029ead8a | ||
|
|
f4de1e863c | ||
|
|
538b4a2194 | ||
|
|
fc74b07369 | ||
|
|
6627c5214c | ||
|
|
c0f32e1dba | ||
|
|
e923db2a94 |
+2
-2
@@ -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.82.0
|
||||
want: v2.82.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
go.work
|
||||
go.work.sum
|
||||
.tool-versions
|
||||
.python-version
|
||||
|
||||
# app configuration
|
||||
/.syft.yaml
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
@@ -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=
|
||||
|
||||
@@ -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.40"
|
||||
JSONSchemaVersion = "16.0.41"
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ func AllTypes() []any {
|
||||
pkg.PhpPeclEntry{},
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPackage{},
|
||||
pkg.PythonPdmLockEntry{},
|
||||
pkg.PythonPipfileLockEntry{},
|
||||
pkg.PythonPoetryLockEntry{},
|
||||
pkg.PythonRequirementsEntry{},
|
||||
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.40/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.41/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -2549,6 +2549,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/PythonPackage"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/PythonPdmLockEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/PythonPipRequirementsEntry"
|
||||
},
|
||||
@@ -3131,6 +3134,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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,6 +42,7 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
pkg.PhpPeclEntry{},
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPipfileLockEntry{},
|
||||
pkg.PythonPdmLockEntry{},
|
||||
pkg.PythonRequirementsEntry{},
|
||||
pkg.PythonPoetryLockEntry{},
|
||||
pkg.PythonUvLockEntry{},
|
||||
@@ -342,6 +343,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{
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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==}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -30,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.
|
||||
|
||||
@@ -454,6 +454,7 @@ func Test_IndexCataloger_Globs(t *testing.T) {
|
||||
"src/poetry.lock",
|
||||
"src/Pipfile.lock",
|
||||
"src/uv.lock",
|
||||
"src/pdm.lock",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
bogus
|
||||
@@ -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"},
|
||||
]
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user