feat(nodejs): add dependency line numbers for npm lock files (#2932)

This commit is contained in:
DmitriyLewen
2022-10-12 18:22:34 +06:00
committed by GitHub
parent a8ff5f06b5
commit ca434f7f26
11 changed files with 566 additions and 10 deletions

2
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/alicebob/miniredis/v2 v2.23.0
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
github.com/aquasecurity/defsec v0.78.0
github.com/aquasecurity/go-dep-parser v0.0.0-20221003104638-a5451f447820
github.com/aquasecurity/go-dep-parser v0.0.0-20221011183558-5415cc446853
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46

4
go.sum
View File

@@ -197,8 +197,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
github.com/aquasecurity/defsec v0.78.0 h1:C0HjNrKtEM2LT1+adIV5rwczBobqnaJtbZx8D7AgMDQ=
github.com/aquasecurity/defsec v0.78.0/go.mod h1:wg9tVostHI0ynguaVfw+CDxrsQM/nY0sJIRmpu9FzhU=
github.com/aquasecurity/go-dep-parser v0.0.0-20221003104638-a5451f447820 h1:Rz33hTHMlsXx1H/Fyk6GdJbF0GGIPTequSrvs8ujkMk=
github.com/aquasecurity/go-dep-parser v0.0.0-20221003104638-a5451f447820/go.mod h1:6G1Y5nht5TL9kr1SzmrdE8PrmbNXo9nHx3qFR3qURg0=
github.com/aquasecurity/go-dep-parser v0.0.0-20221011183558-5415cc446853 h1:Ll9CkzGrZgVXvDARqv2N0R90kAWRwmJEKmg2D4M7v/0=
github.com/aquasecurity/go-dep-parser v0.0.0-20221011183558-5415cc446853/go.mod h1:n1NDYrNV4RFozrMCpS8ur2Y8pVTnh9rbOoBr3F9DF0A=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s=
github.com/aquasecurity/go-mock-aws v0.0.0-20220726154943-99847deb62b0 h1:tihCUjLWkF0b1SAjAKcFltUs3SpsqGrLtI+Frye0D10=

View File

@@ -65,6 +65,7 @@ func TestFilesystem(t *testing.T) {
args: args{
securityChecks: "vuln",
input: "testdata/fixtures/fs/nodejs",
listAllPkgs: true,
},
golden: "testdata/nodejs.json.golden",
},

View File

@@ -19,6 +19,202 @@
"Target": "package-lock.json",
"Class": "lang-pkgs",
"Type": "npm",
"Packages": [
{
"ID": "asap@2.0.6",
"Name": "asap",
"Version": "2.0.6",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 6,
"EndLine": 10
}
]
},
{
"ID": "jquery@3.3.9",
"Name": "jquery",
"Version": "3.3.9",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 16,
"EndLine": 20
}
]
},
{
"ID": "js-tokens@4.0.0",
"Name": "js-tokens",
"Version": "4.0.0",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 21,
"EndLine": 25
}
]
},
{
"ID": "lodash@4.17.4",
"Name": "lodash",
"Version": "4.17.4",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 11,
"EndLine": 15
}
]
},
{
"ID": "loose-envify@1.4.0",
"Name": "loose-envify",
"Version": "1.4.0",
"Indirect": true,
"DependsOn": [
"js-tokens@4.0.0"
],
"Layer": {},
"Locations": [
{
"StartLine": 26,
"EndLine": 33
}
]
},
{
"ID": "object-assign@4.1.1",
"Name": "object-assign",
"Version": "4.1.1",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 34,
"EndLine": 38
}
]
},
{
"ID": "promise@8.0.3",
"Name": "promise",
"Version": "8.0.3",
"Indirect": true,
"DependsOn": [
"asap@2.0.6"
],
"Layer": {},
"Locations": [
{
"StartLine": 39,
"EndLine": 46
}
]
},
{
"ID": "prop-types@15.7.2",
"Name": "prop-types",
"Version": "15.7.2",
"Indirect": true,
"DependsOn": [
"loose-envify@1.4.0",
"object-assign@4.1.1",
"react-is@16.8.6"
],
"Layer": {},
"Locations": [
{
"StartLine": 47,
"EndLine": 56
}
]
},
{
"ID": "react@16.8.6",
"Name": "react",
"Version": "16.8.6",
"Indirect": true,
"DependsOn": [
"loose-envify@1.4.0",
"object-assign@4.1.1",
"prop-types@15.7.2",
"scheduler@0.13.6"
],
"Layer": {},
"Locations": [
{
"StartLine": 57,
"EndLine": 67
}
]
},
{
"ID": "react-is@16.8.6",
"Name": "react-is",
"Version": "16.8.6",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 68,
"EndLine": 72
}
]
},
{
"ID": "redux@4.0.1",
"Name": "redux",
"Version": "4.0.1",
"Indirect": true,
"DependsOn": [
"loose-envify@1.4.0",
"symbol-observable@1.2.0"
],
"Layer": {},
"Locations": [
{
"StartLine": 73,
"EndLine": 81
}
]
},
{
"ID": "scheduler@0.13.6",
"Name": "scheduler",
"Version": "0.13.6",
"Indirect": true,
"DependsOn": [
"loose-envify@1.4.0",
"object-assign@4.1.1"
],
"Layer": {},
"Locations": [
{
"StartLine": 82,
"EndLine": 90
}
]
},
{
"ID": "symbol-observable@1.2.0",
"Name": "symbol-observable",
"Version": "1.2.0",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 91,
"EndLine": 95
}
]
}
],
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2019-11358",

