@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
@@ -4,23 +4,37 @@ const SENTINEL_EXACT = ['99.99.99'];
4
4
  const SENTINEL_FAMILY = ['9.9.9', '9.9.10', '10.10.10', '11.11.11'];
5
5
 
6
6
  function severityLabel(score) {
7
- if (score >= 80) return 'high';
8
- if (score >= 60) return 'medium';
7
+ if (score >= 80) {
8
+ return 'high';
9
+ }
10
+ if (score >= 60) {
11
+ return 'medium';
12
+ }
9
13
  return 'low';
10
14
  }
11
15
 
12
16
  function confidenceLabel(score) {
13
- if (score >= 80) return 'HIGH';
14
- if (score >= 60) return 'MEDIUM';
17
+ if (score >= 80) {
18
+ return 'HIGH';
19
+ }
20
+ if (score >= 60) {
21
+ return 'MEDIUM';
22
+ }
15
23
  return 'LOW';
16
24
  }
17
25
 
18
26
  function parseVersion(version) {
19
- if (!version || typeof version !== 'string') return null;
27
+ if (!version || typeof version !== 'string') {
28
+ return null;
29
+ }
20
30
  const parts = version.split('.');
21
- if (parts.length !== 3) return null;
31
+ if (parts.length !== 3) {
32
+ return null;
33
+ }
22
34
  const [major, minor, patch] = parts.map(Number);
23
- if (isNaN(major) || isNaN(minor) || isNaN(patch)) return null;
35
+ if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
36
+ return null;
37
+ }
24
38
  return { major, minor, patch };
25
39
  }
26
40
 
