@lateos/npm-scan 0.16.0 → 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.
Files changed (110) hide show
  1. package/.dockerignore +20 -20
  2. package/.husky/pre-commit +1 -1
  3. package/CHANGELOG.md +199 -199
  4. package/LICENSING.md +19 -19
  5. package/README.de.md +708 -669
  6. package/README.fr.md +707 -668
  7. package/README.ja.md +704 -665
  8. package/README.md +826 -801
  9. package/README.zh.md +708 -669
  10. package/SECURITY.md +72 -72
  11. package/backend/cra.js +68 -68
  12. package/backend/db/schema.sql +32 -32
  13. package/backend/db.js +88 -88
  14. package/backend/detectors/atk-001-lifecycle.js +17 -17
  15. package/backend/detectors/atk-002-obfusc.js +261 -261
  16. package/backend/detectors/atk-003-creds.js +13 -13
  17. package/backend/detectors/atk-004-persist.js +13 -13
  18. package/backend/detectors/atk-005-exfil.js +13 -13
  19. package/backend/detectors/atk-006-depconf.js +14 -14
  20. package/backend/detectors/atk-007-typosquat.js +34 -34
  21. package/backend/detectors/atk-008-tarball-tamper.js +91 -91
  22. package/backend/detectors/atk-009-dormant-trigger.js +62 -62
  23. package/backend/detectors/atk-010-sandbox-evasion.js +50 -50
  24. package/backend/detectors/atk-011-transitive-prop.js +76 -76
  25. package/backend/detectors/axios-poisoning/d1-version-fingerprint.js +24 -0
  26. package/backend/detectors/axios-poisoning/d2-decoy-dep.js +24 -0
  27. package/backend/detectors/axios-poisoning/d3-postinstall-rat.js +90 -0
  28. package/backend/detectors/axios-poisoning/index.js +94 -0
  29. package/backend/detectors/cve-2026-48710-badhost/codePattern.js +99 -99
  30. package/backend/detectors/cve-2026-48710-badhost/findings.js +105 -105
  31. package/backend/detectors/cve-2026-48710-badhost/index.js +15 -15
  32. package/backend/detectors/cve-2026-48710-badhost/manifest.js +305 -305
  33. package/backend/detectors/cve-2026-48710-badhost/transitive.js +189 -189
  34. package/backend/detectors/hf-impersonation/index.js +396 -396
  35. package/backend/detectors/hf-impersonation/jaro-winkler.js +44 -44
  36. package/backend/detectors/hf-impersonation/known-orgs.js +5 -5
  37. package/backend/detectors/hf-impersonation/simhash.js +46 -46
  38. package/backend/detectors/index.js +75 -38
  39. package/backend/detectors/megalodon/d1-workflow-scan.js +147 -147
  40. package/backend/detectors/megalodon/d2-credential-harvest.js +61 -61
  41. package/backend/detectors/megalodon/d3-publish-velocity.js +67 -67
  42. package/backend/detectors/megalodon/d4-publisher-drift.js +124 -124
  43. package/backend/detectors/megalodon/d5-bot-commit-identity.js +3 -3
  44. package/backend/detectors/megalodon/d6-date-anachronism.js +3 -3
  45. package/backend/detectors/megalodon/index.js +80 -80
  46. package/backend/detectors/megalodon/types.js +9 -9
  47. package/backend/detectors/mini-shai-hulud/d1-burst-publish.js +42 -42
  48. package/backend/detectors/mini-shai-hulud/d2-sibling-compromise.js +116 -116
  49. package/backend/detectors/mini-shai-hulud/d3-slsa-mismatch.js +72 -72
  50. package/backend/detectors/mini-shai-hulud/d4-maintainer-anomaly.js +45 -45
  51. package/backend/detectors/mini-shai-hulud/d5-ioc-check.js +95 -95
  52. package/backend/detectors/mini-shai-hulud/d6-token-exfil.js +38 -38
  53. package/backend/detectors/mini-shai-hulud/index.js +118 -118
  54. package/backend/detectors/mini-shai-hulud/iocs.json +79 -79
  55. package/backend/detectors/msh-supplement/d1-obfuscation.js +18 -0
  56. package/backend/detectors/msh-supplement/d2-persistence.js +47 -0
  57. package/backend/detectors/msh-supplement/d3-geo-killswitch.js +35 -0
  58. package/backend/detectors/msh-supplement/d4-c2-deaddrop.js +33 -0
  59. package/backend/detectors/msh-supplement/index.js +107 -0
  60. package/backend/detectors/tier1-binary-embed.js +219 -0
  61. package/backend/detectors/tier1-infostealer.js +280 -0
  62. package/backend/detectors/tier1-lifecycle-hook.js +176 -0
  63. package/backend/detectors/tier1-metadata-spoof.js +180 -0
  64. package/backend/detectors/tier1-typosquat.js +219 -0
  65. package/backend/detectors/typosquat-vpmdhaj/d1-maintainer.js +77 -0
  66. package/backend/detectors/typosquat-vpmdhaj/d2-preinstall-loader.js +37 -0
  67. package/backend/detectors/typosquat-vpmdhaj/d3-cred-exfil.js +66 -0
  68. package/backend/detectors/typosquat-vpmdhaj/index.js +98 -0
  69. package/backend/fetch.js +175 -175
  70. package/backend/index.js +4 -4
  71. package/backend/license.js +89 -89
  72. package/backend/lockfile.js +379 -379
  73. package/backend/pdf.js +245 -245
  74. package/backend/policy.js +193 -176
  75. package/backend/provenance.js +79 -0
  76. package/backend/report.js +254 -254
  77. package/backend/sbom.js +66 -66
  78. package/backend/siem/cef.js +32 -32
  79. package/backend/siem/ecs.js +40 -40
  80. package/backend/siem/index.js +18 -18
  81. package/backend/siem/qradar.js +56 -56
  82. package/backend/siem/sentinel.js +27 -27
  83. package/backend/vsix-scan/detectors/activation-event-risk.js +116 -116
  84. package/backend/vsix-scan/detectors/burst-publish.js +52 -52
  85. package/backend/vsix-scan/detectors/exfil-pattern.js +88 -88
  86. package/backend/vsix-scan/detectors/known-ioc.js +105 -105
  87. package/backend/vsix-scan/detectors/orphan-commit-fetch.js +69 -69
  88. package/backend/vsix-scan/detectors/publisher-anomaly.js +70 -70
  89. package/backend/vsix-scan/index.js +183 -183
  90. package/backend/vsix-scan/marketplace-client.js +145 -145
  91. package/backend/vsix-scan/vsix-iocs.json +31 -31
  92. package/cli/cli.js +458 -458
  93. package/deploy/helm/npm-scan/Chart.yaml +21 -21
  94. package/deploy/helm/npm-scan/templates/_helpers.tpl +8 -8
  95. package/deploy/helm/npm-scan/templates/api.yaml +93 -93
  96. package/deploy/helm/npm-scan/templates/ingress.yaml +27 -27
  97. package/deploy/helm/npm-scan/templates/postgresql.yaml +66 -66
  98. package/deploy/helm/npm-scan/templates/secrets.yaml +18 -18
  99. package/deploy/helm/npm-scan/templates/worker.yaml +31 -31
  100. package/deploy/helm/npm-scan/values.byoc.yaml +74 -74
  101. package/deploy/helm/npm-scan/values.yaml +102 -102
  102. package/package.json +57 -57
  103. package/scripts/download-corpus.js +30 -30
  104. package/scripts/gen-mal-corpus.js +34 -34
  105. package/scripts/generate-campaign-fixtures.js +170 -0
  106. package/src/config/top-5000.json +87 -0
  107. package/test/fixtures/lockfiles/npm-lock.json +68 -68
  108. package/test/fixtures/lockfiles/pnpm-lock.yaml +117 -117
  109. package/test/fixtures/lockfiles/yarn.lock +103 -103
  110. package/test/fixtures/mock-data.js +69 -69
