@lateos/npm-scan 1.0.0 → 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 (125) hide show
  1. package/README.md +864 -861
  2. package/backend/cra.js +113 -21
  3. package/backend/db.js +18 -10
  4. package/backend/detectors/atk-001-lifecycle.js +5 -5
  5. package/backend/detectors/atk-002-obfusc.js +126 -47
  6. package/backend/detectors/atk-003-creds.js +8 -4
  7. package/backend/detectors/atk-004-persist.js +3 -3
  8. package/backend/detectors/atk-005-exfil.js +8 -4
  9. package/backend/detectors/atk-006-depconf.js +3 -3
  10. package/backend/detectors/atk-007-typosquat.js +64 -10
  11. package/backend/detectors/atk-008-tarball-tamper.js +6 -6
  12. package/backend/detectors/atk-009-dormant-trigger.js +9 -5
  13. package/backend/detectors/atk-010-sandbox-evasion.js +25 -10
  14. package/backend/detectors/atk-011-transitive-prop.js +14 -13
  15. package/backend/detectors/axios-poisoning/d1-version-fingerprint.js +4 -4
  16. package/backend/detectors/axios-poisoning/d2-decoy-dep.js +5 -1
  17. package/backend/detectors/axios-poisoning/d3-postinstall-rat.js +64 -19
  18. package/backend/detectors/axios-poisoning/index.js +77 -60
  19. package/backend/detectors/config/thresholds.js +48 -3
  20. package/backend/detectors/cve-2026-48710-badhost/codePattern.js +26 -9
  21. package/backend/detectors/cve-2026-48710-badhost/findings.js +8 -4
  22. package/backend/detectors/cve-2026-48710-badhost/index.js +1 -1
  23. package/backend/detectors/cve-2026-48710-badhost/manifest.js +127 -39
  24. package/backend/detectors/cve-2026-48710-badhost/transitive.js +87 -28
  25. package/backend/detectors/hf-impersonation/index.js +94 -31
  26. package/backend/detectors/hf-impersonation/jaro-winkler.js +33 -12
  27. package/backend/detectors/hf-impersonation/known-orgs.js +15 -3
  28. package/backend/detectors/hf-impersonation/simhash.js +2 -2
  29. package/backend/detectors/index.js +181 -34
  30. package/backend/detectors/lib/ast-patterns.js +4 -1
  31. package/backend/detectors/lib/entropy-analyzer.js +12 -4
  32. package/backend/detectors/megalodon/d1-workflow-scan.js +40 -16
  33. package/backend/detectors/megalodon/d2-credential-harvest.js +12 -5
  34. package/backend/detectors/megalodon/d3-publish-velocity.js +17 -11
  35. package/backend/detectors/megalodon/d4-publisher-drift.js +48 -16
  36. package/backend/detectors/megalodon/d5-bot-commit-identity.js +1 -1
  37. package/backend/detectors/megalodon/d6-date-anachronism.js +1 -1
  38. package/backend/detectors/megalodon/index.js +35 -25
  39. package/backend/detectors/mini-shai-hulud/d1-burst-publish.js +3 -1
  40. package/backend/detectors/mini-shai-hulud/d2-sibling-compromise.js +22 -10
  41. package/backend/detectors/mini-shai-hulud/d3-slsa-mismatch.js +30 -10
  42. package/backend/detectors/mini-shai-hulud/d4-maintainer-anomaly.js +17 -13
  43. package/backend/detectors/mini-shai-hulud/d5-ioc-check.js +12 -4
  44. package/backend/detectors/mini-shai-hulud/d6-token-exfil.js +6 -2
  45. package/backend/detectors/mini-shai-hulud/index.js +63 -26
  46. package/backend/detectors/msh-supplement/d2-persistence.js +30 -12
  47. package/backend/detectors/msh-supplement/d3-geo-killswitch.js +20 -8
  48. package/backend/detectors/msh-supplement/d4-c2-deaddrop.js +19 -5
  49. package/backend/detectors/msh-supplement/index.js +78 -63
  50. package/backend/detectors/node-ipc-compromise/d1-version-blocklist.js +4 -2
  51. package/backend/detectors/node-ipc-compromise/d10-unauthorized-publisher.js +9 -5
  52. package/backend/detectors/node-ipc-compromise/d11-blast-radius.js +7 -3
  53. package/backend/detectors/node-ipc-compromise/d2-tarball-hash.js +9 -4
  54. package/backend/detectors/node-ipc-compromise/d3-cjs-payload-injection.js +7 -5
  55. package/backend/detectors/node-ipc-compromise/d4-injected-payload-hash.js +4 -2
  56. package/backend/detectors/node-ipc-compromise/d5-dns-c2-pattern.js +13 -10
  57. package/backend/detectors/node-ipc-compromise/d7-dns-txt-exfil.js +3 -1
  58. package/backend/detectors/node-ipc-compromise/d8-runtime-trigger.js +5 -2
  59. package/backend/detectors/node-ipc-compromise/index.js +21 -15
  60. package/backend/detectors/tier1-binary-embed.js +109 -41
  61. package/backend/detectors/tier1-cloud-imds.js +57 -37
  62. package/backend/detectors/tier1-encrypted-c2.js +198 -0
  63. package/backend/detectors/tier1-infostealer.js +121 -68
  64. package/backend/detectors/tier1-lifecycle-hook.js +63 -23
  65. package/backend/detectors/tier1-maintainer-compromise.js +157 -0
  66. package/backend/detectors/tier1-metadata-spoof.js +92 -42
  67. package/backend/detectors/tier1-multistage-postinstall.js +46 -19
  68. package/backend/detectors/tier1-obfuscation-heuristics.js +45 -17
  69. package/backend/detectors/tier1-self-propagation.js +115 -0
  70. package/backend/detectors/tier1-slsa-attestation.js +1 -1
  71. package/backend/detectors/tier1-transitive-deps.js +182 -0
  72. package/backend/detectors/tier1-typosquat.js +129 -50
  73. package/backend/detectors/tier1-version-anomaly.js +77 -41
  74. package/backend/detectors/tier1-version-confusion.js +79 -59
  75. package/backend/detectors/trapdoor/d1-campaign-marker.js +3 -1
  76. package/backend/detectors/trapdoor/d2-payload-fingerprint.js +1 -1
  77. package/backend/detectors/trapdoor/d3-publisher-blocklist.js +4 -3
  78. package/backend/detectors/trapdoor/d4-gists-exfil.js +4 -2
  79. package/backend/detectors/trapdoor/d5-ai-poisoning.js +5 -3
  80. package/backend/detectors/trapdoor/d6-lure-name.js +12 -7
  81. package/backend/detectors/trapdoor/d7-crypto-primitives.js +2 -2
  82. package/backend/detectors/trapdoor/d8-xor-key.js +7 -2
  83. package/backend/detectors/trapdoor/d9-cred-validation.js +4 -5
  84. package/backend/detectors/trapdoor/index.js +19 -14
  85. package/backend/detectors/typosquat-vpmdhaj/d1-maintainer.js +32 -8
  86. package/backend/detectors/typosquat-vpmdhaj/d2-preinstall-loader.js +5 -3
  87. package/backend/detectors/typosquat-vpmdhaj/d3-cred-exfil.js +34 -12
  88. package/backend/detectors/typosquat-vpmdhaj/index.js +78 -59
  89. package/backend/detectors.test.js +78 -19
  90. package/backend/fetch.js +37 -29
  91. package/backend/index.js +1 -1
  92. package/backend/license.js +20 -4
  93. package/backend/lockfile.js +60 -36
  94. package/backend/pdf.js +107 -28
  95. package/backend/policy.js +183 -56
  96. package/backend/provenance.js +28 -3
  97. package/backend/report.js +136 -70
  98. package/backend/sbom.js +33 -27
  99. package/backend/scripts/analyze-false-positives.js +14 -8
  100. package/backend/scripts/analyze-validation.js +27 -21
  101. package/backend/scripts/detect-false-positives.js +20 -10
  102. package/backend/scripts/fetch-top-packages.js +197 -49
  103. package/backend/scripts/validate-d10-d13.js +103 -0
  104. package/backend/scripts/validate-detectors.js +26 -17
  105. package/backend/siem/cef.js +23 -21
  106. package/backend/siem/ecs.js +3 -3
  107. package/backend/siem/index.js +1 -1
  108. package/backend/siem/qradar.js +3 -3
  109. package/backend/siem/sentinel.js +2 -2
  110. package/backend/tests-d5-enhanced.test.js +13 -12
  111. package/backend/tests-d6-version-anomaly.test.js +17 -8
  112. package/backend/tests-d6.test.js +24 -14
  113. package/backend/tests-d6c.test.js +27 -14
  114. package/backend/tests-d7-obfuscation.test.js +9 -12
  115. package/backend/tests.test.js +182 -83
  116. package/backend/vsix-scan/detectors/activation-event-risk.js +36 -19
  117. package/backend/vsix-scan/detectors/burst-publish.js +14 -7
  118. package/backend/vsix-scan/detectors/exfil-pattern.js +7 -3
  119. package/backend/vsix-scan/detectors/known-ioc.js +23 -8
  120. package/backend/vsix-scan/detectors/orphan-commit-fetch.js +11 -7
  121. package/backend/vsix-scan/detectors/publisher-anomaly.js +24 -10
  122. package/backend/vsix-scan/index.js +97 -41
  123. package/backend/vsix-scan/marketplace-client.js +29 -13
  124. package/cli/cli.js +154 -64
  125. package/package.json +12 -3