@@ -30,77 +44,83 @@ function matchesHeuristic(parsed) {
30
44
 
31
45
  export const name = 'tier1-version-confusion';
32
46
 
33
- export async function scan(pkgJson, jsFiles, registryMeta, allFiles) {
47
+ export async function scan(pkgJson, _jsFiles, _registryMeta, _allFiles) {
34
48
  const pkgName = pkgJson?.name;
35
49
  const version = pkgJson?.version;
36
50
 
37
- if (!pkgName || !version) return [];
38
- if (KNOWN_REPUTABLE_PACKAGES.has(pkgName)) return [];
51
+ if (!pkgName || !version) {
52
+ return [];
53
+ }
54
+ if (KNOWN_REPUTABLE_PACKAGES.has(pkgName)) {
55
+ return [];
56
+ }
39
57
 
40
58
  const parsed = parseVersion(version);
41
- if (!parsed) return [];
59
+ if (!parsed) {
60
+ return [];
61
+ }
42
62
 
43
63
  const vStr = version;
44
64
 
45
65
  // Priority: SENTINEL_EXACT > SENTINEL_FAMILY > HEURISTIC
46
66
  if (SENTINEL_EXACT.includes(vStr)) {
47
67
  const score = 85;
48
- return [{
49
- detector: 'tier1-version-confusion',
50
- id: 'TIER1-VERSION-CONFUSION',
51
- severity: severityLabel(score),
52
- confidence: confidenceLabel(score),
53
- confidenceScore: score,
54
- subtype: 'sentinel_exact',
55
- message: `Package "${pkgName}" uses exact sentinel version ${vStr} — dependency confusion indicator`,
56
- evidence: [
57
- `version: ${vStr}`,
58
- `sentinel: exact match`,
59
- ],
60
- crossFiles: [],
61
- locations: [{ file: 'package.json', line: 3, column: 10 }],
62
- reference: 'Sonatype-2026-003429',
63
- }];
68
+ return [
69
+ {
70
+ detector: 'tier1-version-confusion',
71
+ id: 'TIER1-VERSION-CONFUSION',
72
+ severity: severityLabel(score),
73
+ confidence: confidenceLabel(score),
74
+ confidenceScore: score,
75
+ subtype: 'sentinel_exact',
76
+ message: `Package "${pkgName}" uses exact sentinel version ${vStr} — dependency confusion indicator`,
77
+ evidence: [`version: ${vStr}`, `sentinel: exact match`],
78
+ crossFiles: [],
79
+ locations: [{ file: 'package.json', line: 3, column: 10 }],
80
+ reference: 'Sonatype-2026-003429',
81
+ },
82
+ ];
64
83
  }
65
84
 
66
85
  if (SENTINEL_FAMILY.includes(vStr)) {
67
86
  const score = 65;
68
- return [{
69
- detector: 'tier1-version-confusion',
70
- id: 'TIER1-VERSION-CONFUSION',
71
- severity: severityLabel(score),
72
- confidence: confidenceLabel(score),
73
- confidenceScore: score,
74
- subtype: 'sentinel_family',
75
- message: `Package "${pkgName}" uses sentinel family version ${vStr} — dependency confusion indicator`,
76
- evidence: [
77
- `version: ${vStr}`,
78
- `sentinel: family match`,
79
- ],
80
- crossFiles: [],
81
- locations: [{ file: 'package.json', line: 3, column: 10 }],
82
- reference: 'Sonatype-2026-003429',
83
- }];
87
+ return [
88
+ {
89
+ detector: 'tier1-version-confusion',
90
+ id: 'TIER1-VERSION-CONFUSION',
91
+ severity: severityLabel(score),
92
+ confidence: confidenceLabel(score),
93
+ confidenceScore: score,
94
+ subtype: 'sentinel_family',
95
+ message: `Package "${pkgName}" uses sentinel family version ${vStr} — dependency confusion indicator`,
96
+ evidence: [`version: ${vStr}`, `sentinel: family match`],
97
+ crossFiles: [],
98
+ locations: [{ file: 'package.json', line: 3, column: 10 }],
99
+ reference: 'Sonatype-2026-003429',
100
+ },
101
+ ];
84
102
  }
85
103
 
86
104
  if (matchesHeuristic(parsed)) {
87
105
  const score = 62;
88
- return [{
89
- detector: 'tier1-version-confusion',
90
- id: 'TIER1-VERSION-CONFUSION',
91
- severity: severityLabel(score),
92
- confidence: confidenceLabel(score),
93
- confidenceScore: score,
94
- subtype: 'high_version_heuristic',
95
- message: `Package "${pkgName}" version ${vStr} matches high-version heuristic — possible dependency confusion`,
96
- evidence: [
97
- `version: ${vStr}`,
98
- `major: ${parsed.major}, minor: ${parsed.minor}, patch: ${parsed.patch}`,
99
- ],
100
- crossFiles: [],
101
- locations: [{ file: 'package.json', line: 3, column: 10 }],
102
- reference: 'Microsoft Scope Confusion',
103
- }];
106
+ return [
107
+ {
108
+ detector: 'tier1-version-confusion',
109
+ id: 'TIER1-VERSION-CONFUSION',
110
+ severity: severityLabel(score),
111
+ confidence: confidenceLabel(score),
112
+ confidenceScore: score,
113
+ subtype: 'high_version_heuristic',
114
+ message: `Package "${pkgName}" version ${vStr} matches high-version heuristic — possible dependency confusion`,
115
+ evidence: [
116
+ `version: ${vStr}`,
117
+ `major: ${parsed.major}, minor: ${parsed.minor}, patch: ${parsed.patch}`,
118
+ ],
119
+ crossFiles: [],
120
+ locations: [{ file: 'package.json', line: 3, column: 10 }],
121
+ reference: 'Microsoft Scope Confusion',
122
+ },
123
+ ];
104
124
  }
105
125
 
106
126
  return [];
@@ -10,7 +10,9 @@ export function scanCampaignMarker(allFiles) {
10
10
  const ext = path.includes('.') ? '.' + path.split('.').pop() : '';
11
11
 
12
12
  const isTarget = TARGET_FILENAMES.has(basename) || TARGET_EXTENSIONS.includes(ext);
13
- if (!isTarget) continue;
13
+ if (!isTarget) {
14
+ continue;
15
+ }
14
16
 
15
17
  if (content.includes('P-2024-001')) {
16
18
  matches.push({ file: path });
@@ -12,7 +12,7 @@ export function scanPayloadFingerprint(allFiles) {
12
12
  }
13
13
 
14
14
  if (byteSize === 48485) {
15
- const alreadyMatched = matches.some(m => m.file === path);
15
+ const alreadyMatched = matches.some((m) => m.file === path);
16
16
  if (!alreadyMatched) {
17
17
  matches.push({ file: path, matchType: 'byteSize', byteSize });
18
18
  }
@@ -1,7 +1,8 @@
1
1
  export function scanPublisherBlocklist(pkgJson, registryMeta) {
2
- const publisherAccount = registryMeta?.versions?.[pkgJson?.version]?._npmUser?.name
3
- || registryMeta?.versions?.[Object.keys(registryMeta.versions || {})[0]]?._npmUser?.name
4
- || null;
2
+ const publisherAccount =
3
+ registryMeta?.versions?.[pkgJson?.version]?._npmUser?.name ||
4
+ registryMeta?.versions?.[Object.keys(registryMeta.versions || {})[0]]?._npmUser?.name ||
5
+ null;
5
6
 
6
7
  if (publisherAccount === 'asdxzxc') {
7
8
  return { triggered: true, publisher: publisherAccount };
@@ -3,8 +3,10 @@ const C2_PATTERNS = [/ddjidd564\.github\.io/i, /gist\.github\.com/i];
3
3
 
4
4
  function scanContent(content, filePath) {
5
5
  const matches = [];
6
- const hasC2 = C2_PATTERNS.some(p => p.test(content));
7
- if (!hasC2) return matches;
6
+ const hasC2 = C2_PATTERNS.some((p) => p.test(content));
7
+ if (!hasC2) {
8
+ return matches;
9
+ }
8
10
 
9
11
  const hasCredPath = CRED_PATH_PATTERNS.test(content);
10
12
  if (hasCredPath) {
@@ -1,6 +1,6 @@
1
1
  const ZERO_WIDTH_RANGES = [
2
- [0x200B, 0x200D],
3
- [0xFEFF, 0xFEFF],
2
+ [0x200b, 0x200d],
3
+ [0xfeff, 0xfeff],
4
4
  ];
5
5
 
6
6
  function isZeroWidthChar(code) {
@@ -14,7 +14,9 @@ export function scanAIPoisoning(allFiles) {
14
14
 
15
15
  for (const file of allFiles) {
16
16
  const path = file.path?.replace(/\\/g, '/') || '';
17
- if (!TARGET_FILES.test(path)) continue;
17
+ if (!TARGET_FILES.test(path)) {
18
+ continue;
19
+ }
18
20
 
19
21
  const content = file.content || '';
20
22
  const found = [];
@@ -12,16 +12,21 @@ const LURE_PATTERNS = [
12
12
 
13
13
  export function scanLureName(pkgJson, registryMeta) {
14
14
  const pkgName = pkgJson?.name || '';
15
- const matchedPattern = LURE_PATTERNS.find(p => p.test(pkgName));
16
- if (!matchedPattern) return { triggered: false };
15
+ const matchedPattern = LURE_PATTERNS.find((p) => p.test(pkgName));
16
+ if (!matchedPattern) {
17
+ return { triggered: false };
18
+ }
17
19
 
18
20
  const timeMap = registryMeta?.time || {};
19
- const versions = Object.keys(timeMap).filter(v => v !== 'created' && v !== 'modified');
20
- const firstVersion = versions.length > 0
21
- ? versions.sort((a, b) => new Date(timeMap[a]) - new Date(timeMap[b]))[0]
22
- : null;
21
+ const versions = Object.keys(timeMap).filter((v) => v !== 'created' && v !== 'modified');
22
+ const firstVersion =
23
+ versions.length > 0
24
+ ? versions.sort((a, b) => new Date(timeMap[a]) - new Date(timeMap[b]))[0]
25
+ : null;
23
26
 
24
- if (!firstVersion) return { triggered: false };
27
+ if (!firstVersion) {
28
+ return { triggered: false };
29
+ }
25
30
 
26
31
  const firstPubDate = new Date(timeMap[firstVersion]);
27
32
  const now = new Date();
@@ -7,8 +7,8 @@ export function scanCryptoPrimitives(allFiles, pkgJson) {
7
7
  .map(([hook, content]) => ({ file: `script:${hook}`, content }));
8
8
 
9
9
  const jsFiles = allFiles
10
- .filter(f => f.path?.endsWith('.js') || f.path?.endsWith('.mjs') || f.path?.endsWith('.cjs'))
11
- .map(f => ({ file: f.path, content: f.content || '' }));
10
+ .filter((f) => f.path?.endsWith('.js') || f.path?.endsWith('.mjs') || f.path?.endsWith('.cjs'))
11
+ .map((f) => ({ file: f.path, content: f.content || '' }));
12
12
 
13
13
  for (const { file, content } of [...scriptEntries, ...jsFiles]) {
14
14
  const hasFernet = /Fernet/i.test(content);
@@ -2,9 +2,14 @@ export function scanXorKey(allFiles) {
2
2
  const matches = [];
3
3
  for (const file of allFiles) {
4
4
  const path = file.path?.replace(/\\/g, '/') || '';
5
- const isLockFile = /(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|pnpm-lock\.yml|Cargo\.lock|Cargo\.toml)/i.test(path);
5
+ const isLockFile =
6
+ /(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|pnpm-lock\.yml|Cargo\.lock|Cargo\.toml)/i.test(
7
+ path
8
+ );
6
9
  const isBundled = /\.node$|vendor|native/i.test(path);
7
- if (!isLockFile && !isBundled) continue;
10
+ if (!isLockFile && !isBundled) {
11
+ continue;
12
+ }
8
13
 
9
14
  const content = file.content || '';
10
15
  if (content.includes('cargo-build-helper-2026')) {
@@ -1,7 +1,4 @@
1
- const CRED_VALIDATION_PATTERNS = [
2
- /sts\.amazonaws\.com/i,
3
- /api\.github\.com\/user/i,
4
- ];
1
+ const CRED_VALIDATION_PATTERNS = [/sts\.amazonaws\.com/i, /api\.github\.com\/user/i];
5
2
 
6
3
  export function scanCredValidation(allFiles, pkgJson) {
7
4
  const matches = [];
@@ -19,7 +16,9 @@ export function scanCredValidation(allFiles, pkgJson) {
19
16
 
20
17
  for (const file of allFiles) {
21
18
  const path = file.path || '';
22
- if (!path.endsWith('.js') && !path.endsWith('.mjs') && !path.endsWith('.cjs')) continue;
19
+ if (!path.endsWith('.js') && !path.endsWith('.mjs') && !path.endsWith('.cjs')) {
20
+ continue;
21
+ }
23
22
  const content = file.content || '';
24
23
  for (const pattern of CRED_VALIDATION_PATTERNS) {
25
24
  if (pattern.test(content)) {
@@ -24,7 +24,9 @@ const SEVERITY_ORDER = ['critical', 'high', 'medium', 'low', 'info', 'none'];
24
24
 
25
25
  function highestSeverity(severities) {
26
26
  for (const s of SEVERITY_ORDER) {
27
- if (severities.includes(s)) return s;
27
+ if (severities.includes(s)) {
28
+ return s;
29
+ }
28
30
  }
29
31
  return 'none';
30
32
  }
@@ -48,16 +50,16 @@ export async function scan(pkgJson, files = [], registryMeta = null, allFiles =
48
50
  .filter(([_, r]) => r.triggered)
49
51
  .map(([id]) => id);
50
52
 
51
- if (triggered.length === 0) return [];
53
+ if (triggered.length === 0) {
54
+ return [];
55
+ }
52
56
 
53
- const severity = highestSeverity(triggered.map(id => RULE_SEVERITY[id]));
57
+ const severity = highestSeverity(triggered.map((id) => RULE_SEVERITY[id]));
54
58
 
55
59
  const evidence = {
56
60
  campaign: 'TRAPDOOR',
57
61
  triggeredRules: triggered,
58
- details: Object.fromEntries(
59
- Object.entries(results).filter(([_, r]) => r.triggered)
60
- ),
62
+ details: Object.fromEntries(Object.entries(results).filter(([_, r]) => r.triggered)),
61
63
  iocSummary: {
62
64
  publisher: 'asdxzxc',
63
65
  c2Domain: 'ddjidd564.github.io',
@@ -66,12 +68,15 @@ export async function scan(pkgJson, files = [], registryMeta = null, allFiles =
66
68
  },
67
69
  };
68
70
 
69
- return [{
70
- id: 'TRAPDOOR',
71
- severity,
72
- title: 'TrapDoor cross-ecosystem supply chain attack campaign',
73
- description: `${triggered.length} signal(s): ${triggered.join(', ')}`,
74
- evidence: JSON.stringify(evidence),
75
- mitigation: 'Block install immediately. Revoke any npm tokens associated with this package. Rotate CI/CD secrets. Audit for postinstall scripts accessing credentials. Check for AI config poisoning (.cursorrules/CLAUDE.md). Verify all package versions from publisher asdxzxc. If confirmed compromise, follow incident response procedures per SECURITY.md.',
76
- }];
71
+ return [
72
+ {
73
+ id: 'TRAPDOOR',
74
+ severity,
75
+ title: 'TrapDoor cross-ecosystem supply chain attack campaign',
76
+ description: `${triggered.length} signal(s): ${triggered.join(', ')}`,
77
+ evidence: JSON.stringify(evidence),
78
+ mitigation:
79
+ 'Block install immediately. Revoke any npm tokens associated with this package. Rotate CI/CD secrets. Audit for postinstall scripts accessing credentials. Check for AI config poisoning (.cursorrules/CLAUDE.md). Verify all package versions from publisher asdxzxc. If confirmed compromise, follow incident response procedures per SECURITY.md.',
80
+ },
81
+ ];
77
82
  }
@@ -1,19 +1,37 @@
1
1
  const BLOCKED_MAINTAINERS = ['vpmdhaj'];
2
2
  const VPMDHAJ_PREFIX_RE = /^vpmdhaj-/;
3
3
  const TYPOSQUAT_TARGETS = [
4
- 'opensearch-setup', 'env-config-manager',
5
- 'express', 'lodash', 'axios', 'react', 'vue', 'angular',
6
- 'babel', 'webpack', 'typescript', 'moment', 'dotenv',
4
+ 'opensearch-setup',
5
+ 'env-config-manager',
6
+ 'express',
7
+ 'lodash',
8
+ 'axios',
9
+ 'react',
10
+ 'vue',
11
+ 'angular',
12
+ 'babel',
13
+ 'webpack',
14
+ 'typescript',
15
+ 'moment',
16
+ 'dotenv',
7
17
  ];
8
18
 
9
19
  function levenshteinDistance(a, b) {
10
- const m = a.length, n = b.length;
20
+ const m = a.length,
21
+ n = b.length;
11
22
  const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
12
- for (let i = 0; i <= m; i++) dp[i][0] = i;
13
- for (let j = 0; j <= n; j++) dp[0][j] = j;
23
+ for (let i = 0; i <= m; i++) {
24
+ dp[i][0] = i;
25
+ }
26
+ for (let j = 0; j <= n; j++) {
27
+ dp[0][j] = j;
28
+ }
14
29
  for (let i = 1; i <= m; i++) {
15
30
  for (let j = 1; j <= n; j++) {
16
- dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
31
+ dp[i][j] =
32
+ a[i - 1] === b[j - 1]
33
+ ? dp[i - 1][j - 1]
34
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
17
35
  }
18
36
  }
19
37
  return dp[m][n];
@@ -71,7 +89,13 @@ export function scanMaintainerAnomaly(pkgJson, registryMeta) {
71
89
  };
72
90
  }
73
91
 
74
- return { triggered: false, stopCondition: false, maintainer: '', suspiciousAliases: [], reason: '' };
92
+ return {
93
+ triggered: false,
94
+ stopCondition: false,
95
+ maintainer: '',
96
+ suspiciousAliases: [],
97
+ reason: '',
98
+ };
75
99
  }
76
100
 
77
101
  export { BLOCKED_MAINTAINERS };
@@ -1,8 +1,8 @@
1
1
  const SUSPICIOUS_HOOKS = ['preinstall'];
2
- const LOADER_SCRIPTS = ['setup.mjs', 'loader.js', 'stager.js', 'init.mjs'];
2
+ const _LOADER_SCRIPTS = ['setup.mjs', 'loader.js', 'stager.js', 'init.mjs'];
3
3
  const BUN_RUN_RE = /\bbun\s+run\b/;
4
4
  const NODE_SETUP_RE = /\bnode\s+(setup\.mjs|init\.mjs|loader\.js|stager\.js)\b/;
5
- const PREINSTALL_STAGER_RE = /preinstall\s*[:=]/;
5
+ const _PREINSTALL_STAGER_RE = /preinstall\s*[:=]/;
6
6
 
7
7
  export function scanPreinstallLoader(pkgJson) {
8
8
  const scripts = pkgJson?.scripts || {};
@@ -10,7 +10,9 @@ export function scanPreinstallLoader(pkgJson) {
10
10
 
11
11
  for (const hook of SUSPICIOUS_HOOKS) {
12
12
  const cmd = scripts[hook];
13
- if (!cmd) continue;
13
+ if (!cmd) {
14
+ continue;
15
+ }
14
16
 
15
17
  const details = { hookType: hook, hookCommand: cmd };
16
18
 
@@ -4,49 +4,71 @@ const VAULT_CRED_RE = /VAULT_ADDR|VAULT_TOKEN/;
4
4
  const GITHUB_TOKEN_RE = /GITHUB_TOKEN|GH_TOKEN/;
5
5
  const AWS_ACCESS_KEY_RE = /AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN/;
6
6
  const BASE64_OBFUSCATION_RE = /Buffer\.from\([^)]+['"]base64['"]\)|btoa\(|atob\(/;
7
- const HTTP_POST_EXFIL_RE = /(?:fetch|axios|request|got|curl)\s*\([^)]*(?:https?:\/\/[^'"\s)\]]+)[^)]*(?:method\s*[:=]\s*['"]POST['"]|\.post\s*\()/;
8
- const DOMAIN_EXFIL_RE = /(?:fetch|axios|request|got|curl)\s*\(['"](?:https?:\/\/)?[^'"\s)\]]*\.[^'"\s)\]]{2,}[^)]*\)/;
7
+ const HTTP_POST_EXFIL_RE =
8
+ /(?:fetch|axios|request|got|curl)\s*\([^)]*(?:https?:\/\/[^'"\s)\]]+)[^)]*(?:method\s*[:=]\s*['"]POST['"]|\.post\s*\()/;
9
+ const DOMAIN_EXFIL_RE =
10
+ /(?:fetch|axios|request|got|curl)\s*\(['"](?:https?:\/\/)?[^'"\s)\]]*\.[^'"\s)\]]{2,}[^)]*\)/;
9
11
 
10
12
  const TARGET_ENV_VARS = {
11
- AWS: ['AWS_CONTAINER_CREDENTIALS_FULL_URI', 'AWS_CONTAINER_AUTHORIZATION_TOKEN', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'],
13
+ AWS: [
14
+ 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
15
+ 'AWS_CONTAINER_AUTHORIZATION_TOKEN',
16
+ 'AWS_ACCESS_KEY_ID',
17
+ 'AWS_SECRET_ACCESS_KEY',
18
+ 'AWS_SESSION_TOKEN',
19
+ ],
12
20
  VAULT: ['VAULT_ADDR', 'VAULT_TOKEN'],
13
21
  GITHUB: ['GITHUB_TOKEN', 'GH_TOKEN'],
14
22
  };
15
23
 
16
- export function scanCredExfil(files = [], pkgJson) {
17
- const code = files.map(f => f.content || '').join('\n');
18
- if (!code) return { triggered: false, targets: [], exfilMethod: null, detectedEnvVars: [] };
24
+ export function scanCredExfil(files = [], _pkgJson) {
25
+ const code = files.map((f) => f.content || '').join('\n');
26
+ if (!code) {
27
+ return { triggered: false, targets: [], exfilMethod: null, detectedEnvVars: [] };
28
+ }
19
29
 
20
30
  const targets = [];
21
31
  const detectedEnvVars = [];
22
32
 
23
- if (AWS_IMDS_RE.test(code)) targets.push('AWS_IMDSv2');
33
+ if (AWS_IMDS_RE.test(code)) {
34
+ targets.push('AWS_IMDSv2');
35
+ }
24
36
  if (ECS_CRED_RE.test(code)) {
25
37
  targets.push('ECS_TASK_ROLE');
26
38
  for (const v of TARGET_ENV_VARS.AWS) {
27
- if (code.includes(v)) detectedEnvVars.push(v);
39
+ if (code.includes(v)) {
40
+ detectedEnvVars.push(v);
41
+ }
28
42
  }
29
43
  }
30
44
  if (VAULT_CRED_RE.test(code)) {
31
45
  targets.push('VAULT_CREDENTIALS');
32
46
  for (const v of TARGET_ENV_VARS.VAULT) {
33
- if (code.includes(v)) detectedEnvVars.push(v);
47
+ if (code.includes(v)) {
48
+ detectedEnvVars.push(v);
49
+ }
34
50
  }
35
51
  }
36
52
  if (GITHUB_TOKEN_RE.test(code)) {
37
53
  targets.push('GITHUB_TOKEN');
38
54
  for (const v of TARGET_ENV_VARS.GITHUB) {
39
- if (code.includes(v)) detectedEnvVars.push(v);
55
+ if (code.includes(v)) {
56
+ detectedEnvVars.push(v);
57
+ }
40
58
  }
41
59
  }
42
60
  if (AWS_ACCESS_KEY_RE.test(code)) {
43
61
  targets.push('AWS_ACCESS_KEYS');
44
62
  for (const v of TARGET_ENV_VARS.AWS) {
45
- if (code.includes(v) && !detectedEnvVars.includes(v)) detectedEnvVars.push(v);
63
+ if (code.includes(v) && !detectedEnvVars.includes(v)) {
64
+ detectedEnvVars.push(v);
65
+ }
46
66
  }
47
67
  }
48
68
 
49
- if (targets.length === 0) return { triggered: false, targets: [], exfilMethod: null, detectedEnvVars: [] };
69
+ if (targets.length === 0) {
70
+ return { triggered: false, targets: [], exfilMethod: null, detectedEnvVars: [] };
71
+ }
50
72
 
51
73
  let exfilMethod = null;
52
74
  if (HTTP_POST_EXFIL_RE.test(code)) {