Skip to content

Commit

Permalink
scan and report dependency groups of vulnerabilities (#655)
Browse files Browse the repository at this point in the history
Issue #332

Non-default dependency groups are recorded in strings as per eco-system:
 - **Composer:** development dependencies in `packages-dev`
 - **Conan:** dependencies in `build-requires` and `python-requires`
 - **Maven:** `<scope/>` in `<dependency/>`
 - **npm:** `dev` and `optional` dependencies
 - **pipenv:** development dependencies in `develop`
 - **pnpm:** development dependencies with `dev` as true
 - **Poetry:** optional dependencies with `optional = true`
 - **Pubspec:** development dependencies marked with `dev`
- **requirements.txt:** group of a dependency is the file name without
the extension

Reporters:
- **table:** non-default groups are appended to the end of package name,
for example: `abc (development)`
 - **json:** non-default group information in `dependencyGroups`

---------

Co-authored-by: josieang <[email protected]>
Co-authored-by: Gareth Jones <[email protected]>
Co-authored-by: Mend Renovate <[email protected]>
  • Loading branch information
4 people authored Dec 11, 2023
1 parent cfe3604 commit 64f5c2d
Show file tree
Hide file tree
Showing 31 changed files with 441 additions and 31 deletions.
7 changes: 6 additions & 1 deletion internal/output/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"golang.org/x/exp/maps"

"github.com/google/osv-scanner/internal/utility/results"
"github.com/google/osv-scanner/pkg/lockfile"
"github.com/google/osv-scanner/pkg/models"

"github.com/jedib0t/go-pretty/v6/table"
Expand Down Expand Up @@ -130,7 +131,11 @@ func tableBuilderInner(vulnResult *models.VulnerabilityResults, addStyling bool,
outputRow = append(outputRow, "GIT", pkgCommitStr, pkgCommitStr)
shouldMerge = true
} else {
outputRow = append(outputRow, pkg.Package.Ecosystem, pkg.Package.Name, pkg.Package.Version)
name := pkg.Package.Name
if lockfile.Ecosystem(pkg.Package.Ecosystem).IsDevGroup(pkg.DepGroups) {
name += " (dev)"
}
outputRow = append(outputRow, pkg.Package.Ecosystem, name, pkg.Package.Version)
}

outputRow = append(outputRow, source.Path)
Expand Down
4 changes: 2 additions & 2 deletions pkg/lockfile/dpkg-status.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ func (e DpkgStatusExtractor) Extract(f DepFile) ([]PackageDetails, error) {
pkg := parseDpkgPackageGroup(group)

// PackageDetails does not contain any field that represent a "not installed" state
// To manage this state and avoid false positives, empty struct means "not installed" so skip it
if (PackageDetails{}) == pkg {
// To manage this state and avoid false positives, empty ecosystem means "not installed" so skip it
if pkg.Ecosystem == "" {
continue
}

Expand Down
10 changes: 10 additions & 0 deletions pkg/lockfile/fixtures/maven/with-scope.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<project>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
22 changes: 22 additions & 0 deletions pkg/lockfile/fixtures/npm/optional-package.v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true,
"optional": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"optional": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
}
30 changes: 30 additions & 0 deletions pkg/lockfile/fixtures/npm/optional-package.v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "my-library",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {},
"devDependencies": { "wrappy": "^1.0.0" }
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"devOptional": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
}
},
"dependencies": {}
}
15 changes: 15 additions & 0 deletions pkg/lockfile/fixtures/poetry/optional-package.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[[package]]
name = "numpy"
version = "1.23.3"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = true
python-versions = ">=3.8"

[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "399777887f0c3171cbc3fc8a8e350d0fca4d882cf126657f60ec83872572ed44"

[metadata.files]
numpy = []
3 changes: 2 additions & 1 deletion pkg/lockfile/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lockfile_test
import (
"errors"
"fmt"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -48,7 +49,7 @@ func hasPackage(t *testing.T, packages []lockfile.PackageDetails, pkg lockfile.P
t.Helper()

for _, details := range packages {
if details == pkg {
if reflect.DeepEqual(details, pkg) {
return true
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/lockfile/parse-composer-lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (e ComposerLockExtractor) Extract(f DepFile) ([]PackageDetails, error) {
Commit: composerPackage.Dist.Reference,
Ecosystem: ComposerEcosystem,
CompareAs: ComposerEcosystem,
DepGroups: []string{"dev"},
})
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/lockfile/parse-composer-lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func TestParseComposerLock_OnePackageDev(t *testing.T) {
Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874",
Ecosystem: lockfile.ComposerEcosystem,
CompareAs: lockfile.ComposerEcosystem,
DepGroups: []string{"dev"},
},
})
}
Expand Down Expand Up @@ -152,6 +153,7 @@ func TestParseComposerLock_TwoPackages(t *testing.T) {
Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
Ecosystem: lockfile.ComposerEcosystem,
CompareAs: lockfile.ComposerEcosystem,
DepGroups: []string{"dev"},
},
})
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/lockfile/parse-conan-lock-v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestParseConanLock_v2_OnePackage(t *testing.T) {
Version: "1.2.11",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
})
}
Expand All @@ -71,6 +72,7 @@ func TestParseConanLock_v2_NoName(t *testing.T) {
Version: "1.2.11",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
})
}
Expand All @@ -90,12 +92,14 @@ func TestParseConanLock_v2_TwoPackages(t *testing.T) {
Version: "1.2.11",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
{
Name: "bzip2",
Version: "1.0.8",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
})
}
Expand All @@ -115,30 +119,35 @@ func TestParseConanLock_v2_NestedDependencies(t *testing.T) {
Version: "1.2.13",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
{
Name: "bzip2",
Version: "1.0.8",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
{
Name: "freetype",
Version: "2.12.1",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
{
Name: "libpng",
Version: "1.6.39",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
{
Name: "brotli",
Version: "1.0.9",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"requires"},
},
})
}
Expand All @@ -158,6 +167,7 @@ func TestParseConanLock_v2_OnePackageDev(t *testing.T) {
Version: "1.11.1",
Ecosystem: lockfile.ConanEcosystem,
CompareAs: lockfile.ConanEcosystem,
DepGroups: []string{"build-requires"},
},
})
}
9 changes: 5 additions & 4 deletions pkg/lockfile/parse-conan-lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func parseConanV1Lock(lockfile ConanLockFile) []PackageDetails {
return packages
}