package/backend/sbom.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export function generateSBOM(pkgJson, findings, format = 'json') {
2
- if (format === 'spdx') return generateSPDX(pkgJson, findings);
2
+ if (format === 'spdx') {
3
+ return generateSPDX(pkgJson, findings);
4
+ }
3
5
  return generateCycloneDX(pkgJson, findings);
4
6
  }
5
7
 
@@ -13,20 +15,20 @@ function generateCycloneDX(pkgJson, findings) {
13
15
  type: 'library',
14
16
  name: pkgJson.name || 'unknown',
15
17
  version: pkgJson.version || 'unknown',
16
- purl: `pkg:npm/${pkgJson.name || 'unknown'}@${pkgJson.version || 'unknown'}`
18
+ purl: `pkg:npm/${pkgJson.name || 'unknown'}@${pkgJson.version || 'unknown'}`,
17
19
  },
18
- tools: [{ name: 'npm-scan', version: process.env.npm_package_version || '0.3.2' }]
20
+ tools: [{ name: 'npm-scan', version: process.env.npm_package_version || '0.3.2' }],
19
21
  },
20
- vulnerabilities: findings.map(f => {
22
+ vulnerabilities: findings.map((f) => {
21
23
  const atkId = f.atk_id || f.id;
22
24
  return {
23
- id: atkId,
24
- source: { name: 'npm-scan' },
25
- ratings: [{ severity: f.severity }],
26
- description: f.description || f.title || '',
27
- recommendation: f.mitigation || 'Review evidence'
25
+ id: atkId,
26
+ source: { name: 'npm-scan' },
27
+ ratings: [{ severity: f.severity }],
28
+ description: f.description || f.title || '',
29
+ recommendation: f.mitigation || 'Review evidence',
28
30
  };
29
- })
31
+ }),
30
32
  };
