Skip to content

Commit b28e651

Browse files
committed
Fix git pkgs where matching inside integrity hashes
The plain substring search matched package names inside base64 integrity hashes, producing false positives. Replace strings.Contains with a regex using non-alphanumeric boundary assertions so that a search for "six" won't match "sha512-...SIxia...".
1 parent 376652a commit b28e651

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

cmd/where.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9+
"regexp"
910
"strings"
1011

1112
"github.com/git-pkgs/manifests"
@@ -150,6 +151,11 @@ func searchFileForPackage(path, relPath, packageName, ecosystem string, contextL
150151
var matches []WhereMatch
151152
var lines []string
152153

154+
// Case-insensitive search with non-alphanumeric boundaries to avoid matching inside hashes.
155+
// We can't use \b because package names may start/end with non-word chars (e.g. @scope/pkg).
156+
quoted := regexp.QuoteMeta(packageName)
157+
re := regexp.MustCompile(`(?i)(?:^|[^A-Za-z0-9])` + quoted + `(?:$|[^A-Za-z0-9])`)
158+
153159
scanner := bufio.NewScanner(file)
154160
lineNum := 0
155161

@@ -158,8 +164,7 @@ func searchFileForPackage(path, relPath, packageName, ecosystem string, contextL
158164
line := scanner.Text()
159165
lines = append(lines, line)
160166

161-
// Case-insensitive search for the package name
162-
if strings.Contains(strings.ToLower(line), strings.ToLower(packageName)) {
167+
if re.MatchString(line) {
163168
match := WhereMatch{
164169
FilePath: relPath,
165170
LineNumber: lineNum,

cmd/where_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func TestSearchFileForPackage(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
content string
13+
packageName string
14+
wantLines []int
15+
}{
16+
{
17+
name: "matches package name in dependency line",
18+
content: ` "six": "^1.0.0",`,
19+
packageName: "six",
20+
wantLines: []int{1},
21+
},
22+
{
23+
name: "does not match inside integrity hash",
24+
content: ` "integrity": "sha512-abc123SIxia456def==",`,
25+
packageName: "six",
26+
wantLines: nil,
27+
},
28+
{
29+
name: "matches real dependency but not hash containing same text",
30+
content: `{
31+
"node_modules/six": {
32+
"version": "1.16.0",
33+
"resolved": "https://registry.npmjs.org/six/-/six-1.16.0.tgz",
34+
"integrity": "sha512-ySIxiAbcSIxcdefgSIxyz=="
35+
}
36+
}`,
37+
packageName: "six",
38+
wantLines: []int{2, 4},
39+
},
40+
{
41+
name: "case insensitive match",
42+
content: ` "Six": "^2.0.0",`,
43+
packageName: "six",
44+
wantLines: []int{1},
45+
},
46+
{
47+
name: "matches with special regex characters in name",
48+
content: ` "@scope/my.pkg": "^1.0.0",`,
49+
packageName: "@scope/my.pkg",
50+
wantLines: []int{1},
51+
},
52+
{
53+
name: "no match when package name is substring of another word",
54+
content: ` "sixteenth": "^1.0.0",`,
55+
packageName: "six",
56+
wantLines: nil,
57+
},
58+
}
59+
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
dir := t.TempDir()
63+
path := filepath.Join(dir, "package-lock.json")
64+
if err := os.WriteFile(path, []byte(tt.content), 0644); err != nil {
65+
t.Fatal(err)
66+
}
67+
68+
matches, err := searchFileForPackage(path, "package-lock.json", tt.packageName, "npm", 0)
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
73+
if len(matches) != len(tt.wantLines) {
74+
t.Fatalf("got %d matches, want %d", len(matches), len(tt.wantLines))
75+
}
76+
77+
for i, m := range matches {
78+
if m.LineNumber != tt.wantLines[i] {
79+
t.Errorf("match %d: got line %d, want %d", i, m.LineNumber, tt.wantLines[i])
80+
}
81+
}
82+
})
83+
}
84+
}

0 commit comments

Comments
 (0)