@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.
- package/fesm2022/angular-helpers-security.mjs +407 -0
- package/fesm2022/angular-helpers-security.mjs.map +1 -0
- package/package.json +15 -16
- package/types/angular-helpers-security.d.ts +154 -0
- package/ng-package.json +0 -7
- package/src/index.ts +0 -1
- package/src/interfaces/security.interface.ts +0 -32
- package/src/providers.ts +0 -29
- package/src/services/regex-security.service.ts +0 -487
|
@@ -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.
|
|
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.
|
|
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
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
"
|
|
45
|
-
|
|
46
|
-
"
|
|
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
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
|
-
}
|