31
33
  return JSON.stringify(bom, null, 2);
32
34
  }
@@ -42,26 +44,30 @@ function generateSPDX(pkgJson, findings) {
42
44
  documentNamespace: `https://npm-scan.io/spdx/${pkgName}-${pkgVer}`,
43
45
  creationInfo: {
44
46
  creators: ['Tool: npm-scan'],
45
- created: new Date().toISOString()
47
+ created: new Date().toISOString(),
46
48
  },
47
- packages: [{
48
- SPDXID: 'SPDXRef-Package',
49
- name: pkgName,
50
- versionInfo: pkgVer,
51
- packageFileName: `pkg:npm/${pkgName}@${pkgVer}`,
52
- primaryPackagePurpose: 'LIBRARY',
53
- externalRefs: [{
54
- referenceCategory: 'PACKAGE-MANAGER',
55
- referenceType: 'purl',
56
- referenceLocator: `pkg:npm/${pkgName}@${pkgVer}`
57
- }]
58
- }],
59
- annotations: findings.map(f => ({
49
+ packages: [
50
+ {
51
+ SPDXID: 'SPDXRef-Package',
52
+ name: pkgName,
53
+ versionInfo: pkgVer,
54
+ packageFileName: `pkg:npm/${pkgName}@${pkgVer}`,
55
+ primaryPackagePurpose: 'LIBRARY',
56
+ externalRefs: [
57
+ {
58
+ referenceCategory: 'PACKAGE-MANAGER',
59
+ referenceType: 'purl',
60
+ referenceLocator: `pkg:npm/${pkgName}@${pkgVer}`,
61
+ },
62
+ ],
63
+ },
64
+ ],
65
+ annotations: findings.map((f) => ({
60
66
  annotationDate: new Date().toISOString(),
61
67
  annotationType: 'OTHER',
62
68
  annotator: 'Tool: npm-scan',
63
- comment: `[${f.atk_id || f.id}] ${f.severity.toUpperCase()}: ${f.description || f.title || ''}`
64
- }))
69
+ comment: `[${f.atk_id || f.id}] ${f.severity.toUpperCase()}: ${f.description || f.title || ''}`,
70
+ })),
65
71
  };
66
72
  return JSON.stringify(spdx, null, 2);
67
- }
73
+ }
@@ -20,7 +20,7 @@ function analyzeFalsePositives(fpFile) {
20
20
  }
21
21
 
22
22
  const text = readFileSync(absPath, 'utf-8');
23
- const lines = text.split('\n').filter(l => l.trim());
23
+ const lines = text.split('\n').filter((l) => l.trim());
24
24
 