func parseConanRequires(packages *[]PackageDetails, requires []string) {
func parseConanRequires(packages *[]PackageDetails, requires []string, group string) {
for _, ref := range requires {
reference := parseConanRenference(ref)
// skip entries with no name, they are most likely consumer's conanfiles
Expand All @@ -146,6 +146,7 @@ func parseConanRequires(packages *[]PackageDetails, requires []string) {
Version: reference.Version,
Ecosystem: ConanEcosystem,
CompareAs: ConanEcosystem,
DepGroups: []string{group},
})
}
}
Expand All @@ -157,9 +158,9 @@ func parseConanV2Lock(lockfile ConanLockFile) []PackageDetails {
uint64(len(lockfile.Requires))+uint64(len(lockfile.BuildRequires))+uint64(len(lockfile.PythonRequires)),
)

parseConanRequires(&packages, lockfile.Requires)
parseConanRequires(&packages, lockfile.BuildRequires)
parseConanRequires(&packages, lockfile.PythonRequires)
parseConanRequires(&packages, lockfile.Requires, "requires")
parseConanRequires(&packages, lockfile.BuildRequires, "build-requires")
parseConanRequires(&packages, lockfile.PythonRequires, "python-requires")

return packages
}
Expand Down
15 changes: 12 additions & 3 deletions pkg/lockfile/parse-maven-lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/google/osv-scanner/internal/cachedregexp"
)
Expand All @@ -14,6 +15,7 @@ type MavenLockDependency struct {
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Version string `xml:"version"`
Scope string `xml:"scope"`
}

func (mld MavenLockDependency) parseResolvedVersion(version string) string {
Expand Down Expand Up @@ -121,24 +123,31 @@ func (e MavenLockExtractor) Extract(f DepFile) ([]PackageDetails, error) {
for _, lockPackage := range parsedLockfile.Dependencies {
finalName := lockPackage.GroupID + ":" + lockPackage.ArtifactID

details[finalName] = PackageDetails{
pkgDetails := PackageDetails{
Name: finalName,
Version: lockPackage.ResolveVersion(*parsedLockfile),
Ecosystem: MavenEcosystem,
CompareAs: MavenEcosystem,
}
if strings.TrimSpace(lockPackage.Scope) != "" {
pkgDetails.DepGroups = append(pkgDetails.DepGroups, lockPackage.Scope)
}
details[finalName] = pkgDetails
}

// managed dependencies take precedent over standard dependencies
for _, lockPackage := range parsedLockfile.ManagedDependencies {
finalName := lockPackage.GroupID + ":" + lockPackage.ArtifactID

details[finalName] = PackageDetails{
pkgDetails := PackageDetails{
Name: finalName,
Version: lockPackage.ResolveVersion(*parsedLockfile),
Ecosystem: MavenEcosystem,
CompareAs: MavenEcosystem,
}
if strings.TrimSpace(lockPackage.Scope) != "" {
pkgDetails.DepGroups = append(pkgDetails.DepGroups, lockPackage.Scope)
}
details[finalName] = pkgDetails
}

return pkgDetailsMapToSlice(details), nil
Expand Down
20 changes: 20 additions & 0 deletions pkg/lockfile/parse-maven-lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,23 @@ func TestMavenLockDependency_ResolveVersion(t *testing.T) {
})
}
}

func TestParseMavenLock_WithScope(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseMavenLock("fixtures/maven/with-scope.xml")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "junit:junit",
Version: "4.12",
Ecosystem: lockfile.MavenEcosystem,
CompareAs: lockfile.MavenEcosystem,
DepGroups: []string{"test"},
},
})
}
Loading

0 comments on commit 64f5c2d

Please sign in to comment.