@cyberhub/shieldpm 0.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -0
  3. package/dist/analyzer/static.d.ts +35 -0
  4. package/dist/analyzer/static.d.ts.map +1 -0
  5. package/dist/analyzer/static.js +416 -0
  6. package/dist/analyzer/static.js.map +1 -0
  7. package/dist/analyzer/typosquat.d.ts +30 -0
  8. package/dist/analyzer/typosquat.d.ts.map +1 -0
  9. package/dist/analyzer/typosquat.js +211 -0
  10. package/dist/analyzer/typosquat.js.map +1 -0
  11. package/dist/cli.d.ts +10 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +621 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/diff/dependency.d.ts +51 -0
  16. package/dist/diff/dependency.d.ts.map +1 -0
  17. package/dist/diff/dependency.js +222 -0
  18. package/dist/diff/dependency.js.map +1 -0
  19. package/dist/fingerprint/profile.d.ts +68 -0
  20. package/dist/fingerprint/profile.d.ts.map +1 -0
  21. package/dist/fingerprint/profile.js +233 -0
  22. package/dist/fingerprint/profile.js.map +1 -0
  23. package/dist/index.d.ts +21 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/monitor/permissions.d.ts +45 -0
  28. package/dist/monitor/permissions.d.ts.map +1 -0
  29. package/dist/monitor/permissions.js +265 -0
  30. package/dist/monitor/permissions.js.map +1 -0
  31. package/dist/sandbox/runner.d.ts +46 -0
  32. package/dist/sandbox/runner.d.ts.map +1 -0
  33. package/dist/sandbox/runner.js +216 -0
  34. package/dist/sandbox/runner.js.map +1 -0
  35. package/dist/utils/colors.d.ts +31 -0
  36. package/dist/utils/colors.d.ts.map +1 -0
  37. package/dist/utils/colors.js +54 -0
  38. package/dist/utils/colors.js.map +1 -0
  39. package/dist/utils/logger.d.ts +26 -0
  40. package/dist/utils/logger.d.ts.map +1 -0
  41. package/dist/utils/logger.js +77 -0
  42. package/dist/utils/logger.js.map +1 -0
  43. package/package.json +24 -0
  44. package/src/analyzer/static.ts +483 -0
  45. package/src/analyzer/typosquat.ts +272 -0
  46. package/src/cli.ts +700 -0
  47. package/src/diff/dependency.ts +297 -0
  48. package/src/fingerprint/profile.ts +333 -0
  49. package/src/index.ts +34 -0
  50. package/src/monitor/permissions.ts +330 -0
  51. package/src/sandbox/runner.ts +302 -0
  52. package/src/utils/colors.ts +58 -0
  53. package/src/utils/logger.ts +87 -0
  54. package/tsconfig.json +19 -0