25
25
  for (const line of lines) {
26
26
  const fp = JSON.parse(line);
@@ -63,13 +63,14 @@ function analyzeFalsePositives(fpFile) {
63
63
  }
64
64
 
65
65
  for (const [detectorName, stats] of Object.entries(analysis.detectors)) {
66
- stats.avg_confidence = stats.confidences.length > 0
67
- ? (stats.confidences.reduce((a, b) => a + b, 0) / stats.confidences.length).toFixed(1)
68
- : '0.0';
66
+ stats.avg_confidence =
67
+ stats.confidences.length > 0
68
+ ? (stats.confidences.reduce((a, b) => a + b, 0) / stats.confidences.length).toFixed(1)
69
+ : '0.0';
69
70
  stats.unique_package_count = stats.unique_packages.size;
70
71
  delete stats.unique_packages;
71
72
 
72
- const fpShare = (stats.fp_count / analysis.total_fps * 100).toFixed(1);
73
+ const fpShare = ((stats.fp_count / analysis.total_fps) * 100).toFixed(1);
73
74
 
74
75
  if (stats.fp_count >= 5) {
75
76
  analysis.high_fp_detectors.push(detectorName);
@@ -114,10 +115,13 @@ for (const [name, stats] of Object.entries(analysis.detectors).sort(
114
115
  (a, b) => b[1].fp_count - a[1].fp_count
115
116
  )) {
116
117
  const dName = name.padEnd(32).slice(0, 32);
117
- const examples = stats.examples.slice(0, 2).map(e => e.package).join(', ');
118
+ const examples = stats.examples
119
+ .slice(0, 2)
120
+ .map((e) => e.package)
121
+ .join(', ');
118
122
  console.log(
119
123
  `${dName} ${String(stats.fp_count).padStart(4)} ${String(stats.unique_package_count).padStart(11)} ` +
120
- `${stats.avg_confidence.padStart(7)} ${examples}`
124
+ `${stats.avg_confidence.padStart(7)} ${examples}`
121
125
  );
122
126
  }
123
127
 
@@ -125,7 +129,9 @@ if (analysis.recommendations.length > 0) {
125
129
  console.log('\n=== RECOMMENDATIONS ===');
126
130
  for (const rec of analysis.recommendations) {
127
131
  console.log(`\n${rec.detector}:`);
128
- console.log(` FPs: ${rec.fp_count} (${rec.share_of_total_fps} of total) across ${rec.unique_packages} unique packages`);
132
+ console.log(
133
+ ` FPs: ${rec.fp_count} (${rec.share_of_total_fps} of total) across ${rec.unique_packages} unique packages`
134
+ );
129
135
  console.log(` Avg confidence: ${rec.avg_confidence}`);
130
136
  console.log(` Severity breakdown: ${JSON.stringify(rec.severity_distribution)}`);
131
137
  console.log(` Suggestion: ${rec.suggested_action}`);
@@ -20,11 +20,13 @@ async function analyzeValidation(resultsFile) {
20
20
  }
21
21
 
22
22
  const text = readFileSync(absPath, 'utf-8');
23
- const lines = text.split('\n').filter(l => l.trim());
23
+ const lines = text.split('\n').filter((l) => l.trim());
24
24
 
25
25
  for (const line of lines) {
26
26
  const result = JSON.parse(line);
27
- if (result.error) continue;
27
+ if (result.error) {
28
+ continue;
29
+ }
28
30
 
29
31
  stats.total_packages += 1;
30
32
  stats.total_detections += result.detection_count;
@@ -47,8 +49,8 @@ async function analyzeValidation(resultsFile) {
47
49
  campaign.total += 1;
48
50
  campaign.total_expected += result.expected_detectors.length;
49
51
 
50
- const matched = result.expected_detectors.filter(
51
- id => result.detected_detectors.includes(id)
52
+ const matched = result.expected_detectors.filter((id) =>
53
+ result.detected_detectors.includes(id)
52
54
  );
53
55
  campaign.total_matched += matched.length;
54
56
  stats.total_expected += result.expected_detectors.length;
@@ -90,22 +92,24 @@ async function analyzeValidation(resultsFile) {
90
92
 
91
93
  for (const campaignId of Object.keys(stats.campaigns)) {
92
94
  const campaign = stats.campaigns[campaignId];
93
- campaign.detection_rate = campaign.total > 0
94
- ? ((campaign.detected / campaign.total) * 100).toFixed(1) + '%'
95
- : '0%';
96
- campaign.expected_match_rate = campaign.total_expected > 0
97
- ? ((campaign.total_matched / campaign.total_expected) * 100).toFixed(1) + '%'
98
- : '0%';
95
+ campaign.detection_rate =
96
+ campaign.total > 0 ? ((campaign.detected / campaign.total) * 100).toFixed(1) + '%' : '0%';
97
+ campaign.expected_match_rate =
98
+ campaign.total_expected > 0
99
+ ? ((campaign.total_matched / campaign.total_expected) * 100).toFixed(1) + '%'
100
+ : '0%';
99
101
  }
100
102
 
101
103
  for (const detectorName of Object.keys(stats.detectors)) {
102
104
  const detector = stats.detectors[detectorName];
103
- detector.avg_confidence = detector.confidences.length > 0
104
- ? (detector.confidences.reduce((a, b) => a + b, 0) / detector.confidences.length).toFixed(1)
105
- : '0.0';
106
- detector.precision = detector.total_hits > 0
107
- ? ((detector.expected_count / detector.total_hits) * 100).toFixed(1) + '%'
108
- : '0%';
105
+ detector.avg_confidence =
106
+ detector.confidences.length > 0
107
+ ? (detector.confidences.reduce((a, b) => a + b, 0) / detector.confidences.length).toFixed(1)
108
+ : '0.0';
109
+ detector.precision =
110
+ detector.total_hits > 0
111
+ ? ((detector.expected_count / detector.total_hits) * 100).toFixed(1) + '%'
112
+ : '0%';
109
113
  }
110
114
 
111
115
  return stats;
@@ -117,14 +121,16 @@ console.log(`[INFO] Analyzing ${resultsFile}...`);
117
121
  const stats = await analyzeValidation(resultsFile);
118
122
 
119
123
  console.log('\n=== CAMPAIGN DETECTION RATES ===');
120
- console.log('Campaign Packages Detected Rate Expected Matched Match%');
124
+ console.log(
125
+ 'Campaign Packages Detected Rate Expected Matched Match%'
126
+ );
121
127
  console.log('─'.repeat(95));
122
- for (const [id, campaign] of Object.entries(stats.campaigns)) {
128
+ for (const [_id, campaign] of Object.entries(stats.campaigns)) {
123
129
  const name = campaign.name.padEnd(33).slice(0, 33);
124
130
  console.log(
125
131
  `${name} ${String(campaign.total).padStart(8)} ${String(campaign.detected).padStart(9)} ` +
126
- `${campaign.detection_rate.padStart(7)} ${String(campaign.total_expected).padStart(9)} ` +
127
- `${String(campaign.total_matched).padStart(8)} ${campaign.expected_match_rate.padStart(7)}`
132
+ `${campaign.detection_rate.padStart(7)} ${String(campaign.total_expected).padStart(9)} ` +
133
+ `${String(campaign.total_matched).padStart(8)} ${campaign.expected_match_rate.padStart(7)}`
128
134
  );
129
135
  }
130
136
  console.log(`\nTotal: ${stats.total_packages} packages, ${stats.total_detections} detections`);
@@ -138,7 +144,7 @@ for (const [name, detector] of Object.entries(stats.detectors).sort(
138
144
  const dName = name.padEnd(32).slice(0, 32);
139
145
  console.log(
140
146
  `${dName} ${String(detector.total_hits).padStart(5)} ${String(detector.expected_count).padStart(9)} ` +
141
- `${detector.precision.padStart(10)} ${detector.avg_confidence.padStart(14)}`
147
+ `${detector.precision.padStart(10)} ${detector.avg_confidence.padStart(14)}`
142
148
  );
143
149
  }
144
150
 
@@ -18,7 +18,7 @@ async function detectFalsePositives(topPackagesFile, confidenceThreshold = 70) {
18
18
  }
19
19
 
20
20
  const text = readFileSync(absPath, 'utf-8');
21
- const lines = text.split('\n').filter(l => l.trim());
21
+ const lines = text.split('\n').filter((l) => l.trim());
22
22
  console.log(`[INFO] Loaded ${lines.length} packages from ${topPackagesFile}`);
23
23
 
24
24
  const falsePositives = [];
@@ -48,8 +48,12 @@ async function detectFalsePositives(topPackagesFile, confidenceThreshold = 70) {
48
48
  const findings = await runAll(pkgJson, [], null, []);
49
49
 
50
50
  for (const detection of findings) {
51
- if (detection.confidenceScore < confidenceThreshold) continue;
52
- if (whitelistedDetectors && whitelistedDetectors.has(detection.id)) continue;
51
+ if (detection.confidenceScore < confidenceThreshold) {
52
+ continue;
53
+ }
54
+ if (whitelistedDetectors && whitelistedDetectors.has(detection.id)) {
55
+ continue;
56
+ }
53
57
 
54
58
  falsePositives.push({
55
59
  package: pkgName,
@@ -64,7 +68,9 @@ async function detectFalsePositives(topPackagesFile, confidenceThreshold = 70) {
64
68
  });
65
69
 
66
70
  if (falsePositives.length <= 10) {
67
- console.log(`[FLAG] ${pkgName}@${pkg.version}: ${detection.id} (${detection.confidenceScore}%)`);
71
+ console.log(
72
+ `[FLAG] ${pkgName}@${pkg.version}: ${detection.id} (${detection.confidenceScore}%)`
73
+ );
68
74
  }
69
75
  }
70
76
  } catch (err) {
@@ -73,12 +79,14 @@ async function detectFalsePositives(topPackagesFile, confidenceThreshold = 70) {
73
79
  }
74
80
 
75
81
  const outPath = resolve('false-positives.jsonl');
76
- const outputData = falsePositives.map(fp => JSON.stringify(fp)).join('\n') + '\n';
82
+ const outputData = falsePositives.map((fp) => JSON.stringify(fp)).join('\n') + '\n';
77
83
  writeFileSync(outPath, outputData, 'utf-8');
78
84
 
79
85
  const scannedCount = count - skipped;
80
86
  console.log(`\n[SUMMARY] Scanned ${scannedCount} packages (skipped ${skipped} whitelisted)`);
81
- console.log(`[SUMMARY] Found ${falsePositives.length} potential false positives (${(falsePositives.length / scannedCount * 100).toFixed(1)}% FP rate)`);
87
+ console.log(
88
+ `[SUMMARY] Found ${falsePositives.length} potential false positives (${((falsePositives.length / scannedCount) * 100).toFixed(1)}% FP rate)`
89
+ );
82
90
  console.log(`[INFO] Written to ${outPath}`);
83
91
 
84
92
  return falsePositives;
@@ -87,7 +95,9 @@ async function detectFalsePositives(topPackagesFile, confidenceThreshold = 70) {
87
95
  const topPackagesFile = process.argv[2] || 'top-packages.jsonl';
88
96
  const confidenceThreshold = parseInt(process.argv[3]) || 70;
89
97
 
90
- detectFalsePositives(topPackagesFile, confidenceThreshold).then(() => process.exit(0)).catch(err => {
91
- console.error(`[FATAL] ${err.message}`);
92
- process.exit(1);
93
- });
98
+ detectFalsePositives(topPackagesFile, confidenceThreshold)
99
+ .then(() => process.exit(0))
100
+ .catch((err) => {
101
+ console.error(`[FATAL] ${err.message}`);
102
+ process.exit(1);
103
+ });
@@ -26,7 +26,9 @@ async function fetchTopPackages(limit = 1000) {
26
26
  const data = await response.json();
27
27
 
28
28
  for (const result of data.results || []) {
29
- if (packages.length >= limit) break;
29
+ if (packages.length >= limit) {
30
+ break;
31
+ }
30
32
  packages.push({
31
33
  name: result.package.name,
32
34
  version: result.package.version,
@@ -34,12 +36,14 @@ async function fetchTopPackages(limit = 1000) {
34
36
  keywords: result.package.keywords || [],
35
37
  publisher: result.package.publisher ? result.package.publisher.username : null,
36
38
  date: result.package.date,
37
- score: result.score ? {
38
- final: result.score.final,
39
- quality: result.score.detail?.quality,
40
- popularity: result.score.detail?.popularity,
41
- maintenance: result.score.detail?.maintenance,
42
- } : null,
39
+ score: result.score
40
+ ? {
41
+ final: result.score.final,
42
+ quality: result.score.detail?.quality,
43
+ popularity: result.score.detail?.popularity,
44
+ maintenance: result.score.detail?.maintenance,
45
+ }
46
+ : null,
43
47
  });
44
48
  }
45
49
 
@@ -48,10 +52,12 @@ async function fetchTopPackages(limit = 1000) {
48
52
  console.error(`[ERROR] Failed page ${page + 1}: ${err.message}`);
49
53
  }
50
54
 
51
- if (packages.length >= limit) break;
55
+ if (packages.length >= limit) {
56
+ break;
57
+ }
52
58
 
53
59
  if (page < numPages - 1) {
54
- await new Promise(r => setTimeout(r, 2000));
60
+ await new Promise((r) => setTimeout(r, 2000));
55
61
  }
56
62
  }
57
63
 
@@ -61,7 +67,7 @@ async function fetchTopPackages(limit = 1000) {
61
67
  }
62
68
 
63
69
  const outPath = resolve('top-packages.jsonl');
64
- const lines = packages.map(pkg => JSON.stringify(pkg)).join('\n') + '\n';
70
+ const lines = packages.map((pkg) => JSON.stringify(pkg)).join('\n') + '\n';
65
71
  writeFileSync(outPath, lines, 'utf-8');
66
72
  console.log(`\n[INFO] Written ${packages.length} packages to ${outPath}`);
67
73
  return packages;
@@ -69,37 +75,177 @@ async function fetchTopPackages(limit = 1000) {
69
75
 
70
76
  async function fallbackList(limit) {
71
77
  const knownTop = [
72
- 'lodash', 'chalk', 'react', 'express', 'commander', 'axios', 'moment',
73
- 'webpack', 'eslint', 'typescript', 'prettier', 'babel', 'next', 'vue',
74
- 'angular', 'redux', 'jest', 'mocha', 'chai', 'sinon', 'nodemon',
75
- 'debug', 'async', 'request', 'colors', 'mkdirp', 'fs-extra', 'glob',
76
- 'yargs', 'minimist', 'uuid', 'date-fns', 'crypto-js', 'jsonwebtoken',
77
- 'passport', 'socket.io', 'ws', 'graphql', 'apollo', 'prisma',
78
- 'mongoose', 'pg', 'mysql2', 'redis', 'sequelize', 'typeorm',
79
- 'dotenv', 'cross-env', 'rimraf', 'semver', 'rimraf', 'tar',
80
- 'inquirer', 'ora', 'listr', 'conf', 'env-paths', 'find-up',
81
- 'p-locate', 'locate-path', 'path-exists', 'y18n', 'yallist',
82
- 'minipass', 'minizlib', 'supports-color', 'has-flag', 'wrap-ansi',
83
- 'string-width', 'strip-ansi', 'ansi-regex', 'is-fullwidth-code-point',
84
- 'emoji-regex', 'cliui', 'escalade', 'get-caller-file', 'require-directory',
85
- 'npm', 'node-fetch', 'got', 'phin', 'undici', 'make-fetch-happen',
86
- 'cacache', 'ssri', 'unique-filename', 'unique-slug', 'imurmurhash',
87
- 'signal-exit', 'which', 'isexe', 'minimatch', 'brace-expansion',
88
- 'balanced-match', 'concat-map', 'lru-cache', 'yallist', 'semver',
89
- 'json5', 'tslib', 'source-map', 'source-map-js', 'ms', 'mime',
90
- 'cookie', 'express-session', 'body-parser', 'cors', 'helmet',
91
- 'morgan', 'compression', 'serve-static', 'send', 'fresh',
92
- 'etag', 'parseurl', 'utils-merge', 'methods', 'array-flatten',
93
- 'qs', 'merge-descriptors', 'path-to-regexp', 'iconv-lite',
94
- 'raw-body', 'on-finished', 'ee-first', 'inherits', 'depd',
95
- 'http-errors', 'statuses', 'setprototypeof', 'toidentifier',
96
- 'content-type', 'negotiator', 'accepts', 'type-is', 'vary',
97
- 'encodeurl', 'escape-html', 'destroy', 'bytes', 'unpipe',
98
- 'finalhandler', 'media-typer', 'http-proxy', 'http-proxy-middleware',
99
- 'morgan', 'connect', 'pino', 'winston', 'bunyan', 'log4js',
100
- 'nanoid', 'uid', 'ulid', 'cuid', 'shortid', 'uuidv4', 'uuidv7',
101
- 'bcrypt', 'bcryptjs', 'argon2', 'scrypt', 'pbkdf2', 'crypto',
102
- 'node-forge', 'pkijs', 'asn1js', 'jsrsasign', 'jose', 'jwk',
78
+ 'lodash',
79
+ 'chalk',
80
+ 'react',
81
+ 'express',
82
+ 'commander',
83
+ 'axios',
84
+ 'moment',
85
+ 'webpack',
86
+ 'eslint',
87
+ 'typescript',
88
+ 'prettier',
89
+ 'babel',
90
+ 'next',
91
+ 'vue',
92
+ 'angular',
93
+ 'redux',
94
+ 'jest',
95
+ 'mocha',
96
+ 'chai',
97
+ 'sinon',
98
+ 'nodemon',
99
+ 'debug',
100
+ 'async',
101
+ 'request',
102
+ 'colors',
103
+ 'mkdirp',
104
+ 'fs-extra',
105
+ 'glob',
106
+ 'yargs',
107
+ 'minimist',
108
+ 'uuid',
109
+ 'date-fns',
110
+ 'crypto-js',
111
+ 'jsonwebtoken',
112
+ 'passport',
113
+ 'socket.io',
114
+ 'ws',
115
+ 'graphql',
116
+ 'apollo',
117
+ 'prisma',
118
+ 'mongoose',
119
+ 'pg',
120
+ 'mysql2',
121
+ 'redis',
122
+ 'sequelize',
123
+ 'typeorm',
124
+ 'dotenv',
125
+ 'cross-env',
126
+ 'rimraf',
127
+ 'semver',
128
+ 'rimraf',
129
+ 'tar',
130
+ 'inquirer',
131
+ 'ora',
132
+ 'listr',
133
+ 'conf',
134
+ 'env-paths',
135
+ 'find-up',
136
+ 'p-locate',
137
+ 'locate-path',
138
+ 'path-exists',
139
+ 'y18n',
140
+ 'yallist',
141
+ 'minipass',
142
+ 'minizlib',
143
+ 'supports-color',
144
+ 'has-flag',
145
+ 'wrap-ansi',
146
+ 'string-width',
147
+ 'strip-ansi',
148
+ 'ansi-regex',
149
+ 'is-fullwidth-code-point',
150
+ 'emoji-regex',
151
+ 'cliui',
152
+ 'escalade',
153
+ 'get-caller-file',
154
+ 'require-directory',
155
+ 'npm',
156
+ 'node-fetch',
157
+ 'got',
158
+ 'phin',
159
+ 'undici',
160
+ 'make-fetch-happen',
161
+ 'cacache',
162
+ 'ssri',
163
+ 'unique-filename',
164
+ 'unique-slug',
165
+ 'imurmurhash',
166
+ 'signal-exit',
167
+ 'which',
168
+ 'isexe',
169
+ 'minimatch',
170
+ 'brace-expansion',
171
+ 'balanced-match',
172
+ 'concat-map',
173
+ 'lru-cache',
174
+ 'yallist',
175
+ 'semver',
176
+ 'json5',
177
+ 'tslib',
178
+ 'source-map',
179
+ 'source-map-js',
180
+ 'ms',
181
+ 'mime',
182
+ 'cookie',
183
+ 'express-session',
184
+ 'body-parser',
185
+ 'cors',
186
+ 'helmet',
187
+ 'morgan',
188
+ 'compression',
189
+ 'serve-static',
190
+ 'send',
191
+ 'fresh',
192
+ 'etag',
193
+ 'parseurl',
194
+ 'utils-merge',
195
+ 'methods',
196
+ 'array-flatten',
197
+ 'qs',
198
+ 'merge-descriptors',
199
+ 'path-to-regexp',
200
+ 'iconv-lite',
201
+ 'raw-body',
202
+ 'on-finished',
203
+ 'ee-first',
204
+ 'inherits',
205
+ 'depd',
206
+ 'http-errors',
207
+ 'statuses',
208
+ 'setprototypeof',
209
+ 'toidentifier',
210
+ 'content-type',
211
+ 'negotiator',
212
+ 'accepts',
213
+ 'type-is',
214
+ 'vary',
215
+ 'encodeurl',
216
+ 'escape-html',
217
+ 'destroy',
218
+ 'bytes',
219
+ 'unpipe',
220
+ 'finalhandler',
221
+ 'media-typer',
222
+ 'http-proxy',
223
+ 'http-proxy-middleware',
224
+ 'morgan',
225
+ 'connect',
226
+ 'pino',
227
+ 'winston',
228
+ 'bunyan',
229
+ 'log4js',
230
+ 'nanoid',
231
+ 'uid',
232
+ 'ulid',
233
+ 'cuid',
234
+ 'shortid',
235
+ 'uuidv4',
236
+ 'uuidv7',
237
+ 'bcrypt',
238
+ 'bcryptjs',
239
+ 'argon2',
240
+ 'scrypt',
241
+ 'pbkdf2',
242
+ 'crypto',
243
+ 'node-forge',
244
+ 'pkijs',
245
+ 'asn1js',
246
+ 'jsrsasign',
247
+ 'jose',
248
+ 'jwk',
103
249
  ];
104
250
  const pkgs = knownTop.slice(0, limit).map((name, i) => ({
105
251
  name,
@@ -113,17 +259,19 @@ async function fallbackList(limit) {
113
259
  console.log(`[FALLBACK] Using ${pkgs.length} known top packages`);
114
260
 
115
261
  const outPath = resolve('top-packages.jsonl');
116
- const lines = pkgs.map(pkg => JSON.stringify(pkg)).join('\n') + '\n';
262
+ const lines = pkgs.map((pkg) => JSON.stringify(pkg)).join('\n') + '\n';
117
263
  writeFileSync(outPath, lines, 'utf-8');
118
264
  console.log(`[INFO] Written ${pkgs.length} packages to ${outPath}`);
119
265
  return pkgs;
120
266
  }
121
267
 
122
268
  const limit = parseInt(process.argv[2]) || 1000;
123
- fetchTopPackages(limit).then(pkgs => {
124
- console.log(`[DONE] ${pkgs.length} packages fetched`);
125
- process.exit(0);
126
- }).catch(err => {
127
- console.error(`[FATAL] ${err.message}`);
128
- process.exit(1);
129
- });
269
+ fetchTopPackages(limit)
270
+ .then((pkgs) => {
271
+ console.log(`[DONE] ${pkgs.length} packages fetched`);
272
+ process.exit(0);
273
+ })
274
+ .catch((err) => {
275
+ console.error(`[FATAL] ${err.message}`);
276
+ process.exit(1);
277
+ });