guardrail-security 1.0.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 (95) hide show
  1. package/dist/attack-surface/analyzer.d.ts +50 -0
  2. package/dist/attack-surface/analyzer.d.ts.map +1 -0
  3. package/dist/attack-surface/analyzer.js +83 -0
  4. package/dist/attack-surface/index.d.ts +5 -0
  5. package/dist/attack-surface/index.d.ts.map +1 -0
  6. package/dist/attack-surface/index.js +20 -0
  7. package/dist/index.d.ts +15 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +33 -0
  10. package/dist/languages/index.d.ts +21 -0
  11. package/dist/languages/index.d.ts.map +1 -0
  12. package/dist/languages/index.js +78 -0
  13. package/dist/languages/java-analyzer.d.ts +72 -0
  14. package/dist/languages/java-analyzer.d.ts.map +1 -0
  15. package/dist/languages/java-analyzer.js +417 -0
  16. package/dist/languages/python-analyzer.d.ts +70 -0
  17. package/dist/languages/python-analyzer.d.ts.map +1 -0
  18. package/dist/languages/python-analyzer.js +425 -0
  19. package/dist/license/compatibility-matrix.d.ts +28 -0
  20. package/dist/license/compatibility-matrix.d.ts.map +1 -0
  21. package/dist/license/compatibility-matrix.js +323 -0
  22. package/dist/license/engine.d.ts +77 -0
  23. package/dist/license/engine.d.ts.map +1 -0
  24. package/dist/license/engine.js +264 -0
  25. package/dist/license/index.d.ts +6 -0
  26. package/dist/license/index.d.ts.map +1 -0
  27. package/dist/license/index.js +21 -0
  28. package/dist/sbom/generator.d.ts +108 -0
  29. package/dist/sbom/generator.d.ts.map +1 -0
  30. package/dist/sbom/generator.js +271 -0
  31. package/dist/sbom/index.d.ts +5 -0
  32. package/dist/sbom/index.d.ts.map +1 -0
  33. package/dist/sbom/index.js +20 -0
  34. package/dist/secrets/guardian.d.ts +113 -0
  35. package/dist/secrets/guardian.d.ts.map +1 -0
  36. package/dist/secrets/guardian.js +334 -0
  37. package/dist/secrets/index.d.ts +10 -0
  38. package/dist/secrets/index.d.ts.map +1 -0
  39. package/dist/secrets/index.js +30 -0
  40. package/dist/secrets/patterns.d.ts +42 -0
  41. package/dist/secrets/patterns.d.ts.map +1 -0
  42. package/dist/secrets/patterns.js +165 -0
  43. package/dist/secrets/pre-commit.d.ts +39 -0
  44. package/dist/secrets/pre-commit.d.ts.map +1 -0
  45. package/dist/secrets/pre-commit.js +127 -0
  46. package/dist/secrets/vault-integration.d.ts +83 -0
  47. package/dist/secrets/vault-integration.d.ts.map +1 -0
  48. package/dist/secrets/vault-integration.js +295 -0
  49. package/dist/secrets/vault-providers.d.ts +110 -0
  50. package/dist/secrets/vault-providers.d.ts.map +1 -0
  51. package/dist/secrets/vault-providers.js +417 -0
  52. package/dist/supply-chain/detector.d.ts +80 -0
  53. package/dist/supply-chain/detector.d.ts.map +1 -0
  54. package/dist/supply-chain/detector.js +168 -0
  55. package/dist/supply-chain/index.d.ts +11 -0
  56. package/dist/supply-chain/index.d.ts.map +1 -0
  57. package/dist/supply-chain/index.js +26 -0
  58. package/dist/supply-chain/malicious-db.d.ts +41 -0
  59. package/dist/supply-chain/malicious-db.d.ts.map +1 -0
  60. package/dist/supply-chain/malicious-db.js +82 -0
  61. package/dist/supply-chain/script-analyzer.d.ts +54 -0
  62. package/dist/supply-chain/script-analyzer.d.ts.map +1 -0
  63. package/dist/supply-chain/script-analyzer.js +160 -0
  64. package/dist/supply-chain/typosquat.d.ts +58 -0
  65. package/dist/supply-chain/typosquat.d.ts.map +1 -0
  66. package/dist/supply-chain/typosquat.js +257 -0
  67. package/dist/supply-chain/vulnerability-db.d.ts +114 -0
  68. package/dist/supply-chain/vulnerability-db.d.ts.map +1 -0
  69. package/dist/supply-chain/vulnerability-db.js +310 -0
  70. package/package.json +34 -0
  71. package/src/__tests__/license/engine.test.ts +250 -0
  72. package/src/__tests__/supply-chain/typosquat.test.ts +191 -0
  73. package/src/attack-surface/analyzer.ts +152 -0
  74. package/src/attack-surface/index.ts +5 -0
  75. package/src/index.ts +21 -0
  76. package/src/languages/index.ts +91 -0
  77. package/src/languages/java-analyzer.ts +490 -0
  78. package/src/languages/python-analyzer.ts +498 -0
  79. package/src/license/compatibility-matrix.ts +366 -0
  80. package/src/license/engine.ts +345 -0
  81. package/src/license/index.ts +6 -0
  82. package/src/sbom/generator.ts +355 -0
  83. package/src/sbom/index.ts +5 -0
  84. package/src/secrets/guardian.ts +448 -0
  85. package/src/secrets/index.ts +10 -0
  86. package/src/secrets/patterns.ts +186 -0
  87. package/src/secrets/pre-commit.ts +158 -0
  88. package/src/secrets/vault-integration.ts +360 -0
  89. package/src/secrets/vault-providers.ts +446 -0
  90. package/src/supply-chain/detector.ts +252 -0
  91. package/src/supply-chain/index.ts +11 -0
  92. package/src/supply-chain/malicious-db.ts +103 -0
  93. package/src/supply-chain/script-analyzer.ts +194 -0
  94. package/src/supply-chain/typosquat.ts +302 -0
  95. package/src/supply-chain/vulnerability-db.ts +386 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Malicious Package Database
