@angular-helpers/security 21.0.0 → 21.0.2

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.
@@ -0,0 +1,407 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DestroyRef, Injectable } from '@angular/core';
3
+
4
+ /**
5
+ * Security service for regular expressions that prevents ReDoS
6
+ * using Web Workers for safe execution with timeout
7
+ */
8
+ class RegexSecurityService {
9
+ destroyRef = inject(DestroyRef);
10
+ workers = new Map();
11
+ /**
12
+ * Builder pattern to construct safe regular expressions
13
+ */
14
+ static builder() {
15
+ return new RegexSecurityBuilder();
16
+ }
17
+ /**
18
+ * Executes a regular expression safely with a timeout
19
+ */
20
+ async testRegex(pattern, text, config = {}) {
21
+ const startTime = performance.now();
22
+ const finalConfig = this.mergeConfig(config);
23
+ try {
24
+ // First, analyze pattern security
25
+ const securityCheck = await this.analyzePatternSecurity(pattern);
26
+ if (!securityCheck.safe && !finalConfig.safeMode) {
27
+ return {
28
+ match: false,
29
+ executionTime: performance.now() - startTime,
30
+ timeout: false,
31
+ error: `Pattern rejected: ${securityCheck.warnings.join(', ')}`
32
+ };
33
+ }
34
+ // Execute in Web Worker with timeout
35
+ const result = await this.executeInWorker(pattern, text, finalConfig);
36
+ return {
37
+ ...result,
38
+ executionTime: performance.now() - startTime
39
+ };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ match: false,
44
+ executionTime: performance.now() - startTime,
45
+ timeout: false,
46
+ error: error instanceof Error ? error.message : 'Unknown error'
47
+ };
48
+ }
49
+ }
50
+ /**
51
+ * Analyzes the security of a regular expression pattern
52
+ */
53
+ async analyzePatternSecurity(pattern) {
54
+ const warnings = [];
55
+ const recommendations = [];
56
+ let complexity = 0;
57
+ let risk = 'low';
58
+ // Analysis of dangerous patterns
59
+ const dangerousPatterns = [
60
+ { pattern: /\*\*/, risk: 'high', message: 'Nested quantifiers (catastrophic backtracking)' },
61
+ { pattern: /\+\+/, risk: 'high', message: 'Nested plus quantifiers' },
62
+ { pattern: /\(\?\=/, risk: 'medium', message: 'Lookahead assertions' },
63
+ { pattern: /\(\?\!/, risk: 'medium', message: 'Negative lookahead' },
64
+ { pattern: /\(\?\:/, risk: 'low', message: 'Non-capturing groups' },
65
+ { pattern: /\(\?\</, risk: 'high', message: 'Lookbehind assertions' },
66
+ { pattern: /\(\?\(\?\)/, risk: 'critical', message: 'Recursive patterns' },
67
+ { pattern: /(\{(\d+,)?\d+\})/, risk: 'medium', message: 'Quantified repetition' },
68
+ { pattern: /(\.\*)|(\.+)|(\.\?)/, risk: 'medium', message: 'Greedy quantifiers with dot' },
69
+ { pattern: /(\[.*\*.*\])|(\[.*\+.*\])/, risk: 'medium', message: 'Character classes with quantifiers' }
70
+ ];
71
+ // Calculate complexity
72
+ complexity = this.calculateComplexity(pattern);
73
+ // Evaluate dangerous patterns
74
+ for (const dangerous of dangerousPatterns) {
75
+ if (dangerous.pattern.test(pattern)) {
76
+ warnings.push(dangerous.message);
77
+ if (this.getRiskLevel(dangerous.risk) > this.getRiskLevel(risk)) {
78
+ risk = dangerous.risk;
79
+ }
80
+ }
81
+ }
82
+ // Recommendations based on the analysis
83
+ if (complexity > 10) {
84
+ recommendations.push('Consider simplifying the pattern');
85
+ risk = this.getRiskLevel(risk) > this.getRiskLevel('high') ? risk : 'high';
86
+ }
87
+ if (pattern.includes('**') || pattern.includes('++')) {
88
+ recommendations.push('Avoid nested quantifiers to prevent catastrophic backtracking');
89
+ }
90
+ if (pattern.length > 100) {
91
+ recommendations.push('Long patterns are harder to maintain and may impact performance');
92
+ }
93
+ const safe = risk !== 'critical' && warnings.length === 0;
94
+ return {
95
+ safe,
96
+ complexity,
97
+ risk,
98
+ warnings,
99
+ recommendations
100
+ };
101
+ }
102
+ /**
103
+ * Executes the regular expression in a Web Worker
104
+ */
105
+ async executeInWorker(pattern, text, config) {
106
+ return new Promise((resolve) => {
107
+ const workerName = `regex-worker-${Date.now()}`;
108
+ try {
109
+ // Create temporary worker
110
+ const workerCode = this.generateWorkerCode();
111
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
112
+ const worker = new Worker(URL.createObjectURL(blob));
113
+ this.workers.set(workerName, worker);
114
+ const taskId = `regex_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
115
+ const task = {
116
+ id: taskId,
117
+ type: 'regex-test',
118
+ data: {
119
+ pattern,
120
+ text,
121
+ timeout: config.timeout || 5000
122
+ }
123
+ };
124
+ // Timeout for execution
125
+ const timeoutId = setTimeout(() => {
126
+ worker.terminate();
127
+ this.workers.delete(workerName);
128
+ resolve({
129
+ match: false,
130
+ executionTime: 0,
131
+ timeout: true,
132
+ error: 'Execution timeout'
133
+ });
134
+ }, config.timeout || 5000);
135
+ worker.onmessage = (event) => {
136
+ clearTimeout(timeoutId);
137
+ worker.terminate();
138
+ this.workers.delete(workerName);
139
+ if (event.data.id === taskId) {
140
+ resolve(event.data.data);
141
+ }
142
+ };
143
+ worker.onerror = (error) => {
144
+ clearTimeout(timeoutId);
145
+ worker.terminate();
146
+ this.workers.delete(workerName);
147
+ resolve({
148
+ match: false,
149
+ executionTime: 0,
150
+ timeout: false,
151
+ error: `Worker error: ${error.message || 'Unknown error'}`
152
+ });
153
+ };
154
+ worker.postMessage(task);
155
+ }
156
+ catch (error) {
157
+ resolve({
158
+ match: false,
159
+ executionTime: 0,
160
+ timeout: false,
161
+ error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}`
162
+ });
163
+ }
164
+ });
165
+ }
166
+ /**
167
+ * Generates the Web Worker code
168
+ */
169
+ generateWorkerCode() {
170
+ return `
171
+ self.addEventListener('message', function(event) {
172
+ const task = event.data;
173
+
174
+ if (task.type === 'regex-test') {
175
+ const { pattern, text, timeout } = task.data;
176
+ const startTime = performance.now();
177
+
178
+ try {
179
+ const regex = new RegExp(pattern, 'g');
180
+ const matches = [];
181
+ let match;
182
+
183
+ while ((match = regex.exec(text)) !== null) {
184
+ matches.push([...match]);
185
+
186
+ // Prevention of infinite loops
187
+ if (matches.length > 1000) {
188
+ throw new Error('Too many matches - possible infinite loop');
189
+ }
190
+ }
191
+
192
+ const groups = {};
193
+ if (matches.length > 0) {
194
+ const firstMatch = matches[0];
195
+ for (let i = 1; i < firstMatch.length; i++) {
196
+ groups[\`group\${i}\`] = firstMatch[i];
197
+ }
198
+ }
199
+
200
+ self.postMessage({
201
+ id: task.id,
202
+ type: 'regex-result',
203
+ data: {
204
+ match: matches.length > 0,
205
+ matches,
206
+ groups,
207
+ executionTime: performance.now() - startTime,
208
+ timeout: false
209
+ }
210
+ });
211
+ } catch (error) {
212
+ self.postMessage({
213
+ id: task.id,
214
+ type: 'regex-result',
215
+ data: {
216
+ match: false,
217
+ executionTime: performance.now() - startTime,
218
+ timeout: false,
219
+ error: error.message || 'Execution error'
220
+ }
221
+ });
222
+ }
223
+ }
224
+ });
225
+ `;
226
+ }
227
+ /**
228
+ * Calculates the complexity of a pattern
229
+ */
230
+ calculateComplexity(pattern) {
231
+ let complexity = 0;
232
+ // Nested quantifiers increase complexity
233
+ complexity += (pattern.match(/\*\*/g) || []).length * 5;
234
+ complexity += (pattern.match(/\+\+/g) || []).length * 5;
235
+ complexity += (pattern.match(/\?\?/g) || []).length * 3;
236
+ // Lookaheads/lookbehinds
237
+ complexity += (pattern.match(/\(\?\=/g) || []).length * 2;
238
+ complexity += (pattern.match(/\(\?\!/g) || []).length * 2;
239
+ complexity += (pattern.match(/\(\?\</g) || []).length * 3;
240
+ // Nested groups
241
+ const openParens = (pattern.match(/\(/g) || []).length;
242
+ complexity += openParens * 0.5;
243
+ // Pattern length
244
+ complexity += pattern.length * 0.01;
245
+ return Math.round(complexity * 100) / 100;
246
+ }
247
+ /**
248
+ * Gets numeric risk level
249
+ */
250
+ getRiskLevel(risk) {
251
+ const levels = { 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
252
+ return levels[risk] || 0;
253
+ }
254
+ /**
255
+ * Merges configuration with default values
256
+ */
257
+ mergeConfig(config) {
258
+ return {
259
+ timeout: config.timeout || 5000,
260
+ maxComplexity: config.maxComplexity || 10,
261
+ allowBacktracking: config.allowBacktracking || false,
262
+ safeMode: config.safeMode || false
263
+ };
264
+ }
265
+ /**
266
+ * Cleans up resources when the service is destroyed
267
+ */
268
+ ngOnDestroy() {
269
+ this.workers.forEach(worker => {
270
+ worker.terminate();
271
+ });
272
+ this.workers.clear();
273
+ }
274
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: RegexSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
275
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: RegexSecurityService });
276
+ }
277
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: RegexSecurityService, decorators: [{
278
+ type: Injectable
279
+ }] });
280
+ /**
281
+ * Builder pattern to construct safe regular expressions
282
+ */
283
+ class RegexSecurityBuilder {
284
+ patternValue = '';
285
+ optionsValue = {};
286
+ securityConfigValue = {};
287
+ /**
288
+ * Defines the base pattern
289
+ */
290
+ pattern(pattern) {
291
+ this.patternValue = pattern;
292
+ return this;
293
+ }
294
+ /**
295
+ * Appends text to the current pattern
296
+ */
297
+ append(text) {
298
+ this.patternValue += text;
299
+ return this;
300
+ }
301
+ /**
302
+ * Adds a capturing group
303
+ */
304
+ group(content, name) {
305
+ if (name) {
306
+ this.patternValue += `(?<${name}>${content})`;
307
+ }
308
+ else {
309
+ this.patternValue += `(${content})`;
310
+ }
311
+ return this;
312
+ }
313
+ /**
314
+ * Adds a non-capturing group
315
+ */
316
+ nonCapturingGroup(content) {
317
+ this.patternValue += `(?:${content})`;
318
+ return this;
319
+ }
320
+ /**
321
+ * Adds an alternative
322
+ */
323
+ or(alternative) {
324
+ this.patternValue += `|${alternative}`;
325
+ return this;
326
+ }
327
+ /**
328
+ * Adds a quantifier
329
+ */
330
+ quantifier(quantifier) {
331
+ this.patternValue += quantifier;
332
+ return this;
333
+ }
334
+ /**
335
+ * Adds a character set
336
+ */
337
+ characterSet(chars, negate = false) {
338
+ this.patternValue += `[${negate ? '^' : ''}${chars}]`;
339
+ return this;
340
+ }
341
+ /**
342
+ * Adds a start of line anchor
343
+ */
344
+ startOfLine() {
345
+ this.patternValue += '^';
346
+ return this;
347
+ }
348
+ /**
349
+ * Adds an end of line anchor
350
+ */
351
+ endOfLine() {
352
+ this.patternValue += '$';
353
+ return this;
354
+ }
355
+ /**
356
+ * Configures regular expression options
357
+ */
358
+ options(options) {
359
+ this.optionsValue = { ...this.optionsValue, ...options };
360
+ return this;
361
+ }
362
+ /**
363
+ * Configures security options
364
+ */
365
+ security(config) {
366
+ this.securityConfigValue = { ...this.securityConfigValue, ...config };
367
+ return this;
368
+ }
369
+ /**
370
+ * Configures timeout
371
+ */
372
+ timeout(ms) {
373
+ this.securityConfigValue.timeout = ms;
374
+ return this;
375
+ }
376
+ /**
377
+ * Activates safe mode
378
+ */
379
+ safeMode() {
380
+ this.securityConfigValue.safeMode = true;
381
+ return this;
382
+ }
383
+ /**
384
+ * Builds the final regular expression
385
+ */
386
+ build() {
387
+ return {
388
+ pattern: this.patternValue,
389
+ options: this.optionsValue,
390
+ security: this.securityConfigValue
391
+ };
392
+ }
393
+ /**
394
+ * Builds and executes the regular expression
395
+ */
396
+ async execute(text, service) {
397
+ const { pattern, security } = this.build();
398
+ return service.testRegex(pattern, text, security);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Generated bundle index. Do not edit.
404
+ */
405
+
406
+ export { RegexSecurityBuilder, RegexSecurityService };
407
+ //# sourceMappingURL=angular-helpers-security.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular-helpers-security.mjs","sources":["../../../packages/security/src/services/regex-security.service.ts","../../../packages/security/src/angular-helpers-security.ts"],"sourcesContent":["import { Injectable, inject, DestroyRef } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport { map, catchError, timeout } from 'rxjs/operators';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nexport interface RegexSecurityConfig {\n timeout?: number; // Timeout in milliseconds (default: 5000)\n maxComplexity?: number; // Maximum complexity allowed\n allowBacktracking?: boolean; // Allow catastrophic backtracking\n safeMode?: boolean; // Safe mode with secure patterns only\n}\n\nexport interface RegexTestResult {\n match: boolean;\n matches?: RegExpMatchArray[];\n groups?: { [key: string]: string };\n executionTime: number;\n timeout: boolean;\n error?: string;\n}\n\nexport interface RegexSecurityResult {\n safe: boolean;\n complexity: number;\n risk: 'low' | 'medium' | 'high' | 'critical';\n warnings: string[];\n recommendations: string[];\n}\n\nexport interface RegexBuilderOptions {\n global?: boolean;\n ignoreCase?: boolean;\n multiline?: boolean;\n dotAll?: boolean;\n unicode?: boolean;\n sticky?: boolean;\n}\n\n/**\n * Security service for regular expressions that prevents ReDoS\n * using Web Workers for safe execution with timeout\n */\n@Injectable()\nexport class RegexSecurityService {\n private destroyRef = inject(DestroyRef);\n private workers = new Map<string, Worker>();\n\n /**\n * Builder pattern to construct safe regular expressions\n */\n static builder(): RegexSecurityBuilder {\n return new RegexSecurityBuilder();\n }\n\n /**\n * Executes a regular expression safely with a timeout\n */\n async testRegex(\n pattern: string,\n text: string,\n config: RegexSecurityConfig = {}\n ): Promise<RegexTestResult> {\n const startTime = performance.now();\n const finalConfig = this.mergeConfig(config);\n\n try {\n // First, analyze pattern security\n const securityCheck = await this.analyzePatternSecurity(pattern);\n \n if (!securityCheck.safe && !finalConfig.safeMode) {\n return {\n match: false,\n executionTime: performance.now() - startTime,\n timeout: false,\n error: `Pattern rejected: ${securityCheck.warnings.join(', ')}`\n };\n }\n\n // Execute in Web Worker with timeout\n const result = await this.executeInWorker(pattern, text, finalConfig);\n \n return {\n ...result,\n executionTime: performance.now() - startTime\n };\n } catch (error) {\n return {\n match: false,\n executionTime: performance.now() - startTime,\n timeout: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Analyzes the security of a regular expression pattern\n */\n async analyzePatternSecurity(pattern: string): Promise<RegexSecurityResult> {\n const warnings: string[] = [];\n const recommendations: string[] = [];\n let complexity = 0;\n let risk: 'low' | 'medium' | 'high' | 'critical' = 'low';\n\n // Analysis of dangerous patterns\n const dangerousPatterns = [\n { pattern: /\\*\\*/, risk: 'high' as const, message: 'Nested quantifiers (catastrophic backtracking)' },\n { pattern: /\\+\\+/, risk: 'high' as const, message: 'Nested plus quantifiers' },\n { pattern: /\\(\\?\\=/, risk: 'medium' as const, message: 'Lookahead assertions' },\n { pattern: /\\(\\?\\!/, risk: 'medium' as const, message: 'Negative lookahead' },\n { pattern: /\\(\\?\\:/, risk: 'low' as const, message: 'Non-capturing groups' },\n { pattern: /\\(\\?\\</, risk: 'high' as const, message: 'Lookbehind assertions' },\n { pattern: /\\(\\?\\(\\?\\)/, risk: 'critical' as const, message: 'Recursive patterns' },\n { pattern: /(\\{(\\d+,)?\\d+\\})/, risk: 'medium' as const, message: 'Quantified repetition' },\n { pattern: /(\\.\\*)|(\\.+)|(\\.\\?)/, risk: 'medium' as const, message: 'Greedy quantifiers with dot' },\n { pattern: /(\\[.*\\*.*\\])|(\\[.*\\+.*\\])/, risk: 'medium' as const, message: 'Character classes with quantifiers' }\n ];\n\n // Calculate complexity\n complexity = this.calculateComplexity(pattern);\n\n // Evaluate dangerous patterns\n for (const dangerous of dangerousPatterns) {\n if (dangerous.pattern.test(pattern)) {\n warnings.push(dangerous.message);\n if (this.getRiskLevel(dangerous.risk) > this.getRiskLevel(risk)) {\n risk = dangerous.risk;\n }\n }\n }\n\n // Recommendations based on the analysis\n if (complexity > 10) {\n recommendations.push('Consider simplifying the pattern');\n risk = this.getRiskLevel(risk) > this.getRiskLevel('high') ? risk : 'high';\n }\n\n if (pattern.includes('**') || pattern.includes('++')) {\n recommendations.push('Avoid nested quantifiers to prevent catastrophic backtracking');\n }\n\n if (pattern.length > 100) {\n recommendations.push('Long patterns are harder to maintain and may impact performance');\n }\n\n const safe = risk !== 'critical' && warnings.length === 0;\n\n return {\n safe,\n complexity,\n risk,\n warnings,\n recommendations\n };\n }\n\n /**\n * Executes the regular expression in a Web Worker\n */\n private async executeInWorker(\n pattern: string,\n text: string,\n config: RegexSecurityConfig\n ): Promise<RegexTestResult> {\n return new Promise((resolve) => {\n const workerName = `regex-worker-${Date.now()}`;\n \n try {\n // Create temporary worker\n const workerCode = this.generateWorkerCode();\n const blob = new Blob([workerCode], { type: 'application/javascript' });\n const worker = new Worker(URL.createObjectURL(blob));\n \n this.workers.set(workerName, worker);\n\n const taskId = `regex_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n \n const task = {\n id: taskId,\n type: 'regex-test',\n data: {\n pattern,\n text,\n timeout: config.timeout || 5000\n }\n };\n\n // Timeout for execution\n const timeoutId = setTimeout(() => {\n worker.terminate();\n this.workers.delete(workerName);\n resolve({\n match: false,\n executionTime: 0,\n timeout: true,\n error: 'Execution timeout'\n });\n }, config.timeout || 5000);\n\n worker.onmessage = (event) => {\n clearTimeout(timeoutId);\n worker.terminate();\n this.workers.delete(workerName);\n \n if (event.data.id === taskId) {\n resolve(event.data.data as RegexTestResult);\n }\n };\n\n worker.onerror = (error) => {\n clearTimeout(timeoutId);\n worker.terminate();\n this.workers.delete(workerName);\n resolve({\n match: false,\n executionTime: 0,\n timeout: false,\n error: `Worker error: ${error.message || 'Unknown error'}`\n });\n };\n\n worker.postMessage(task);\n } catch (error) {\n resolve({\n match: false,\n executionTime: 0,\n timeout: false,\n error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}`\n });\n }\n });\n }\n\n /**\n * Generates the Web Worker code\n */\n private generateWorkerCode(): string {\n return `\n self.addEventListener('message', function(event) {\n const task = event.data;\n \n if (task.type === 'regex-test') {\n const { pattern, text, timeout } = task.data;\n const startTime = performance.now();\n \n try {\n const regex = new RegExp(pattern, 'g');\n const matches = [];\n let match;\n \n while ((match = regex.exec(text)) !== null) {\n matches.push([...match]);\n \n // Prevention of infinite loops\n if (matches.length > 1000) {\n throw new Error('Too many matches - possible infinite loop');\n }\n }\n \n const groups = {};\n if (matches.length > 0) {\n const firstMatch = matches[0];\n for (let i = 1; i < firstMatch.length; i++) {\n groups[\\`group\\${i}\\`] = firstMatch[i];\n }\n }\n \n self.postMessage({\n id: task.id,\n type: 'regex-result',\n data: {\n match: matches.length > 0,\n matches,\n groups,\n executionTime: performance.now() - startTime,\n timeout: false\n }\n });\n } catch (error) {\n self.postMessage({\n id: task.id,\n type: 'regex-result',\n data: {\n match: false,\n executionTime: performance.now() - startTime,\n timeout: false,\n error: error.message || 'Execution error'\n }\n });\n }\n }\n });\n `;\n }\n\n /**\n * Calculates the complexity of a pattern\n */\n private calculateComplexity(pattern: string): number {\n let complexity = 0;\n \n // Nested quantifiers increase complexity\n complexity += (pattern.match(/\\*\\*/g) || []).length * 5;\n complexity += (pattern.match(/\\+\\+/g) || []).length * 5;\n complexity += (pattern.match(/\\?\\?/g) || []).length * 3;\n \n // Lookaheads/lookbehinds\n complexity += (pattern.match(/\\(\\?\\=/g) || []).length * 2;\n complexity += (pattern.match(/\\(\\?\\!/g) || []).length * 2;\n complexity += (pattern.match(/\\(\\?\\</g) || []).length * 3;\n \n // Nested groups\n const openParens = (pattern.match(/\\(/g) || []).length;\n complexity += openParens * 0.5;\n \n // Pattern length\n complexity += pattern.length * 0.01;\n \n return Math.round(complexity * 100) / 100;\n }\n\n /**\n * Gets numeric risk level\n */\n private getRiskLevel(risk: 'low' | 'medium' | 'high' | 'critical'): number {\n const levels = { 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };\n return levels[risk] || 0;\n }\n\n /**\n * Merges configuration with default values\n */\n private mergeConfig(config: RegexSecurityConfig): Required<RegexSecurityConfig> {\n return {\n timeout: config.timeout || 5000,\n maxComplexity: config.maxComplexity || 10,\n allowBacktracking: config.allowBacktracking || false,\n safeMode: config.safeMode || false\n };\n }\n\n /**\n * Cleans up resources when the service is destroyed\n */\n ngOnDestroy(): void {\n this.workers.forEach(worker => {\n worker.terminate();\n });\n this.workers.clear();\n }\n}\n\n/**\n * Builder pattern to construct safe regular expressions\n */\nexport class RegexSecurityBuilder {\n private patternValue: string = '';\n private optionsValue: RegexBuilderOptions = {};\n private securityConfigValue: RegexSecurityConfig = {};\n\n /**\n * Defines the base pattern\n */\n pattern(pattern: string): RegexSecurityBuilder {\n this.patternValue = pattern;\n return this;\n }\n\n /**\n * Appends text to the current pattern\n */\n append(text: string): RegexSecurityBuilder {\n this.patternValue += text;\n return this;\n }\n\n /**\n * Adds a capturing group\n */\n group(content: string, name?: string): RegexSecurityBuilder {\n if (name) {\n this.patternValue += `(?<${name}>${content})`;\n } else {\n this.patternValue += `(${content})`;\n }\n return this;\n }\n\n /**\n * Adds a non-capturing group\n */\n nonCapturingGroup(content: string): RegexSecurityBuilder {\n this.patternValue += `(?:${content})`;\n return this;\n }\n\n /**\n * Adds an alternative\n */\n or(alternative: string): RegexSecurityBuilder {\n this.patternValue += `|${alternative}`;\n return this;\n }\n\n /**\n * Adds a quantifier\n */\n quantifier(quantifier: '*' | '+' | '?' | '{n}' | '{n,}' | '{n,m}'): RegexSecurityBuilder {\n this.patternValue += quantifier;\n return this;\n }\n\n /**\n * Adds a character set\n */\n characterSet(chars: string, negate = false): RegexSecurityBuilder {\n this.patternValue += `[${negate ? '^' : ''}${chars}]`;\n return this;\n }\n\n /**\n * Adds a start of line anchor\n */\n startOfLine(): RegexSecurityBuilder {\n this.patternValue += '^';\n return this;\n }\n\n /**\n * Adds an end of line anchor\n */\n endOfLine(): RegexSecurityBuilder {\n this.patternValue += '$';\n return this;\n }\n\n /**\n * Configures regular expression options\n */\n options(options: RegexBuilderOptions): RegexSecurityBuilder {\n this.optionsValue = { ...this.optionsValue, ...options };\n return this;\n }\n\n /**\n * Configures security options\n */\n security(config: RegexSecurityConfig): RegexSecurityBuilder {\n this.securityConfigValue = { ...this.securityConfigValue, ...config };\n return this;\n }\n\n /**\n * Configures timeout\n */\n timeout(ms: number): RegexSecurityBuilder {\n this.securityConfigValue.timeout = ms;\n return this;\n }\n\n /**\n * Activates safe mode\n */\n safeMode(): RegexSecurityBuilder {\n this.securityConfigValue.safeMode = true;\n return this;\n }\n\n /**\n * Builds the final regular expression\n */\n build(): { pattern: string; options: RegexBuilderOptions; security: RegexSecurityConfig } {\n return {\n pattern: this.patternValue,\n options: this.optionsValue,\n security: this.securityConfigValue\n };\n }\n\n /**\n * Builds and executes the regular expression\n */\n async execute(text: string, service: RegexSecurityService): Promise<RegexTestResult> {\n const { pattern, security } = this.build();\n return service.testRegex(pattern, text, security);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAsCA;;;AAGG;MAEU,oBAAoB,CAAA;AACvB,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAC/B,IAAA,OAAO,GAAG,IAAI,GAAG,EAAkB;AAE3C;;AAEG;AACH,IAAA,OAAO,OAAO,GAAA;QACZ,OAAO,IAAI,oBAAoB,EAAE;IACnC;AAEA;;AAEG;IACH,MAAM,SAAS,CACb,OAAe,EACf,IAAY,EACZ,SAA8B,EAAE,EAAA;AAEhC,QAAA,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;AAE5C,QAAA,IAAI;;YAEF,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC;YAEhE,IAAI,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE;gBAChD,OAAO;AACL,oBAAA,KAAK,EAAE,KAAK;AACZ,oBAAA,aAAa,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS;AAC5C,oBAAA,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,CAAA,kBAAA,EAAqB,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;iBAC9D;YACH;;AAGA,YAAA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC;YAErE,OAAO;AACL,gBAAA,GAAG,MAAM;AACT,gBAAA,aAAa,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG;aACpC;QACH;QAAE,OAAO,KAAK,EAAE;YACd,OAAO;AACL,gBAAA,KAAK,EAAE,KAAK;AACZ,gBAAA,aAAa,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS;AAC5C,gBAAA,OAAO,EAAE,KAAK;AACd,gBAAA,KAAK,EAAE,KAAK,YAAY,KAAK,GAAG,KAAK,CAAC,OAAO,GAAG;aACjD;QACH;IACF;AAEA;;AAEG;IACH,MAAM,sBAAsB,CAAC,OAAe,EAAA;QAC1C,MAAM,QAAQ,GAAa,EAAE;QAC7B,MAAM,eAAe,GAAa,EAAE;QACpC,IAAI,UAAU,GAAG,CAAC;QAClB,IAAI,IAAI,GAA2C,KAAK;;AAGxD,QAAA,MAAM,iBAAiB,GAAG;YACxB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,gDAAgD,EAAE;YACrG,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,yBAAyB,EAAE;YAC9E,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;YAC/E,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,oBAAoB,EAAE;YAC7E,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAc,EAAE,OAAO,EAAE,sBAAsB,EAAE;YAC5E,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,uBAAuB,EAAE;YAC9E,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,UAAmB,EAAE,OAAO,EAAE,oBAAoB,EAAE;YACnF,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,uBAAuB,EAAE;YAC1F,EAAE,OAAO,EAAE,qBAAqB,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,6BAA6B,EAAE;YACnG,EAAE,OAAO,EAAE,2BAA2B,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,oCAAoC;SAC/G;;AAGD,QAAA,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;;AAG9C,QAAA,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE;YACzC,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;AACnC,gBAAA,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;AAChC,gBAAA,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;AAC/D,oBAAA,IAAI,GAAG,SAAS,CAAC,IAAI;gBACvB;YACF;QACF;;AAGA,QAAA,IAAI,UAAU,GAAG,EAAE,EAAE;AACnB,YAAA,eAAe,CAAC,IAAI,CAAC,kCAAkC,CAAC;YACxD,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,MAAM;QAC5E;AAEA,QAAA,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AACpD,YAAA,eAAe,CAAC,IAAI,CAAC,+DAA+D,CAAC;QACvF;AAEA,QAAA,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE;AACxB,YAAA,eAAe,CAAC,IAAI,CAAC,iEAAiE,CAAC;QACzF;QAEA,MAAM,IAAI,GAAG,IAAI,KAAK,UAAU,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAEzD,OAAO;YACL,IAAI;YACJ,UAAU;YACV,IAAI;YACJ,QAAQ;YACR;SACD;IACH;AAEA;;AAEG;AACK,IAAA,MAAM,eAAe,CAC3B,OAAe,EACf,IAAY,EACZ,MAA2B,EAAA;AAE3B,QAAA,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAI;YAC7B,MAAM,UAAU,GAAG,CAAA,aAAA,EAAgB,IAAI,CAAC,GAAG,EAAE,EAAE;AAE/C,YAAA,IAAI;;AAEF,gBAAA,MAAM,UAAU,GAAG,IAAI,CAAC,kBAAkB,EAAE;AAC5C,gBAAA,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC;AACvE,gBAAA,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;gBAEpD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEpC,MAAM,MAAM,GAAG,CAAA,MAAA,EAAS,IAAI,CAAC,GAAG,EAAE,CAAA,CAAA,EAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA,CAAE;AAE/E,gBAAA,MAAM,IAAI,GAAG;AACX,oBAAA,EAAE,EAAE,MAAM;AACV,oBAAA,IAAI,EAAE,YAAY;AAClB,oBAAA,IAAI,EAAE;wBACJ,OAAO;wBACP,IAAI;AACJ,wBAAA,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI;AAC5B;iBACF;;AAGD,gBAAA,MAAM,SAAS,GAAG,UAAU,CAAC,MAAK;oBAChC,MAAM,CAAC,SAAS,EAAE;AAClB,oBAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;AAC/B,oBAAA,OAAO,CAAC;AACN,wBAAA,KAAK,EAAE,KAAK;AACZ,wBAAA,aAAa,EAAE,CAAC;AAChB,wBAAA,OAAO,EAAE,IAAI;AACb,wBAAA,KAAK,EAAE;AACR,qBAAA,CAAC;AACJ,gBAAA,CAAC,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC;AAE1B,gBAAA,MAAM,CAAC,SAAS,GAAG,CAAC,KAAK,KAAI;oBAC3B,YAAY,CAAC,SAAS,CAAC;oBACvB,MAAM,CAAC,SAAS,EAAE;AAClB,oBAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;oBAE/B,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,EAAE;AAC5B,wBAAA,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAuB,CAAC;oBAC7C;AACF,gBAAA,CAAC;AAED,gBAAA,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,KAAI;oBACzB,YAAY,CAAC,SAAS,CAAC;oBACvB,MAAM,CAAC,SAAS,EAAE;AAClB,oBAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;AAC/B,oBAAA,OAAO,CAAC;AACN,wBAAA,KAAK,EAAE,KAAK;AACZ,wBAAA,aAAa,EAAE,CAAC;AAChB,wBAAA,OAAO,EAAE,KAAK;AACd,wBAAA,KAAK,EAAE,CAAA,cAAA,EAAiB,KAAK,CAAC,OAAO,IAAI,eAAe,CAAA;AACzD,qBAAA,CAAC;AACJ,gBAAA,CAAC;AAED,gBAAA,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;YAC1B;YAAE,OAAO,KAAK,EAAE;AACd,gBAAA,OAAO,CAAC;AACN,oBAAA,KAAK,EAAE,KAAK;AACZ,oBAAA,aAAa,EAAE,CAAC;AAChB,oBAAA,OAAO,EAAE,KAAK;AACd,oBAAA,KAAK,EAAE,CAAA,yBAAA,EAA4B,KAAK,YAAY,KAAK,GAAG,KAAK,CAAC,OAAO,GAAG,eAAe,CAAA;AAC5F,iBAAA,CAAC;YACJ;AACF,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;IACK,kBAAkB,GAAA;QACxB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuDN;IACH;AAEA;;AAEG;AACK,IAAA,mBAAmB,CAAC,OAAe,EAAA;QACzC,IAAI,UAAU,GAAG,CAAC;;AAGlB,QAAA,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC;AACvD,QAAA,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC;AACvD,QAAA,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC;;AAGvD,QAAA,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC;AACzD,QAAA,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC;AACzD,QAAA,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC;;AAGzD,QAAA,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM;AACtD,QAAA,UAAU,IAAI,UAAU,GAAG,GAAG;;AAG9B,QAAA,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI;QAEnC,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,GAAG;IAC3C;AAEA;;AAEG;AACK,IAAA,YAAY,CAAC,IAA4C,EAAA;AAC/D,QAAA,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE;AAClE,QAAA,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;IAC1B;AAEA;;AAEG;AACK,IAAA,WAAW,CAAC,MAA2B,EAAA;QAC7C,OAAO;AACL,YAAA,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;AAC/B,YAAA,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,EAAE;AACzC,YAAA,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,KAAK;AACpD,YAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI;SAC9B;IACH;AAEA;;AAEG;IACH,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,IAAG;YAC5B,MAAM,CAAC,SAAS,EAAE;AACpB,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;IACtB;uGAlTW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;2GAApB,oBAAoB,EAAA,CAAA;;2FAApB,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBADhC;;AAsTD;;AAEG;MACU,oBAAoB,CAAA;IACvB,YAAY,GAAW,EAAE;IACzB,YAAY,GAAwB,EAAE;IACtC,mBAAmB,GAAwB,EAAE;AAErD;;AAEG;AACH,IAAA,OAAO,CAAC,OAAe,EAAA;AACrB,QAAA,IAAI,CAAC,YAAY,GAAG,OAAO;AAC3B,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,MAAM,CAAC,IAAY,EAAA;AACjB,QAAA,IAAI,CAAC,YAAY,IAAI,IAAI;AACzB,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;IACH,KAAK,CAAC,OAAe,EAAE,IAAa,EAAA;QAClC,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,YAAY,IAAI,CAAA,GAAA,EAAM,IAAI,CAAA,CAAA,EAAI,OAAO,GAAG;QAC/C;aAAO;AACL,YAAA,IAAI,CAAC,YAAY,IAAI,CAAA,CAAA,EAAI,OAAO,GAAG;QACrC;AACA,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,iBAAiB,CAAC,OAAe,EAAA;AAC/B,QAAA,IAAI,CAAC,YAAY,IAAI,CAAA,GAAA,EAAM,OAAO,GAAG;AACrC,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,EAAE,CAAC,WAAmB,EAAA;AACpB,QAAA,IAAI,CAAC,YAAY,IAAI,CAAA,CAAA,EAAI,WAAW,EAAE;AACtC,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,UAAU,CAAC,UAAsD,EAAA;AAC/D,QAAA,IAAI,CAAC,YAAY,IAAI,UAAU;AAC/B,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,YAAY,CAAC,KAAa,EAAE,MAAM,GAAG,KAAK,EAAA;AACxC,QAAA,IAAI,CAAC,YAAY,IAAI,CAAA,CAAA,EAAI,MAAM,GAAG,GAAG,GAAG,EAAE,CAAA,EAAG,KAAK,GAAG;AACrD,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;IACH,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,YAAY,IAAI,GAAG;AACxB,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;IACH,SAAS,GAAA;AACP,QAAA,IAAI,CAAC,YAAY,IAAI,GAAG;AACxB,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,OAAO,CAAC,OAA4B,EAAA;AAClC,QAAA,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,EAAE;AACxD,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,QAAQ,CAAC,MAA2B,EAAA;AAClC,QAAA,IAAI,CAAC,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC,mBAAmB,EAAE,GAAG,MAAM,EAAE;AACrE,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACH,IAAA,OAAO,CAAC,EAAU,EAAA;AAChB,QAAA,IAAI,CAAC,mBAAmB,CAAC,OAAO,GAAG,EAAE;AACrC,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;IACH,QAAQ,GAAA;AACN,QAAA,IAAI,CAAC,mBAAmB,CAAC,QAAQ,GAAG,IAAI;AACxC,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;IACH,KAAK,GAAA;QACH,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,YAAY;YAC1B,OAAO,EAAE,IAAI,CAAC,YAAY;YAC1B,QAAQ,EAAE,IAAI,CAAC;SAChB;IACH;AAEA;;AAEG;AACH,IAAA,MAAM,OAAO,CAAC,IAAY,EAAE,OAA6B,EAAA;QACvD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE;QAC1C,OAAO,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC;IACnD;AACD;;ACteD;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-helpers/security",
3
- "version": "21.0.0",
3
+ "version": "21.0.2",
4
4
  "description": "Angular security helpers for preventing ReDoS and other security vulnerabilities",
5
5
  "keywords": [
6
6
  "angular",
@@ -24,26 +24,25 @@
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
27
- "scripts": {
28
- "build": "ng-packagr -p ng-package.json"
29
- },
30
27
  "peerDependencies": {
31
28
  "@angular/common": "^21.0.0",
32
29
  "@angular/core": "^21.0.0",
33
- "@angular-helpers/browser-web-apis": "21.0.0",
34
- "rxjs": "^7.0.0 || ^8.0.0",
35
- "tslib": "^2.0.0"
30
+ "@angular-helpers/browser-web-apis": "21.0.2",
31
+ "rxjs": "^7.0.0 || ^8.0.0"
36
32
  },
37
33
  "dependencies": {
38
34
  "tslib": "^2.0.0"
39
35
  },
40
- "ngPackage": {
41
- "lib": {
42
- "entryFile": "src/index.ts"
36
+ "module": "fesm2022/angular-helpers-security.mjs",
37
+ "typings": "types/angular-helpers-security.d.ts",
38
+ "exports": {
39
+ "./package.json": {
40
+ "default": "./package.json"
43
41
  },
44
- "dest": "../../dist/security",
45
- "allowedNonPeerDependencies": [
46
- "tslib"
47
- ]
48
- }
49
- }
42
+ ".": {
43
+ "types": "./types/angular-helpers-security.d.ts",
44
+ "default": "./fesm2022/angular-helpers-security.mjs"
45
+ }
46
+ },
47
+ "sideEffects": false
48
+ }
@@ -0,0 +1,154 @@
1
+ import * as i0 from '@angular/core';
2
+
3
+ interface RegexSecurityConfig {
4
+ timeout?: number;
5
+ maxComplexity?: number;
6
+ allowBacktracking?: boolean;
7
+ safeMode?: boolean;
8
+ }
9
+ interface RegexTestResult {
10
+ match: boolean;
11
+ matches?: RegExpMatchArray[];
12
+ groups?: {
13
+ [key: string]: string;
14
+ };
15
+ executionTime: number;
16
+ timeout: boolean;
17
+ error?: string;
18
+ }
19
+ interface RegexSecurityResult {
20
+ safe: boolean;
21
+ complexity: number;
22
+ risk: 'low' | 'medium' | 'high' | 'critical';
23
+ warnings: string[];
24
+ recommendations: string[];
25
+ }
26
+ interface RegexBuilderOptions {
27
+ global?: boolean;
28
+ ignoreCase?: boolean;
29
+ multiline?: boolean;
30
+ dotAll?: boolean;
31
+ unicode?: boolean;
32
+ sticky?: boolean;
33
+ }
34
+ /**
35
+ * Security service for regular expressions that prevents ReDoS
36
+ * using Web Workers for safe execution with timeout
37
+ */
38
+ declare class RegexSecurityService {
39
+ private destroyRef;
40
+ private workers;
41
+ /**
42
+ * Builder pattern to construct safe regular expressions
43
+ */
44
+ static builder(): RegexSecurityBuilder;
45
+ /**
46
+ * Executes a regular expression safely with a timeout
47
+ */
48
+ testRegex(pattern: string, text: string, config?: RegexSecurityConfig): Promise<RegexTestResult>;
49
+ /**
50
+ * Analyzes the security of a regular expression pattern
51
+ */
52
+ analyzePatternSecurity(pattern: string): Promise<RegexSecurityResult>;
53
+ /**
54
+ * Executes the regular expression in a Web Worker
55
+ */
56
+ private executeInWorker;
57
+ /**
58
+ * Generates the Web Worker code
59
+ */
60
+ private generateWorkerCode;
61
+ /**
62
+ * Calculates the complexity of a pattern
63
+ */
64
+ private calculateComplexity;
65
+ /**
66
+ * Gets numeric risk level
67
+ */
68
+ private getRiskLevel;
69
+ /**
70
+ * Merges configuration with default values
71
+ */
72
+ private mergeConfig;
73
+ /**
74
+ * Cleans up resources when the service is destroyed
75
+ */
76
+ ngOnDestroy(): void;
77
+ static ɵfac: i0.ɵɵFactoryDeclaration<RegexSecurityService, never>;
78
+ static ɵprov: i0.ɵɵInjectableDeclaration<RegexSecurityService>;
79
+ }
80
+ /**
81
+ * Builder pattern to construct safe regular expressions
82
+ */
83
+ declare class RegexSecurityBuilder {
84
+ private patternValue;
85
+ private optionsValue;
86
+ private securityConfigValue;
87
+ /**
88
+ * Defines the base pattern
89
+ */
90
+ pattern(pattern: string): RegexSecurityBuilder;
91
+ /**
92
+ * Appends text to the current pattern
93
+ */
94
+ append(text: string): RegexSecurityBuilder;
95
+ /**
96
+ * Adds a capturing group
97
+ */
98
+ group(content: string, name?: string): RegexSecurityBuilder;
99
+ /**
100
+ * Adds a non-capturing group
101
+ */
102
+ nonCapturingGroup(content: string): RegexSecurityBuilder;
103
+ /**
104
+ * Adds an alternative
105
+ */
106
+ or(alternative: string): RegexSecurityBuilder;
107
+ /**
108
+ * Adds a quantifier
109
+ */
110
+ quantifier(quantifier: '*' | '+' | '?' | '{n}' | '{n,}' | '{n,m}'): RegexSecurityBuilder;
111
+ /**
112
+ * Adds a character set
113
+ */
114
+ characterSet(chars: string, negate?: boolean): RegexSecurityBuilder;
115
+ /**
116
+ * Adds a start of line anchor
117
+ */
118
+ startOfLine(): RegexSecurityBuilder;
119
+ /**
120
+ * Adds an end of line anchor
121
+ */
122
+ endOfLine(): RegexSecurityBuilder;
123
+ /**
124
+ * Configures regular expression options
125
+ */
126
+ options(options: RegexBuilderOptions): RegexSecurityBuilder;
127
+ /**
128
+ * Configures security options
129
+ */
130
+ security(config: RegexSecurityConfig): RegexSecurityBuilder;
131
+ /**
132
+ * Configures timeout
133
+ */
134
+ timeout(ms: number): RegexSecurityBuilder;
135
+ /**
136
+ * Activates safe mode
137
+ */
138
+ safeMode(): RegexSecurityBuilder;
139
+ /**
140
+ * Builds the final regular expression
141
+ */
142
+ build(): {
143
+ pattern: string;
144
+ options: RegexBuilderOptions;
145
+ security: RegexSecurityConfig;
146
+ };
147
+ /**
148
+ * Builds and executes the regular expression
149
+ */
150
+ execute(text: string, service: RegexSecurityService): Promise<RegexTestResult>;
151
+ }
152
+
153
+ export { RegexSecurityBuilder, RegexSecurityService };
154
+ export type { RegexBuilderOptions, RegexSecurityConfig, RegexSecurityResult, RegexTestResult };
package/ng-package.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
- "lib": {
4
- "entryFile": "src/index.ts"
5
- },
6
- "dest": "../../dist/security"
7
- }
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './services/regex-security.service';
@@ -1,32 +0,0 @@
1
- export interface RegexSecurityConfig {
2
- timeout?: number; // Timeout en milisegundos (default: 5000)
3
- maxComplexity?: number; // Máxima complejidad permitida
4
- allowBacktracking?: boolean; // Permitir backtracking catastrófico
5
- safeMode?: boolean; // Modo seguro solo con patrones seguros
6
- }
7
-
8
- export interface RegexTestResult {
9
- match: boolean;
10
- matches?: RegExpMatchArray[];
11
- groups?: { [key: string]: string };
12
- executionTime: number;
13
- timeout: boolean;
14
- error?: string;
15
- }
16
-
17
- export interface RegexSecurityResult {
18
- safe: boolean;
19
- complexity: number;
20
- risk: 'low' | 'medium' | 'high' | 'critical';
21
- warnings: string[];
22
- recommendations: string[];
23
- }
24
-
25
- export interface RegexBuilderOptions {
26
- global?: boolean;
27
- ignoreCase?: boolean;
28
- multiline?: boolean;
29
- dotAll?: boolean;
30
- unicode?: boolean;
31
- sticky?: boolean;
32
- }
package/src/providers.ts DELETED
@@ -1,29 +0,0 @@
1
- import { makeEnvironmentProviders, EnvironmentProviders } from '@angular/core';
2
- import { RegexSecurityService } from './services/regex-security.service';
3
-
4
- export interface SecurityConfig {
5
- enableRegexSecurity?: boolean;
6
- defaultTimeout?: number;
7
- safeMode?: boolean;
8
- }
9
-
10
- export const defaultSecurityConfig: SecurityConfig = {
11
- enableRegexSecurity: true,
12
- defaultTimeout: 5000,
13
- safeMode: false
14
- };
15
-
16
- export function provideSecurity(config: SecurityConfig = {}): EnvironmentProviders {
17
- const mergedConfig = { ...defaultSecurityConfig, ...config };
18
- const providers = [];
19
-
20
- if (mergedConfig.enableRegexSecurity) {
21
- providers.push(RegexSecurityService);
22
- }
23
-
24
- return makeEnvironmentProviders(providers);
25
- }
26
-
27
- export function provideRegexSecurity(): EnvironmentProviders {
28
- return makeEnvironmentProviders([RegexSecurityService]);
29
- }
@@ -1,487 +0,0 @@
1
- import { Injectable, inject, DestroyRef } from '@angular/core';
2
- import { Observable, Subject } from 'rxjs';
3
- import { map, catchError, timeout } from 'rxjs/operators';
4
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
-
6
- export interface RegexSecurityConfig {
7
- timeout?: number; // Timeout in milliseconds (default: 5000)
8
- maxComplexity?: number; // Maximum complexity allowed
9
- allowBacktracking?: boolean; // Allow catastrophic backtracking
10
- safeMode?: boolean; // Safe mode with secure patterns only
11
- }
12
-
13
- export interface RegexTestResult {
14
- match: boolean;
15
- matches?: RegExpMatchArray[];
16
- groups?: { [key: string]: string };
17
- executionTime: number;
18
- timeout: boolean;
19
- error?: string;
20
- }
21
-
22
- export interface RegexSecurityResult {
23
- safe: boolean;
24
- complexity: number;
25
- risk: 'low' | 'medium' | 'high' | 'critical';
26
- warnings: string[];
27
- recommendations: string[];
28
- }
29
-
30
- export interface RegexBuilderOptions {
31
- global?: boolean;
32
- ignoreCase?: boolean;
33
- multiline?: boolean;
34
- dotAll?: boolean;
35
- unicode?: boolean;
36
- sticky?: boolean;
37
- }
38
-
39
- /**
40
- * Security service for regular expressions that prevents ReDoS
41
- * using Web Workers for safe execution with timeout
42
- */
43
- @Injectable()
44
- export class RegexSecurityService {
45
- private destroyRef = inject(DestroyRef);
46
- private workers = new Map<string, Worker>();
47
-
48
- /**
49
- * Builder pattern to construct safe regular expressions
50
- */
51
- static builder(): RegexSecurityBuilder {
52
- return new RegexSecurityBuilder();
53
- }
54
-
55
- /**
56
- * Executes a regular expression safely with a timeout
57
- */
58
- async testRegex(
59
- pattern: string,
60
- text: string,
61
- config: RegexSecurityConfig = {}
62
- ): Promise<RegexTestResult> {
63
- const startTime = performance.now();
64
- const finalConfig = this.mergeConfig(config);
65
-
66
- try {
67
- // First, analyze pattern security
68
- const securityCheck = await this.analyzePatternSecurity(pattern);
69
-
70
- if (!securityCheck.safe && !finalConfig.safeMode) {
71
- return {
72
- match: false,
73
- executionTime: performance.now() - startTime,
74
- timeout: false,
75
- error: `Pattern rejected: ${securityCheck.warnings.join(', ')}`
76
- };
77
- }
78
-
79
- // Execute in Web Worker with timeout
80
- const result = await this.executeInWorker(pattern, text, finalConfig);
81
-
82
- return {
83
- ...result,
84
- executionTime: performance.now() - startTime
85
- };
86
- } catch (error) {
87
- return {
88
- match: false,
89
- executionTime: performance.now() - startTime,
90
- timeout: false,
91
- error: error instanceof Error ? error.message : 'Unknown error'
92
- };
93
- }
94
- }
95
-
96
- /**
97
- * Analyzes the security of a regular expression pattern
98
- */
99
- async analyzePatternSecurity(pattern: string): Promise<RegexSecurityResult> {
100
- const warnings: string[] = [];
101
- const recommendations: string[] = [];
102
- let complexity = 0;
103
- let risk: 'low' | 'medium' | 'high' | 'critical' = 'low';
104
-
105
- // Analysis of dangerous patterns
106
- const dangerousPatterns = [
107
- { pattern: /\*\*/, risk: 'high' as const, message: 'Nested quantifiers (catastrophic backtracking)' },
108
- { pattern: /\+\+/, risk: 'high' as const, message: 'Nested plus quantifiers' },
109
- { pattern: /\(\?\=/, risk: 'medium' as const, message: 'Lookahead assertions' },
110
- { pattern: /\(\?\!/, risk: 'medium' as const, message: 'Negative lookahead' },
111
- { pattern: /\(\?\:/, risk: 'low' as const, message: 'Non-capturing groups' },
112
- { pattern: /\(\?\</, risk: 'high' as const, message: 'Lookbehind assertions' },
113
- { pattern: /\(\?\(\?\)/, risk: 'critical' as const, message: 'Recursive patterns' },
114
- { pattern: /(\{(\d+,)?\d+\})/, risk: 'medium' as const, message: 'Quantified repetition' },
115
- { pattern: /(\.\*)|(\.+)|(\.\?)/, risk: 'medium' as const, message: 'Greedy quantifiers with dot' },
116
- { pattern: /(\[.*\*.*\])|(\[.*\+.*\])/, risk: 'medium' as const, message: 'Character classes with quantifiers' }
117
- ];
118
-
119
- // Calculate complexity
120
- complexity = this.calculateComplexity(pattern);
121
-
122
- // Evaluate dangerous patterns
123
- for (const dangerous of dangerousPatterns) {
124
- if (dangerous.pattern.test(pattern)) {
125
- warnings.push(dangerous.message);
126
- if (this.getRiskLevel(dangerous.risk) > this.getRiskLevel(risk)) {
127
- risk = dangerous.risk;
128
- }
129
- }
130
- }
131
-
132
- // Recommendations based on the analysis
133
- if (complexity > 10) {
134
- recommendations.push('Consider simplifying the pattern');
135
- risk = this.getRiskLevel(risk) > this.getRiskLevel('high') ? risk : 'high';
136
- }
137
-
138
- if (pattern.includes('**') || pattern.includes('++')) {
139
- recommendations.push('Avoid nested quantifiers to prevent catastrophic backtracking');
140
- }
141
-
142
- if (pattern.length > 100) {
143
- recommendations.push('Long patterns are harder to maintain and may impact performance');
144
- }
145
-
146
- const safe = risk !== 'critical' && warnings.length === 0;
147
-
148
- return {
149
- safe,
150
- complexity,
151
- risk,
152
- warnings,
153
- recommendations
154
- };
155
- }
156
-
157
- /**
158
- * Executes the regular expression in a Web Worker
159
- */
160
- private async executeInWorker(
161
- pattern: string,
162
- text: string,
163
- config: RegexSecurityConfig
164
- ): Promise<RegexTestResult> {
165
- return new Promise((resolve) => {
166
- const workerName = `regex-worker-${Date.now()}`;
167
-
168
- try {
169
- // Create temporary worker
170
- const workerCode = this.generateWorkerCode();
171
- const blob = new Blob([workerCode], { type: 'application/javascript' });
172
- const worker = new Worker(URL.createObjectURL(blob));
173
-
174
- this.workers.set(workerName, worker);
175
-
176
- const taskId = `regex_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
177
-
178
- const task = {
179
- id: taskId,
180
- type: 'regex-test',
181
- data: {
182
- pattern,
183
- text,
184
- timeout: config.timeout || 5000
185
- }
186
- };
187
-
188
- // Timeout for execution
189
- const timeoutId = setTimeout(() => {
190
- worker.terminate();
191
- this.workers.delete(workerName);
192
- resolve({
193
- match: false,
194
- executionTime: 0,
195
- timeout: true,
196
- error: 'Execution timeout'
197
- });
198
- }, config.timeout || 5000);
199
-
200
- worker.onmessage = (event) => {
201
- clearTimeout(timeoutId);
202
- worker.terminate();
203
- this.workers.delete(workerName);
204
-
205
- if (event.data.id === taskId) {
206
- resolve(event.data.data as RegexTestResult);
207
- }
208
- };
209
-
210
- worker.onerror = (error) => {
211
- clearTimeout(timeoutId);
212
- worker.terminate();
213
- this.workers.delete(workerName);
214
- resolve({
215
- match: false,
216
- executionTime: 0,
217
- timeout: false,
218
- error: `Worker error: ${error.message || 'Unknown error'}`
219
- });
220
- };
221
-
222
- worker.postMessage(task);
223
- } catch (error) {
224
- resolve({
225
- match: false,
226
- executionTime: 0,
227
- timeout: false,
228
- error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}`
229
- });
230
- }
231
- });
232
- }
233
-
234
- /**
235
- * Generates the Web Worker code
236
- */
237
- private generateWorkerCode(): string {
238
- return `
239
- self.addEventListener('message', function(event) {
240
- const task = event.data;
241
-
242
- if (task.type === 'regex-test') {
243
- const { pattern, text, timeout } = task.data;
244
- const startTime = performance.now();
245
-
246
- try {
247
- const regex = new RegExp(pattern, 'g');
248
- const matches = [];
249
- let match;
250
-
251
- while ((match = regex.exec(text)) !== null) {
252
- matches.push([...match]);
253
-
254
- // Prevention of infinite loops
255
- if (matches.length > 1000) {
256
- throw new Error('Too many matches - possible infinite loop');
257
- }
258
- }
259
-
260
- const groups = {};
261
- if (matches.length > 0) {
262
- const firstMatch = matches[0];
263
- for (let i = 1; i < firstMatch.length; i++) {
264
- groups[\`group\${i}\`] = firstMatch[i];
265
- }
266
- }
267
-
268
- self.postMessage({
269
- id: task.id,
270
- type: 'regex-result',
271
- data: {
272
- match: matches.length > 0,
273
- matches,
274
- groups,
275
- executionTime: performance.now() - startTime,
276
- timeout: false
277
- }
278
- });
279
- } catch (error) {
280
- self.postMessage({
281
- id: task.id,
282
- type: 'regex-result',
283
- data: {
284
- match: false,
285
- executionTime: performance.now() - startTime,
286
- timeout: false,
287
- error: error.message || 'Execution error'
288
- }
289
- });
290
- }
291
- }
292
- });
293
- `;
294
- }
295
-
296
- /**
297
- * Calculates the complexity of a pattern
298
- */
299
- private calculateComplexity(pattern: string): number {
300
- let complexity = 0;
301
-
302
- // Nested quantifiers increase complexity
303
- complexity += (pattern.match(/\*\*/g) || []).length * 5;
304
- complexity += (pattern.match(/\+\+/g) || []).length * 5;
305
- complexity += (pattern.match(/\?\?/g) || []).length * 3;
306
-
307
- // Lookaheads/lookbehinds
308
- complexity += (pattern.match(/\(\?\=/g) || []).length * 2;
309
- complexity += (pattern.match(/\(\?\!/g) || []).length * 2;
310
- complexity += (pattern.match(/\(\?\</g) || []).length * 3;
311
-
312
- // Nested groups
313
- const openParens = (pattern.match(/\(/g) || []).length;
314
- complexity += openParens * 0.5;
315
-
316
- // Pattern length
317
- complexity += pattern.length * 0.01;
318
-
319
- return Math.round(complexity * 100) / 100;
320
- }
321
-
322
- /**
323
- * Gets numeric risk level
324
- */
325
- private getRiskLevel(risk: 'low' | 'medium' | 'high' | 'critical'): number {
326
- const levels = { 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
327
- return levels[risk] || 0;
328
- }
329
-
330
- /**
331
- * Merges configuration with default values
332
- */
333
- private mergeConfig(config: RegexSecurityConfig): Required<RegexSecurityConfig> {
334
- return {
335
- timeout: config.timeout || 5000,
336
- maxComplexity: config.maxComplexity || 10,
337
- allowBacktracking: config.allowBacktracking || false,
338
- safeMode: config.safeMode || false
339
- };
340
- }
341
-
342
- /**
343
- * Cleans up resources when the service is destroyed
344
- */
345
- ngOnDestroy(): void {
346
- this.workers.forEach(worker => {
347
- worker.terminate();
348
- });
349
- this.workers.clear();
350
- }
351
- }
352
-
353
- /**
354
- * Builder pattern to construct safe regular expressions
355
- */
356
- export class RegexSecurityBuilder {
357
- private patternValue: string = '';
358
- private optionsValue: RegexBuilderOptions = {};
359
- private securityConfigValue: RegexSecurityConfig = {};
360
-
361
- /**
362
- * Defines the base pattern
363
- */
364
- pattern(pattern: string): RegexSecurityBuilder {
365
- this.patternValue = pattern;
366
- return this;
367
- }
368
-
369
- /**
370
- * Appends text to the current pattern
371
- */
372
- append(text: string): RegexSecurityBuilder {
373
- this.patternValue += text;
374
- return this;
375
- }
376
-
377
- /**
378
- * Adds a capturing group
379
- */
380
- group(content: string, name?: string): RegexSecurityBuilder {
381
- if (name) {
382
- this.patternValue += `(?<${name}>${content})`;
383
- } else {
384
- this.patternValue += `(${content})`;
385
- }
386
- return this;
387
- }
388
-
389
- /**
390
- * Adds a non-capturing group
391
- */
392
- nonCapturingGroup(content: string): RegexSecurityBuilder {
393
- this.patternValue += `(?:${content})`;
394
- return this;
395
- }
396
-
397
- /**
398
- * Adds an alternative
399
- */
400
- or(alternative: string): RegexSecurityBuilder {
401
- this.patternValue += `|${alternative}`;
402
- return this;
403
- }
404
-
405
- /**
406
- * Adds a quantifier
407
- */
408
- quantifier(quantifier: '*' | '+' | '?' | '{n}' | '{n,}' | '{n,m}'): RegexSecurityBuilder {
409
- this.patternValue += quantifier;
410
- return this;
411
- }
412
-
413
- /**
414
- * Adds a character set
415
- */
416
- characterSet(chars: string, negate = false): RegexSecurityBuilder {
417
- this.patternValue += `[${negate ? '^' : ''}${chars}]`;
418
- return this;
419
- }
420
-
421
- /**
422
- * Adds a start of line anchor
423
- */
424
- startOfLine(): RegexSecurityBuilder {
425
- this.patternValue += '^';
426
- return this;
427
- }
428
-
429
- /**
430
- * Adds an end of line anchor
431
- */
432
- endOfLine(): RegexSecurityBuilder {
433
- this.patternValue += '$';
434
- return this;
435
- }
436
-
437
- /**
438
- * Configures regular expression options
439
- */
440
- options(options: RegexBuilderOptions): RegexSecurityBuilder {
441
- this.optionsValue = { ...this.optionsValue, ...options };
442
- return this;
443
- }
444
-
445
- /**
446
- * Configures security options
447
- */
448
- security(config: RegexSecurityConfig): RegexSecurityBuilder {
449
- this.securityConfigValue = { ...this.securityConfigValue, ...config };
450
- return this;
451
- }
452
-
453
- /**
454
- * Configures timeout
455
- */
456
- timeout(ms: number): RegexSecurityBuilder {
457
- this.securityConfigValue.timeout = ms;
458
- return this;
459
- }
460
-
461
- /**
462
- * Activates safe mode
463
- */
464
- safeMode(): RegexSecurityBuilder {
465
- this.securityConfigValue.safeMode = true;
466
- return this;
467
- }
468
-
469
- /**
470
- * Builds the final regular expression
471
- */
472
- build(): { pattern: string; options: RegexBuilderOptions; security: RegexSecurityConfig } {
473
- return {
474
- pattern: this.patternValue,
475
- options: this.optionsValue,
476
- security: this.securityConfigValue
477
- };
478
- }
479
-
480
- /**
481
- * Builds and executes the regular expression
482
- */
483
- async execute(text: string, service: RegexSecurityService): Promise<RegexTestResult> {
484
- const { pattern, security } = this.build();
485
- return service.testRegex(pattern, text, security);
486
- }
487
- }