@@ -1,145 +1,145 @@
1
- const MARKETPLACE_API = 'https://marketplace.visualstudio.com/_apis/public/gallery';
2
- const OPENVSX_API = 'https://open-vsx.org/api';
3
-
4
- const _cache = new Map();
5
- const CACHE_TTL = 5 * 60 * 1000;
6
- const RATE_LIMIT_MS = 6000;
7
- let _lastFetchTime = 0;
8
-
9
- function sleep(ms) {
10
- return new Promise(r => setTimeout(r, ms));
11
- }
12
-
13
- async function rateLimitedFetch(url) {
14
- const now = Date.now();
15
- const elapsed = now - _lastFetchTime;
16
- if (elapsed < RATE_LIMIT_MS) {
17
- await sleep(RATE_LIMIT_MS - elapsed);
18
- }
19
- _lastFetchTime = Date.now();
20
-
21
- const cached = _cache.get(url);
22
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
23
- return cached.data;
24
- }
25
-
26
- let res;
27
- try {
28
- res = await fetch(url);
29
- if (res.status === 429) {
30
- const retryAfter = parseInt(res.headers.get('Retry-After') || '10', 10);
31
- await sleep(retryAfter * 1000);
32
- res = await fetch(url);
33
- }
34
- if (!res.ok) {
35
- console.debug(`Marketplace API warning: ${url} returned ${res.status}`);
36
- return null;
37
- }
38
- const data = await res.json();
39
- _cache.set(url, { data, fetchedAt: Date.now() });
40
- return data;
41
- } catch (err) {
42
- console.debug(`Marketplace API error: ${err.message}`);
43
- return null;
44
- }
45
- }
46
-
47
- function parseExtensionId(id) {
48
- const parts = id.split('.');
49
- if (parts.length < 2) throw new Error(`Invalid extension ID: ${id}`);
50
- return { publisherId: parts[0], extensionName: parts.slice(1).join('.') };
51
- }
52
-
53
- export async function getExtensionMetadata(publisherId, extensionName) {
54
- const url = `${MARKETPLACE_API}/extensionquery`;
55
- const body = {
56
- filters: [{
57
- criteria: [
58
- { filterType: 8, value: `${publisherId}.${extensionName}` },
59
- ],
60
- }],
61
- flags: 914,
62
- };
63
-
64
- const cached = _cache.get(url + JSON.stringify(body));
65
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
66
- return cached.data;
67
- }
68
-
69
- const now = Date.now();
70
- const elapsed = now - _lastFetchTime;
71
- if (elapsed < RATE_LIMIT_MS) {
72
- await sleep(RATE_LIMIT_MS - elapsed);
73
- }
74
- _lastFetchTime = Date.now();
75
-
76
- let res;
77
- try {
78
- res = await fetch(url, {
79
- method: 'POST',
80
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1' },
81
- body: JSON.stringify(body),
82
- });
83
- if (res.status === 429) {
84
- const retryAfter = parseInt(res.headers.get('Retry-After') || '10', 10);
85
- await sleep(retryAfter * 1000);
86
- res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1' }, body: JSON.stringify(body) });
87
- }
88
- if (!res.ok) {
89
- console.debug(`Marketplace API warning: ${url} returned ${res.status}`);
90
- return null;
91
- }
92
- const data = await res.json();
93
- _cache.set(url + JSON.stringify(body), { data, fetchedAt: Date.now() });
94
- return data;
95
- } catch (err) {
96
- console.debug(`Marketplace API error: ${err.message}`);
97
- return null;
98
- }
99
- }
100
-
101
- export async function getVersionHistory(publisherId, extensionName) {
102
- const data = await getExtensionMetadata(publisherId, extensionName);
103
- if (!data?.results?.[0]?.extensions?.[0]) return [];
104
-
105
- const extension = data.results[0].extensions[0];
106
- const versions = extension.versions || [];
107
-
108
- return versions.map(v => ({
109
- version: v.version,
110
- publishedAt: v.lastUpdated || v.publishedDate,
111
- publishedBy: extension.publisher?.publisherName || publisherId,
112
- assetSha256: v.assetUri ? null : null,
113
- flags: v.flags ? [String(v.flags)] : [],
114
- }));
115
- }
116
-
117
- export async function getPublisherProfile(publisherId) {
118
- const url = `${MARKETPLACE_API}/publishers/${publisherId}`;
119
- return rateLimitedFetch(url);
120
- }
121
-
122
- export async function getOpenVsxMetadata(namespace, name) {
123
- const url = `${OPENVSX_API}/${namespace}/${name}`;
124
- return rateLimitedFetch(url);
125
- }
126
-
127
- export async function getOpenVsxVersionHistory(namespace, name) {
128
- const data = await getOpenVsxMetadata(namespace, name);
129
- if (!data) return [];
130
- const versions = data.allVersions || {};
131
- const files = data.files || {};
132
-
133
- return Object.entries(versions).map(([version, publishedAt]) => ({
134
- version,
135
- publishedAt: typeof publishedAt === 'string' ? publishedAt : data.timestamp,
136
- publishedBy: data.namespace || namespace,
137
- assetSha256: files?.[version]?.sha256 || null,
138
- flags: [],
139
- }));
140
- }
141
-
142
- export function clearMarketplaceCache() {
143
- _cache.clear();
144
- _lastFetchTime = 0;
145
- }
1
+ const MARKETPLACE_API = 'https://marketplace.visualstudio.com/_apis/public/gallery';
2
+ const OPENVSX_API = 'https://open-vsx.org/api';
3
+
4
+ const _cache = new Map();
5
+ const CACHE_TTL = 5 * 60 * 1000;
6
+ const RATE_LIMIT_MS = 6000;
7
+ let _lastFetchTime = 0;
8
+
9
+ function sleep(ms) {
10
+ return new Promise(r => setTimeout(r, ms));
11
+ }
12
+
13
+ async function rateLimitedFetch(url) {
14
+ const now = Date.now();
15
+ const elapsed = now - _lastFetchTime;
16
+ if (elapsed < RATE_LIMIT_MS) {
17
+ await sleep(RATE_LIMIT_MS - elapsed);
18
+ }
19
+ _lastFetchTime = Date.now();
20
+
21
+ const cached = _cache.get(url);
22
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
23
+ return cached.data;
24
+ }
25
+
26
+ let res;
27
+ try {
28
+ res = await fetch(url);
29
+ if (res.status === 429) {
30
+ const retryAfter = parseInt(res.headers.get('Retry-After') || '10', 10);
31
+ await sleep(retryAfter * 1000);
32
+ res = await fetch(url);
33
+ }
34
+ if (!res.ok) {
35
+ console.debug(`Marketplace API warning: ${url} returned ${res.status}`);
36
+ return null;
37
+ }
38
+ const data = await res.json();
39
+ _cache.set(url, { data, fetchedAt: Date.now() });
40
+ return data;
41
+ } catch (err) {
42
+ console.debug(`Marketplace API error: ${err.message}`);
43
+ return null;
44
+ }
45
+ }
46
+
47
+ function parseExtensionId(id) {
48
+ const parts = id.split('.');
49
+ if (parts.length < 2) throw new Error(`Invalid extension ID: ${id}`);
50
+ return { publisherId: parts[0], extensionName: parts.slice(1).join('.') };
51
+ }
52
+
53
+ export async function getExtensionMetadata(publisherId, extensionName) {
54
+ const url = `${MARKETPLACE_API}/extensionquery`;
55
+ const body = {
56
+ filters: [{
57
+ criteria: [
58
+ { filterType: 8, value: `${publisherId}.${extensionName}` },
59
+ ],
60
+ }],
61
+ flags: 914,
62
+ };
63
+
64
+ const cached = _cache.get(url + JSON.stringify(body));
65
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
66
+ return cached.data;
67
+ }
68
+
69
+ const now = Date.now();
70
+ const elapsed = now - _lastFetchTime;
71
+ if (elapsed < RATE_LIMIT_MS) {
72
+ await sleep(RATE_LIMIT_MS - elapsed);
73
+ }
74
+ _lastFetchTime = Date.now();
75
+
76
+ let res;
77
+ try {
78
+ res = await fetch(url, {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1' },
81
+ body: JSON.stringify(body),
82
+ });
83
+ if (res.status === 429) {
84
+ const retryAfter = parseInt(res.headers.get('Retry-After') || '10', 10);
85
+ await sleep(retryAfter * 1000);
86
+ res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1' }, body: JSON.stringify(body) });
87
+ }
88
+ if (!res.ok) {
89
+ console.debug(`Marketplace API warning: ${url} returned ${res.status}`);
90
+ return null;
91
+ }
92
+ const data = await res.json();
93
+ _cache.set(url + JSON.stringify(body), { data, fetchedAt: Date.now() });
94
+ return data;
95
+ } catch (err) {
96
+ console.debug(`Marketplace API error: ${err.message}`);
97
+ return null;
98
+ }
99
+ }
100
+
101
+ export async function getVersionHistory(publisherId, extensionName) {
102
+ const data = await getExtensionMetadata(publisherId, extensionName);
103
+ if (!data?.results?.[0]?.extensions?.[0]) return [];
104
+
105
+ const extension = data.results[0].extensions[0];
106
+ const versions = extension.versions || [];
107
+
108
+ return versions.map(v => ({
109
+ version: v.version,
110
+ publishedAt: v.lastUpdated || v.publishedDate,
111
+ publishedBy: extension.publisher?.publisherName || publisherId,
112
+ assetSha256: v.assetUri ? null : null,
113
+ flags: v.flags ? [String(v.flags)] : [],
114
+ }));
115
+ }
116
+
117
+ export async function getPublisherProfile(publisherId) {
118
+ const url = `${MARKETPLACE_API}/publishers/${publisherId}`;
119
+ return rateLimitedFetch(url);
120
+ }
121
+
122
+ export async function getOpenVsxMetadata(namespace, name) {
123
+ const url = `${OPENVSX_API}/${namespace}/${name}`;
124
+ return rateLimitedFetch(url);
125
+ }
126
+
127
+ export async function getOpenVsxVersionHistory(namespace, name) {
128
+ const data = await getOpenVsxMetadata(namespace, name);
129
+ if (!data) return [];
130
+ const versions = data.allVersions || {};
131
+ const files = data.files || {};
132
+
133
+ return Object.entries(versions).map(([version, publishedAt]) => ({
134
+ version,
135
+ publishedAt: typeof publishedAt === 'string' ? publishedAt : data.timestamp,
136
+ publishedBy: data.namespace || namespace,
137
+ assetSha256: files?.[version]?.sha256 || null,
138
+ flags: [],
139
+ }));
140
+ }
141
+
142
+ export function clearMarketplaceCache() {
143
+ _cache.clear();
144
+ _lastFetchTime = 0;
145
+ }
@@ -1,31 +1,31 @@
1
- {
2
- "lastUpdated": "2026-05-25",
3
- "schema": "vsix-ioc-v1",
4
- "iocs": [
5
- {
6
- "type": "extensionId",
7
- "value": "nrwl.angular-console",
8
- "maliciousVersions": ["18.95.0"],
9
- "wave": "nx-console-wave3",
10
- "cve": "CVE-2026-48027",
11
- "exposureWindowStart": "2026-05-18T12:30:00Z",
12
- "exposureWindowEnd": "2026-05-18T13:09:00Z",
13
- "registries": ["marketplace", "open-vsx"],
14
- "safeVersion": ">=18.100.0",
15
- "source": "https://nx.dev/blog/nx-console-v18-95-0-postmortem"
16
- },
17
- {
18
- "type": "publisherAccount",
19
- "value": "nrwl",
20
- "compromiseWindowStart": "2026-05-11T00:00:00Z",
21
- "compromiseWindowEnd": "2026-05-18T13:09:00Z",
22
- "note": "Contributor token stolen via TanStack wave1 on May 11; 7-day dwell before publish"
23
- },
24
- {
25
- "type": "orphanCommitHash",
26
- "value": "PLACEHOLDER_UPDATE_FROM_THREAT_INTEL",
27
- "repo": "nrwl/nx",
28
- "note": "Dangling commit hosting 498KB Bun payload — update hash from StepSecurity IOC report"
29
- }
30
- ]
31
- }
1
+ {
2
+ "lastUpdated": "2026-05-25",
3
+ "schema": "vsix-ioc-v1",
4
+ "iocs": [
5
+ {
6
+ "type": "extensionId",
7
+ "value": "nrwl.angular-console",
8
+ "maliciousVersions": ["18.95.0"],
9
+ "wave": "nx-console-wave3",
10
+ "cve": "CVE-2026-48027",
11
+ "exposureWindowStart": "2026-05-18T12:30:00Z",
12
+ "exposureWindowEnd": "2026-05-18T13:09:00Z",
13
+ "registries": ["marketplace", "open-vsx"],
14
+ "safeVersion": ">=18.100.0",
15
+ "source": "https://nx.dev/blog/nx-console-v18-95-0-postmortem"
16
+ },
17
+ {
18
+ "type": "publisherAccount",
19
+ "value": "nrwl",
20
+ "compromiseWindowStart": "2026-05-11T00:00:00Z",
21
+ "compromiseWindowEnd": "2026-05-18T13:09:00Z",
22
+ "note": "Contributor token stolen via TanStack wave1 on May 11; 7-day dwell before publish"
23
+ },
24
+ {
25
+ "type": "orphanCommitHash",
26
+ "value": "PLACEHOLDER_UPDATE_FROM_THREAT_INTEL",
27
+ "repo": "nrwl/nx",
28
+ "note": "Dangling commit hosting 498KB Bun payload — update hash from StepSecurity IOC report"
29
+ }
30
+ ]
31
+ }