3
+ *
4
+ * Checks packages against known malicious packages
5
+ */
6
+
7
+ export interface MaliciousPackageInfo {
8
+ name: string;
9
+ version?: string;
10
+ reason: string;
11
+ severity: 'low' | 'medium' | 'high' | 'critical';
12
+ cve?: string;
13
+ reported: Date;
14
+ }
15
+
16
+ /**
17
+ * Known malicious packages (this would be updated regularly from external sources)
18
+ */
19
+ const KNOWN_MALICIOUS: MaliciousPackageInfo[] = [
20
+ // Example entries - in production, this would be fetched from:
21
+ // - npm security advisories
22
+ // - Snyk vulnerability database
23
+ // - GitHub Advisory Database
24
+ // - Custom threat intelligence feeds
25
+ ];
26
+
27
+ export class MaliciousPackageDB {
28
+ private maliciousPackages: Map<string, MaliciousPackageInfo[]> = new Map();
29
+
30
+ constructor() {
31
+ this.loadDatabase();
32
+ }
33
+
34
+ /**
35
+ * Check if package is known to be malicious
36
+ */
37
+ async checkPackage(name: string, version: string): Promise<{
38
+ isMalicious: boolean;
39
+ matches: MaliciousPackageInfo[];
40
+ }> {
41
+ const matches: MaliciousPackageInfo[] = [];
42
+
43
+ // Check exact name match
44
+ const nameMatches = this.maliciousPackages.get(name) || [];
45
+
46
+ for (const match of nameMatches) {
47
+ // If no version specified in DB, flag all versions
48
+ if (!match.version) {
49
+ matches.push(match);
50
+ continue;
51
+ }
52
+
53
+ // Check version match
54
+ if (match.version === version || match.version === '*') {
55
+ matches.push(match);
56
+ }
57
+ }
58
+
59
+ return {
60
+ isMalicious: matches.length > 0,
61
+ matches,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Load malicious packages database
67
+ */
68
+ private loadDatabase(): void {
69
+ for (const pkg of KNOWN_MALICIOUS) {
70
+ if (!this.maliciousPackages.has(pkg.name)) {
71
+ this.maliciousPackages.set(pkg.name, []);
72
+ }
73
+ this.maliciousPackages.get(pkg.name)!.push(pkg);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Update database from external sources
79
+ */
80
+ async updateDatabase(): Promise<{ added: number; updated: number }> {
81
+ // In production, this would:
82
+ // 1. Fetch from npm security advisories API
83
+ // 2. Fetch from Snyk API
84
+ // 3. Fetch from GitHub Advisory Database
85
+ // 4. Merge with existing database
86
+ // 5. Return statistics
87
+
88
+ return { added: 0, updated: 0 };
89
+ }
90
+
91
+ /**
92
+ * Add custom malicious package
93
+ */
94
+ addMaliciousPackage(info: MaliciousPackageInfo): void {
95
+ if (!this.maliciousPackages.has(info.name)) {
96
+ this.maliciousPackages.set(info.name, []);
97
+ }
98
+ this.maliciousPackages.get(info.name)!.push(info);
99
+ }
100
+ }
101
+
102
+ // Export singleton
103
+ export const maliciousPackageDB = new MaliciousPackageDB();
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Script Analyzer
3
+ *
4
+ * Analyzes package.json scripts for suspicious behavior
5
+ */
6
+
7
+ export interface ScriptAnalysisResult {
8
+ scriptName: string;
9
+ scriptContent: string;
10
+ isSuspicious: boolean;
11
+ threats: ScriptThreat[];
12
+ riskScore: number;
13
+ }
14
+
15
+ export interface ScriptThreat {
16
+ type: 'data_exfiltration' | 'crypto_mining' | 'backdoor' | 'malicious_download' | 'privilege_escalation';
17
+ pattern: string;
18
+ severity: 'low' | 'medium' | 'high' | 'critical';
19
+ description: string;
20
+ }
21
+
22
+ export class ScriptAnalyzer {
23
+ /**
24
+ * Analyze package.json scripts
25
+ */
26
+ async analyzeScripts(_packageName: string, _version: string): Promise<ScriptAnalysisResult[]> {
27
+ // In production, this would fetch package.json from npm registry
28
+ // For now, return empty array
29
+ return [];
30
+ }
31
+
32
+ /**
33
+ * Analyze a single script
34
+ */
35
+ analyzeScript(scriptName: string, scriptContent: string): ScriptAnalysisResult {
36
+ const threats: ScriptThreat[] = [];
37
+
38
+ // Check for data exfiltration
39
+ if (this.detectExfiltration(scriptContent)) {
40
+ threats.push({
41
+ type: 'data_exfiltration',
42
+ pattern: 'network_request',
43
+ severity: 'high',
44
+ description: 'Script makes network requests that could exfiltrate data',
45
+ });
46
+ }
47
+
48
+ // Check for crypto mining
49
+ if (this.detectCryptoMining(scriptContent)) {
50
+ threats.push({
51
+ type: 'crypto_mining',
52
+ pattern: 'crypto_miner',
53
+ severity: 'high',
54
+ description: 'Script contains crypto mining code',
55
+ });
56
+ }
57
+
58
+ // Check for backdoors
59
+ if (this.detectBackdoor(scriptContent)) {
60
+ threats.push({
61
+ type: 'backdoor',
62
+ pattern: 'reverse_shell',
63
+ severity: 'critical',
64
+ description: 'Script opens a backdoor or reverse shell',
65
+ });
66
+ }
67
+
68
+ // Check for malicious downloads
69
+ if (this.detectMaliciousDownload(scriptContent)) {
70
+ threats.push({
71
+ type: 'malicious_download',
72
+ pattern: 'download_execute',
73
+ severity: 'critical',
74
+ description: 'Script downloads and executes code',
75
+ });
76
+ }
77
+
78
+ // Check for privilege escalation
79
+ if (this.detectPrivilegeEscalation(scriptContent)) {
80
+ threats.push({
81
+ type: 'privilege_escalation',
82
+ pattern: 'sudo_usage',
83
+ severity: 'high',
84
+ description: 'Script attempts privilege escalation',
85
+ });
86
+ }
87
+
88
+ // Calculate risk score
89
+ const riskScore = this.calculateRiskScore(threats);
90
+
91
+ return {
92
+ scriptName,
93
+ scriptContent,
94
+ isSuspicious: threats.length > 0,
95
+ threats,
96
+ riskScore,
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Detect data exfiltration patterns
102
+ */
103
+ detectExfiltration(script: string): boolean {
104
+ const patterns = [
105
+ /curl\s+.*\|\s*bash/i, // Pipe to bash
106
+ /wget\s+.*\|\s*sh/i, // Pipe to sh
107
+ /fetch\(['"]http/i, // HTTP requests
108
+ /axios\./i, // Axios requests
109
+ /http\.request/i, // HTTP module
110
+ /child_process\.exec.*curl/i, // Execute curl
111
+ ];
112
+
113
+ return patterns.some((p) => p.test(script));
114
+ }
115
+
116
+ /**
117
+ * Detect crypto mining
118
+ */
119
+ detectCryptoMining(script: string): boolean {
120
+ const patterns = [
121
+ /coinhive/i,
122
+ /cryptonight/i,
123
+ /monero/i,
124
+ /xmrig/i,
125
+ /stratum\+tcp/i,
126
+ ];
127
+
128
+ return patterns.some((p) => p.test(script));
129
+ }
130
+
131
+ /**
132
+ * Detect backdoor patterns
133
+ */
134
+ private detectBackdoor(script: string): boolean {
135
+ const patterns = [
136
+ /nc\s+-l/i, // Netcat listener
137
+ /\/bin\/sh\s+-i/i, // Interactive shell
138
+ /bash\s+-i/i, // Interactive bash
139
+ /python.*socket/i, // Python socket
140
+ ];
141
+
142
+ return patterns.some((p) => p.test(script));
143
+ }
144
+
145
+ /**
146
+ * Detect malicious downloads
147
+ */
148
+ private detectMaliciousDownload(script: string): boolean {
149
+ const patterns = [
150
+ /curl.*\|\s*bash/i,
151
+ /wget.*&&.*chmod\s*\+x/i,
152
+ /download.*&&.*execute/i,
153
+ ];
154
+
155
+ return patterns.some((p) => p.test(script));
156
+ }
157
+
158
+ /**
159
+ * Detect privilege escalation
160
+ */
161
+ private detectPrivilegeEscalation(script: string): boolean {
162
+ const patterns = [
163
+ /sudo\s+/i,
164
+ /su\s+-/i,
165
+ /chmod\s+777/i,
166
+ /chown\s+root/i,
167
+ ];
168
+
169
+ return patterns.some((p) => p.test(script));
170
+ }
171
+
172
+ /**
173
+ * Calculate risk score
174
+ */
175
+ private calculateRiskScore(threats: ScriptThreat[]): number {
176
+ const severityScores = {
177
+ low: 25,
178
+ medium: 50,
179
+ high: 75,
180
+ critical: 100,
181
+ };
182
+
183
+ if (threats.length === 0) return 0;
184
+
185
+ const totalScore = threats.reduce((sum, threat) => {
186
+ return sum + severityScores[threat.severity];
187
+ }, 0);
188
+
189
+ return Math.min(100, totalScore / threats.length);
190
+ }
191
+ }
192
+
193
+ // Export singleton
194
+ export const scriptAnalyzer = new ScriptAnalyzer();
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Typosquatting Detection
3
+ *
4
+ * Detects potential typosquatting attacks against popular packages
5
+ */
6
+
7
+ /**
8
+ * Top 100 most popular npm packages (simplified list)
9
+ */
10
+ const POPULAR_PACKAGES = [
11
+ 'react', 'vue', 'angular', 'express', 'next', 'axios', 'lodash', 'webpack',
12
+ 'typescript', 'eslint', 'prettier', 'jest', 'mocha', 'chai', 'babel',
13
+ 'moment', 'dayjs', 'date-fns', 'redux', 'mobx', 'rxjs', 'socket.io',
14
+ 'fastify', 'koa', 'hapi', 'nestjs', 'prisma', 'mongoose', 'sequelize',
15
+ 'typeorm', 'knex', 'pg', 'mysql', 'redis', 'mongodb', 'sqlite3',
16
+ 'passport', 'jsonwebtoken', 'bcrypt', 'crypto-js', 'uuid', 'nanoid',
17
+ 'dotenv', 'config', 'yargs', 'commander', 'inquirer', 'chalk', 'ora',
18
+ 'debug', 'winston', 'pino', 'morgan', 'cors', 'helmet', 'compression',
19
+ 'multer', 'body-parser', 'cookie-parser', 'express-session', 'passport',
20
+ 'nodemailer', 'sendgrid', 'twilio', 'stripe', 'aws-sdk', 'google-cloud',
21
+ 'firebase', 'azure', 'docker', 'kubernetes', 'terraform', 'ansible',
22
+ 'jenkins', 'gitlab', 'github', 'bitbucket', 'jira', 'confluence',
23
+ 'slack', 'discord', 'telegram', 'whatsapp', 'sentry', 'datadog',
24
+ 'newrelic', 'prometheus', 'grafana', 'elasticsearch', 'kibana', 'logstash',
25
+ 'kafka', 'rabbitmq', 'celery', 'bull', 'agenda', 'cron', 'node-schedule',
26
+ ];
27
+
28
+ export interface TyposquatResult {
29
+ isTyposquat: boolean;
30
+ suspiciousPackage: string;
31
+ targetPackage?: string;
32
+ similarity: number;
33
+ patterns: string[];
34
+ }
35
+
36
+ export class TyposquatDetector {
37
+ private popularPackages: Set<string>;
38
+
39
+ constructor() {
40
+ this.popularPackages = new Set(POPULAR_PACKAGES);
41
+ }
42
+
43
+ /**
44
+ * Detect typosquatting
45
+ */
46
+ async detectTyposquatting(packageName: string): Promise<TyposquatResult> {
47
+ const patterns: string[] = [];
48
+ let targetPackage: string | undefined;
49
+ let maxSimilarity = 0;
50
+
51
+ // Check against popular packages
52
+ for (const popular of this.popularPackages) {
53
+ // Skip if exact match
54
+ if (packageName === popular) {
55
+ continue;
56
+ }
57
+
58
+ // Check various typosquatting techniques
59
+ const techniques = [
60
+ this.checkCharacterSwap(packageName, popular),
61
+ this.checkMissingCharacter(packageName, popular),
62
+ this.checkExtraCharacter(packageName, popular),
63
+ this.checkHomoglyph(packageName, popular),
64
+ this.checkCombosquatting(packageName, popular),
65
+ this.checkLevenshtein(packageName, popular),
66
+ ];
67
+
68
+ for (const technique of techniques) {
69
+ if (technique.isMatch) {
70
+ patterns.push(technique.pattern);
71
+
72
+ if (technique.similarity > maxSimilarity) {
73
+ maxSimilarity = technique.similarity;
74
+ targetPackage = popular;
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ return {
81
+ isTyposquat: patterns.length > 0,
82
+ suspiciousPackage: packageName,
83
+ targetPackage,
84
+ similarity: maxSimilarity,
85
+ patterns,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Check for character swap (e.g., raect vs react)
91
+ */
92
+ private checkCharacterSwap(pkg: string, popular: string): { isMatch: boolean; pattern: string; similarity: number } {
93
+ if (Math.abs(pkg.length - popular.length) > 0) {
94
+ return { isMatch: false, pattern: '', similarity: 0 };
95
+ }
96
+
97
+ // Try swapping adjacent characters
98
+ for (let i = 0; i < popular.length - 1; i++) {
99
+ const swapped = popular.substring(0, i) +
100
+ popular[i + 1] +
101
+ popular[i] +
102
+ popular.substring(i + 2);
103
+
104
+ if (swapped === pkg) {
105
+ return {
106
+ isMatch: true,
107
+ pattern: 'character_swap',
108
+ similarity: 0.95,
109
+ };
110
+ }
111
+ }
112
+
113
+ return { isMatch: false, pattern: '', similarity: 0 };
114
+ }
115
+
116
+ /**
117
+ * Check for missing character (e.g., reat vs react)
118
+ */
119
+ private checkMissingCharacter(pkg: string, popular: string): { isMatch: boolean; pattern: string; similarity: number } {
120
+ if (pkg.length !== popular.length - 1) {
121
+ return { isMatch: false, pattern: '', similarity: 0 };
122
+ }
123
+
124
+ // Try removing each character
125
+ for (let i = 0; i < popular.length; i++) {
126
+ const removed = popular.substring(0, i) + popular.substring(i + 1);
127
+
128
+ if (removed === pkg) {
129
+ return {
130
+ isMatch: true,
131
+ pattern: 'missing_character',
132
+ similarity: 0.9,
133
+ };
134
+ }
135
+ }
136
+
137
+ return { isMatch: false, pattern: '', similarity: 0 };
138
+ }
139
+
140
+ /**
141
+ * Check for extra character (e.g., reactt vs react)
142
+ */
143
+ private checkExtraCharacter(pkg: string, popular: string): { isMatch: boolean; pattern: string; similarity: number } {
144
+ if (pkg.length !== popular.length + 1) {
145
+ return { isMatch: false, pattern: '', similarity: 0 };
146
+ }
147
+
148
+ // Try removing each character from pkg
149
+ for (let i = 0; i < pkg.length; i++) {
150
+ const removed = pkg.substring(0, i) + pkg.substring(i + 1);
151
+
152
+ if (removed === popular) {
153
+ return {
154
+ isMatch: true,
155
+ pattern: 'extra_character',
156
+ similarity: 0.9,
157
+ };
158
+ }
159
+ }
160
+
161
+ return { isMatch: false, pattern: '', similarity: 0 };
162
+ }
163
+
164
+ /**
165
+ * Check for homoglyph substitution (e.g., react with Cyrillic 'а')
166
+ */
167
+ private checkHomoglyph(pkg: string, popular: string): { isMatch: boolean; pattern: string; similarity: number } {
168
+ // Common homoglyphs
169
+ const homoglyphs: Record<string, string[]> = {
170
+ 'a': ['а', 'ɑ', 'α'], // Cyrillic/Greek a
171
+ 'e': ['е', ' е'], // Cyrillic e
172
+ 'o': ['о', 'ο', '0'], // Cyrillic/Greek o, zero
173
+ 'i': ['і', 'ı', 'l', '1'], // Cyrillic i, Turkish i, l, one
174
+ 'c': ['с', 'ϲ'], // Cyrillic c
175
+ 'p': ['р'], // Cyrillic p
176
+ 'x': ['х', 'χ'], // Cyrillic/Greek x
177
+ };
178
+
179
+ // Normalize both strings
180
+ const normalize = (str: string): string => {
181
+ let normalized = str;
182
+ for (const [latin, alternates] of Object.entries(homoglyphs)) {
183
+ for (const alt of alternates) {
184
+ normalized = normalized.replace(new RegExp(alt, 'g'), latin);
185
+ }
186
+ }
187
+ return normalized;
188
+ };
189
+
190
+ const normalizedPkg = normalize(pkg);
191
+
192
+ if (normalizedPkg === popular && normalizedPkg !== pkg) {
193
+ return {
194
+ isMatch: true,
195
+ pattern: 'homoglyph',
196
+ similarity: 0.95,
197
+ };
198
+ }
199
+
200
+ return { isMatch: false, pattern: '', similarity: 0 };
201
+ }
202
+
203
+ /**
204
+ * Check for combosquatting (e.g., react-native-safe vs react)
205
+ */
206
+ private checkCombosquatting(pkg: string, popular: string): { isMatch: boolean; pattern: string; similarity: number } {
207
+ if (pkg.includes(popular) && pkg !== popular) {
208
+ // Check if it's just adding common suffixes/prefixes
209
+ const commonAdditions = ['-js', '-node', '-utils', '-core', '-plugin', '-webpack', '-babel'];
210
+
211
+ for (const addition of commonAdditions) {
212
+ if (pkg === popular + addition || pkg === addition + popular) {
213
+ return {
214
+ isMatch: true,
215
+ pattern: 'combosquatting',
216
+ similarity: 0.7,
217
+ };
218
+ }
219
+ }
220
+ }
221
+
222
+ return { isMatch: false, pattern: '', similarity: 0 };
223
+ }
224
+
225
+ /**
226
+ * Check Levenshtein distance
227
+ */
228
+ private checkLevenshtein(pkg: string, popular: string): { isMatch: boolean; pattern: string; similarity: number } {
229
+ const distance = this.levenshteinDistance(pkg, popular);
230
+ const maxLength = Math.max(pkg.length, popular.length);
231
+ const similarity = 1 - (distance / maxLength);
232
+
233
+ // Consider it suspicious if similarity > 0.8
234
+ if (similarity >= 0.8 && similarity < 1.0) {
235
+ return {
236
+ isMatch: true,
237
+ pattern: 'levenshtein_distance',
238
+ similarity,
239
+ };
240
+ }
241
+
242
+ return { isMatch: false, pattern: '', similarity: 0 };
243
+ }
244
+
245
+ /**
246
+ * Calculate Levenshtein distance
247
+ */
248
+ levenshteinDistance(a: string, b: string): number {
249
+ const matrix: number[][] = [];
250
+
251
+ // Initialize matrix with proper dimensions
252
+ for (let i = 0; i <= b.length; i++) {
253
+ matrix[i] = [];
254
+ for (let j = 0; j <= a.length; j++) {
255
+ matrix[i]![j] = 0;
256
+ }
257
+ }
258
+
259
+ // Set first column
260
+ for (let i = 0; i <= b.length; i++) {
261
+ matrix[i]![0] = i;
262
+ }
263
+
264
+ // Set first row
265
+ for (let j = 0; j <= a.length; j++) {
266
+ matrix[0]![j] = j;
267
+ }
268
+
269
+ for (let i = 1; i <= b.length; i++) {
270
+ for (let j = 1; j <= a.length; j++) {
271
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
272
+ matrix[i]![j] = matrix[i - 1]![j - 1]!;
273
+ } else {
274
+ matrix[i]![j] = Math.min(
275
+ matrix[i - 1]![j - 1]! + 1, // substitution
276
+ matrix[i]![j - 1]! + 1, // insertion
277
+ matrix[i - 1]![j]! + 1 // deletion
278
+ );
279
+ }
280
+ }
281
+ }
282
+
283
+ return matrix[b.length]![a.length]!;
284
+ }
285
+
286
+ /**
287
+ * Get popular packages list
288
+ */
289
+ async getPopularPackages(): Promise<string[]> {
290
+ return Array.from(this.popularPackages);
291
+ }
292
+
293
+ /**
294
+ * Add custom popular package
295
+ */
296
+ addPopularPackage(packageName: string): void {
297
+ this.popularPackages.add(packageName);
298
+ }
299
+ }
300
+
301
+ // Export singleton
302
+ export const typosquatDetector = new TyposquatDetector();