View File

@@ -42,6 +42,14 @@ func ToAnalysisResult(fileType, filePath, libFilePath string, libs []godeptypes.
licenses[i] = licensing.Normalize(strings.TrimSpace(license))
}
}
var locs []types.Location
for _, loc := range lib.Locations {
l := types.Location{
StartLine: loc.StartLine,
EndLine: loc.EndLine,
}
locs = append(locs, l)
}
pkgs = append(pkgs, types.Package{
ID: lib.ID,
Name: lib.Name,
@@ -50,6 +58,7 @@ func ToAnalysisResult(fileType, filePath, libFilePath string, libs []godeptypes.
Indirect: lib.Indirect,
Licenses: licenses,
DependsOn: deps[lib.ID],
Locations: locs,
})
}
apps := []types.Application{{

View File

@@ -28,7 +28,7 @@ func (a npmLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisIn
p := npm.NewParser()
res, err := language.Analyze(types.Npm, input.FilePath, input.Content, p)
if err != nil {
return nil, xerrors.Errorf("unable to parse package-lock.json: %w", err)
return nil, xerrors.Errorf("unable to parse %s: %w", input.FilePath, err)
}
return res, nil
}

View File

@@ -0,0 +1,194 @@
package npm
import (
"context"
"os"
"sort"
"strings"
"testing"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_npmLibraryAnalyzer_Analyze(t *testing.T) {
tests := []struct {
name string
inputFile string
want *analyzer.AnalysisResult
wantErr string
}{
{
name: "happy path",
inputFile: "testdata/package-lock.json",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Npm,
FilePath: "testdata/package-lock.json",
Libraries: []types.Package{
{
ID: "array-flatten@1.1.1",
Name: "array-flatten",
Version: "1.1.1",
Indirect: true,
Locations: []types.Location{
{
StartLine: 12,
EndLine: 16,
},
},
},
{
ID: "body-parser@1.18.3",
Name: "body-parser",
Version: "1.18.3",
Indirect: true,
DependsOn: []string{"debug@2.6.9"},
Locations: []types.Location{
{
StartLine: 17,
EndLine: 39,
},
},
},
{
ID: "debug@2.6.9",
Name: "debug",
Version: "2.6.9",
Indirect: true,
DependsOn: []string{"ms@2.0.0"},
Locations: []types.Location{
{
StartLine: 25,
EndLine: 32,
},
{
StartLine: 48,
EndLine: 55,
},
},
},
{
ID: "express@4.16.4",
Name: "express",
Version: "4.16.4",
Indirect: true,
DependsOn: []string{"debug@2.6.9"},
Locations: []types.Location{
{
StartLine: 40,
EndLine: 62,
},
},
},
{
ID: "ms@2.0.0",
Name: "ms",
Version: "2.0.0",
Indirect: true,
Locations: []types.Location{
{
StartLine: 33,
EndLine: 37,
},
{
StartLine: 56,
EndLine: 60,
},
},
},
{
ID: "ms@2.1.1",
Name: "ms",
Version: "2.1.1",
Indirect: true,
Locations: []types.Location{
{
StartLine: 63,
EndLine: 67,
},
},
},
},
},
},
},
},
{
name: "sad path",
inputFile: "testdata/wrong.json",
wantErr: "unable to parse testdata/wrong.json",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()
a := npmLibraryAnalyzer{}
got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{
FilePath: tt.inputFile,
Content: f,
})
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.NoError(t, err)
sortPkgs(got.Applications[0].Libraries)
assert.Equal(t, tt.want, got)
})
}
}
func sortPkgs(libs []types.Package) {
sort.Slice(libs, func(i, j int) bool {
ret := strings.Compare(libs[i].Name, libs[j].Name)
if ret == 0 {
return libs[i].Version < libs[j].Version
}
return ret < 0
})
for _, lib := range libs {
sortLocations(lib.Locations)
}
}
func sortLocations(locs []types.Location) {
sort.Slice(locs, func(i, j int) bool {
return locs[i].StartLine < locs[j].StartLine
})
}
func Test_nodePkgLibraryAnalyzer_Required(t *testing.T) {
tests := []struct {
name string
filePath string
want bool
}{
{
name: "happy path",
filePath: "npm/package-lock.json",
want: true,
},
{
name: "sad path",
filePath: "npm/package.json",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := npmLibraryAnalyzer{}
got := a.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,69 @@
{
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ansi-colors": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
"integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
"dev": true
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.18.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
"requires": {
"debug": "2.6.9"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"express": {
"version": "4.16.4",
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
"requires": {
"debug": "2.6.9"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
}

View File

@@ -0,0 +1 @@
{broken}

View File

@@ -736,28 +736,52 @@
"Name": "asap",
"Version": "2.0.6",
"Indirect": true,
"Layer": {}
"Layer": {},
"Locations": [
{
"StartLine": 6,
"EndLine": 10
}
]
},
{
"ID": "jquery@3.3.9",
"Name": "jquery",
"Version": "3.3.9",
"Indirect": true,
"Layer": {}
"Layer": {},
"Locations": [
{
"StartLine": 16,
"EndLine": 20
}
]
},
{
"ID": "js-tokens@4.0.0",
"Name": "js-tokens",
"Version": "4.0.0",
"Indirect": true,
"Layer": {}
"Layer": {},
"Locations": [
{
"StartLine": 21,
"EndLine": 25
}
]
},
{
"ID": "lodash@4.17.4",
"Name": "lodash",
"Version": "4.17.4",
"Indirect": true,
"Layer": {}
"Layer": {},
"Locations": [
{
"StartLine": 11,
"EndLine": 15
}
]
},
{
"ID": "loose-envify@1.4.0",
@@ -765,6 +789,12 @@
"Version": "1.4.0",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 26,
"EndLine": 33
}
],
"DependsOn": ["js-tokens@4.0.0"]
},
{
@@ -772,7 +802,13 @@
"Name": "object-assign",
"Version": "4.1.1",
"Indirect": true,
"Layer": {}
"Layer": {},
"Locations": [
{
"StartLine": 34,
"EndLine": 38
}
]
},
{
"ID": "promise@8.0.3",
@@ -780,6 +816,12 @@
"Version": "8.0.3",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 39,
"EndLine": 46
}
],
"DependsOn": ["asap@2.0.6"]
},
{
@@ -788,6 +830,12 @@
"Version": "15.7.2",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 47,
"EndLine": 56
}
],
"DependsOn": ["loose-envify@1.4.0", "object-assign@4.1.1", "react-is@16.8.6"]
},
{
@@ -796,6 +844,12 @@
"Version": "16.8.6",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 57,
"EndLine": 67
}
],
"DependsOn": ["loose-envify@1.4.0", "object-assign@4.1.1", "prop-types@15.7.2", "scheduler@0.13.6"]
},
{
@@ -803,6 +857,12 @@
"Name": "react-is",
"Version": "16.8.6",
"Indirect": true,
"Locations": [
{
"StartLine": 68,
"EndLine": 72
}
],
"Layer": {}
},
{
@@ -811,6 +871,12 @@
"Version": "4.0.1",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 73,
"EndLine": 81
}
],
"DependsOn": ["loose-envify@1.4.0", "symbol-observable@1.2.0"]
},
{
@@ -819,6 +885,12 @@
"Version": "0.13.6",
"Indirect": true,
"Layer": {},
"Locations": [
{
"StartLine": 82,
"EndLine": 90
}
],
"DependsOn": ["loose-envify@1.4.0", "object-assign@4.1.1"]
},
{
@@ -826,7 +898,13 @@
"Name": "symbol-observable",
"Version": "1.2.0",
"Indirect": true,
"Layer": {}
"Layer": {},
"Locations": [
{
"StartLine": 91,
"EndLine": 95
}
]
}
]
},

View File

@@ -47,6 +47,14 @@ type Package struct {
// Each package metadata have the file path, while the package from lock files does not have.
FilePath string `json:",omitempty"`
// lines from the lock file where the dependency is written
Locations []Location `json:",omitempty"`
}
type Location struct {
StartLine int `json:",omitempty"`
EndLine int `json:",omitempty"`
}
// BuildInfo represents information under /root/buildinfo in RHEL