@@ -0,0 +1,272 @@
1
+ /**
2
+ * ShieldPM — Typosquatting Detector
3
+ * Checks package names against popular npm packages to detect
4
+ * typosquatting attempts using multiple heuristics.
5
+ */
6
+
7
+ // ── Top 50 popular npm packages ──────────────────────────────────────────
8
+
9
+ export const POPULAR_PACKAGES: string[] = [
10
+ 'express', 'lodash', 'react', 'axios', 'chalk', 'commander', 'moment',
11
+ 'debug', 'uuid', 'dotenv', 'typescript', 'webpack', 'eslint', 'prettier',
12
+ 'jest', 'mocha', 'bluebird', 'underscore', 'async', 'request', 'yargs',
13
+ 'inquirer', 'glob', 'minimist', 'semver', 'mkdirp', 'rimraf', 'cheerio',
14
+ 'socket.io', 'mongoose', 'sequelize', 'passport', 'nodemon', 'pm2',
15
+ 'next', 'nuxt', 'vue', 'angular', 'svelte', 'fastify', 'koa', 'hapi',
16
+ 'body-parser', 'cors', 'helmet', 'jsonwebtoken', 'bcrypt', 'sharp',
17
+ 'puppeteer', 'redis',
18
+ ];
19
+
20
+ // ── Types ────────────────────────────────────────────────────────────────
21
+
22
+ export type DetectionMethod =
23
+ | 'levenshtein'
24
+ | 'character-swap'
25
+ | 'hyphen-underscore'
26
+ | 'scope-confusion'
27
+ | 'repeated-character'
28
+ | 'missing-character'
29
+ | 'extra-character';
30
+
31
+ export interface TyposquatResult {
32
+ /** Whether the package name is suspicious */
33
+ isSuspicious: boolean;
34
+ /** The popular package it resembles */
35
+ similarTo: string;
36
+ /** Edit distance or similarity metric */
37
+ distance: number;
38
+ /** How the similarity was detected */
39
+ method: DetectionMethod;
40
+ /** Human-readable explanation */
41
+ reason: string;
42
+ }
43
+
44
+ // ── Levenshtein distance ─────────────────────────────────────────────────
45
+
46
+ export function levenshtein(a: string, b: string): number {
47
+ const m = a.length;
48
+ const n = b.length;
49
+
50
+ if (m === 0) return n;
51
+ if (n === 0) return m;
52
+
53
+ // Use two rows instead of full matrix for memory efficiency
54
+ let prev = new Array<number>(n + 1);
55
+ let curr = new Array<number>(n + 1);
56
+
57
+ for (let j = 0; j <= n; j++) prev[j] = j;
58
+
59
+ for (let i = 1; i <= m; i++) {
60
+ curr[0] = i;
61
+ for (let j = 1; j <= n; j++) {
62
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
63
+ curr[j] = Math.min(
64
+ prev[j] + 1, // deletion
65
+ curr[j - 1] + 1, // insertion
66
+ prev[j - 1] + cost // substitution
67
+ );
68
+ }
69
+ [prev, curr] = [curr, prev];
70
+ }
71
+
72
+ return prev[n];
73
+ }
74
+
75
+ // ── Detection functions ──────────────────────────────────────────────────
76
+
77
+ function normalizePackageName(name: string): { scope: string; base: string } {
78
+ const match = name.match(/^(@[^/]+)\/(.+)$/);
79
+ if (match) {
80
+ return { scope: match[1], base: match[2] };
81
+ }
82
+ return { scope: '', base: name };
83
+ }
84
+
85
+ /**
86
+ * Check for character swaps (transpositions) — e.g. "exprses" vs "express"
87
+ */
88
+ function detectCharacterSwap(input: string, target: string): boolean {
89
+ if (input.length !== target.length) return false;
90
+ if (input === target) return false;
91
+
92
+ let diffs = 0;
93
+ const diffPositions: number[] = [];
94
+
95
+ for (let i = 0; i < input.length; i++) {
96
+ if (input[i] !== target[i]) {
97
+ diffs++;
98
+ diffPositions.push(i);
99
+ }
100
+ }
101
+
102
+ // Exactly two adjacent characters swapped
103
+ if (diffs === 2) {
104
+ const [p1, p2] = diffPositions;
105
+ return (
106
+ p2 - p1 === 1 &&
107
+ input[p1] === target[p2] &&
108
+ input[p2] === target[p1]
109
+ );
110
+ }
111
+
112
+ return false;
113
+ }
114
+
115
+ /**
116
+ * Check for hyphen/underscore confusion — e.g. "lodash" vs "lo-dash", "lo_dash"
117
+ */
118
+ function detectHyphenUnderscore(input: string, target: string): boolean {
119
+ const normalize = (s: string) => s.replace(/[-_.]/g, '');
120
+ return normalize(input) === normalize(target) && input !== target;
121
+ }
122
+
123
+ /**
124
+ * Check for scope confusion — e.g. "@tyeps/react" vs "@types/react"
125
+ */
126
+ function detectScopeConfusion(input: string, knownPackages: string[]): TyposquatResult | null {
127
+ const { scope, base } = normalizePackageName(input);
128
+ if (!scope) return null;
129
+
130
+ // Common scope typos
131
+ const knownScopes = ['@types', '@babel', '@angular', '@vue', '@react-native'];
132
+
133
+ for (const known of knownScopes) {
134
+ if (scope !== known && levenshtein(scope, known) <= 2) {
135
+ const fullKnown = `${known}/${base}`;
136
+ return {
137
+ isSuspicious: true,
138
+ similarTo: fullKnown,
139
+ distance: levenshtein(scope, known),
140
+ method: 'scope-confusion',
141
+ reason: `Scope "${scope}" looks like a typo of "${known}"`,
142
+ };
143
+ }
144
+ }
145
+
146
+ return null;
147
+ }
148
+
149
+ /**
150
+ * Check for repeated characters — e.g. "expresss" vs "express"
151
+ */
152
+ function detectRepeatedChar(input: string, target: string): boolean {
153
+ if (input.length !== target.length + 1) return false;
154
+
155
+ // Try removing each character from input and see if it matches target
156
+ for (let i = 0; i < input.length; i++) {
157
+ const reduced = input.slice(0, i) + input.slice(i + 1);
158
+ if (reduced === target) return true;
159
+ }
160
+ return false;
161
+ }
162
+
163
+ // ── Main detection ───────────────────────────────────────────────────────
164
+
165
+ /**
166
+ * Check a package name for typosquatting against known popular packages.
167
+ * Returns null if the name appears safe.
168
+ */
169
+ export function checkTyposquatting(
170
+ packageName: string,
171
+ knownPackages: string[] = POPULAR_PACKAGES
172
+ ): TyposquatResult | null {
173
+ const { base: inputBase } = normalizePackageName(packageName);
174
+
175
+ // Exact match — it's a real popular package, not suspicious
176
+ if (knownPackages.includes(packageName) || knownPackages.includes(inputBase)) {
177
+ return null;
178
+ }
179
+
180
+ // Check scope confusion first
181
+ const scopeResult = detectScopeConfusion(packageName, knownPackages);
182
+ if (scopeResult) return scopeResult;
183
+
184
+ // Check against each known package
185
+ let bestMatch: TyposquatResult | null = null;
186
+ let bestDistance = Infinity;
187
+
188
+ for (const known of knownPackages) {
189
+ const { base: knownBase } = normalizePackageName(known);
190
+
191
+ // Skip if the lengths are too different for meaningful comparison
192
+ if (Math.abs(inputBase.length - knownBase.length) > 2) continue;
193
+
194
+ // Hyphen/underscore confusion
195
+ if (detectHyphenUnderscore(inputBase, knownBase)) {
196
+ return {
197
+ isSuspicious: true,
198
+ similarTo: known,
199
+ distance: 0,
200
+ method: 'hyphen-underscore',
201
+ reason: `"${packageName}" differs from "${known}" only by hyphen/underscore/dot`,
202
+ };
203
+ }
204
+
205
+ // Character swap
206
+ if (detectCharacterSwap(inputBase, knownBase)) {
207
+ return {
208
+ isSuspicious: true,
209
+ similarTo: known,
210
+ distance: 1,
211
+ method: 'character-swap',
212
+ reason: `"${packageName}" has swapped characters compared to "${known}"`,
213
+ };
214
+ }
215
+
216
+ // Repeated character
217
+ if (detectRepeatedChar(inputBase, knownBase)) {
218
+ return {
219
+ isSuspicious: true,
220
+ similarTo: known,
221
+ distance: 1,
222
+ method: 'repeated-character',
223
+ reason: `"${packageName}" has a repeated character compared to "${known}"`,
224
+ };
225
+ }
226
+
227
+ // Missing character
228
+ if (detectRepeatedChar(knownBase, inputBase)) {
229
+ return {
230
+ isSuspicious: true,
231
+ similarTo: known,
232
+ distance: 1,
233
+ method: 'missing-character',
234
+ reason: `"${packageName}" is missing a character compared to "${known}"`,
235
+ };
236
+ }
237
+
238
+ // General Levenshtein distance
239
+ const dist = levenshtein(inputBase, knownBase);
240
+ if (dist <= 2 && dist < bestDistance) {
241
+ bestDistance = dist;
242
+ bestMatch = {
243
+ isSuspicious: true,
244
+ similarTo: known,
245
+ distance: dist,
246
+ method: 'levenshtein',
247
+ reason: `"${packageName}" is ${dist} edit(s) away from "${known}"`,
248
+ };
249
+ }
250
+ }
251
+
252
+ return bestMatch;
253
+ }
254
+
255
+ /**
256
+ * Batch-check multiple package names.
257
+ */
258
+ export function checkMultiple(
259
+ packageNames: string[],
260
+ knownPackages: string[] = POPULAR_PACKAGES
261
+ ): Map<string, TyposquatResult> {
262
+ const results = new Map<string, TyposquatResult>();
263
+
264
+ for (const name of packageNames) {
265
+ const result = checkTyposquatting(name, knownPackages);
266
+ if (result) {
267
+ results.set(name, result);
268
+ }
269
+ }
270
+
271
+ return results;
272
+ }