From 12c5bf0805753c8d68423a06920f184f2fd55772 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:23:51 +0600 Subject: [PATCH] fix(nodejs): add name validation for package name from `package.json` (#6268) --- .../parser/nodejs/packagejson/parse.go | 16 +++++ .../parser/nodejs/packagejson/parse_test.go | 58 ++++++++++++++++++- .../packagejson/testdata/invalid_name.json | 11 ++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json diff --git a/pkg/dependency/parser/nodejs/packagejson/parse.go b/pkg/dependency/parser/nodejs/packagejson/parse.go index a9f4dea07a1a..f2558750c323 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse.go @@ -3,6 +3,7 @@ package packagejson import ( "encoding/json" "io" + "regexp" "golang.org/x/xerrors" @@ -10,6 +11,8 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" ) +var nameRegexp = regexp.MustCompile(`^(@[A-Za-z0-9-._]+/)?[A-Za-z0-9-._]+$`) + type packageJSON struct { Name string `json:"name"` Version string `json:"version"` @@ -40,6 +43,10 @@ func (p *Parser) Parse(r io.Reader) (Package, error) { return Package{}, xerrors.Errorf("JSON decode error: %w", err) } + if !IsValidName(pkgJSON.Name) { + return Package{}, xerrors.Errorf("Name can only contain URL-friendly characters") + } + var id string // Name and version fields are optional // https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name @@ -73,3 +80,12 @@ func parseLicense(val interface{}) string { } return "" } + +func IsValidName(name string) bool { + // Name is optional field + // https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name + if name == "" { + return true + } + return nameRegexp.MatchString(name) +} diff --git a/pkg/dependency/parser/nodejs/packagejson/parse_test.go b/pkg/dependency/parser/nodejs/packagejson/parse_test.go index c90d37d6440e..9b925a525be3 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse_test.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse_test.go @@ -2,7 +2,6 @@ package packagejson_test import ( "os" - "path" "testing" "github.com/stretchr/testify/assert" @@ -77,6 +76,11 @@ func TestParse(t *testing.T) { }, }, }, + { + name: "invalid package name", + inputFile: "testdata/invalid_name.json", + wantErr: "Name can only contain URL-friendly characters", + }, { name: "sad path", inputFile: "testdata/invalid_package.json", @@ -99,7 +103,7 @@ func TestParse(t *testing.T) { } for _, v := range vectors { - t.Run(path.Base(v.name), func(t *testing.T) { + t.Run(v.name, func(t *testing.T) { f, err := os.Open(v.inputFile) require.NoError(t, err) defer f.Close() @@ -115,3 +119,53 @@ func TestParse(t *testing.T) { }) } } + +func TestIsValidName(t *testing.T) { + tests := []struct { + name string + want bool + }{ + { + name: "", + want: true, + }, + { + name: "test_package", + want: true, + }, + { + name: "test.package", + want: true, + }, + { + name: "test-package", + want: true, + }, + { + name: "@test/package", + want: true, + }, + { + name: "test@package", + want: false, + }, { + name: "test?package", + want: false, + }, + { + name: "test/package", + want: false, + }, + { + name: "package/", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + valid := packagejson.IsValidName(tt.name) + require.Equal(t, tt.want, valid) + }) + } +} diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json b/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json new file mode 100644 index 000000000000..f97f91d2c852 --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json @@ -0,0 +1,11 @@ +{ + "name": "@invalid/packageName/", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} \ No newline at end of file