@angular-helpers/security 21.0.1 → 21.0.3
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/README.es.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
[Read in English](./README.md)
|
|
2
|
+
|
|
3
|
+
🌐 **Documentación y Demo**: https://gaspar1992.github.io/angular-helpers/
|
|
4
|
+
|
|
5
|
+
# Angular Security Helpers
|
|
6
|
+
|
|
7
|
+
Paquete de seguridad para aplicaciones Angular que previene ataques comunes como ReDoS (Regular Expression Denial of Service) usando Web Workers para ejecución segura.
|
|
8
|
+
|
|
9
|
+
## 🛡️ Características
|
|
10
|
+
|
|
11
|
+
### **Prevención de ReDoS**
|
|
12
|
+
|
|
13
|
+
- **Ejecución en Web Worker**: Las expresiones regulares se ejecutan en un hilo separado.
|
|
14
|
+
- **Timeout Configurable**: Previene ejecuciones infinitas.
|
|
15
|
+
- **Análisis de Complejidad**: Detecta patrones peligrosos antes de la ejecución.
|
|
16
|
+
- **Modo Seguro**: Solo permite patrones verificados como seguros.
|
|
17
|
+
|
|
18
|
+
### **Patrón Builder**
|
|
19
|
+
|
|
20
|
+
- **API Fluida**: Construye expresiones regulares de forma intuitiva.
|
|
21
|
+
- **Encadenamiento de Métodos**: `.pattern().group().quantifier()`
|
|
22
|
+
- **Validación en Tiempo Real**: Análisis de seguridad durante la construcción.
|
|
23
|
+
|
|
24
|
+
## 📦 Instalación
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @angular-helpers/security
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 🚀 Uso Básico
|
|
31
|
+
|
|
32
|
+
### **Configuración**
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { provideSecurity } from '@angular-helpers/security';
|
|
36
|
+
|
|
37
|
+
bootstrapApplication(AppComponent, {
|
|
38
|
+
providers: [
|
|
39
|
+
provideSecurity({
|
|
40
|
+
enableRegexSecurity: true,
|
|
41
|
+
defaultTimeout: 5000,
|
|
42
|
+
safeMode: false,
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### **Inyección de Servicios**
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { RegexSecurityService } from '@angular-helpers/security';
|
|
52
|
+
|
|
53
|
+
export class MyComponent {
|
|
54
|
+
private securityService = inject(RegexSecurityService);
|
|
55
|
+
|
|
56
|
+
async validateInput() {
|
|
57
|
+
const pattern = '(.+)+'; // Patrón potencialmente peligroso
|
|
58
|
+
const text = 'texto de prueba';
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await this.securityService.testRegex(pattern, text, {
|
|
62
|
+
timeout: 5000,
|
|
63
|
+
safeMode: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log('Coincidencia:', result.match);
|
|
67
|
+
console.log('Tiempo de ejecución:', result.executionTime);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error de validación:', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### **Uso del Builder**
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { RegexSecurityService } from '@angular-helpers/security';
|
|
79
|
+
|
|
80
|
+
const { pattern, security } = RegexSecurityService.builder()
|
|
81
|
+
.startOfLine()
|
|
82
|
+
.characterSet('0-9')
|
|
83
|
+
.quantifier('+')
|
|
84
|
+
.endOfLine()
|
|
85
|
+
.timeout(3000)
|
|
86
|
+
.safeMode()
|
|
87
|
+
.build();
|
|
88
|
+
|
|
89
|
+
// Ejecutar usando el servicio
|
|
90
|
+
const result = await RegexSecurityService.builder()
|
|
91
|
+
.pattern('\\d+')
|
|
92
|
+
.timeout(3000)
|
|
93
|
+
.execute('12345', this.securityService);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 📊 Niveles de Riesgo
|
|
97
|
+
|
|
98
|
+
| Nivel | Descripción | Acción |
|
|
99
|
+
| -------------- | -------------------- | ---------------------------------------- |
|
|
100
|
+
| 🟢 **Bajo** | Patrones seguros | Ejecución normal |
|
|
101
|
+
| 🟡 **Medio** | Posible riesgo | Advertencia + timeout |
|
|
102
|
+
| 🟠 **Alto** | Riesgo significativo | Timeout estricto + safe mode recomendado |
|
|
103
|
+
| 🔴 **Crítico** | Patrones peligrosos | Bloqueo por defecto |
|
|
104
|
+
|
|
105
|
+
## 🔧 Configuración Avanzada
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
provideSecurity({
|
|
109
|
+
enableRegexSecurity: true,
|
|
110
|
+
defaultTimeout: 5000,
|
|
111
|
+
safeMode: false,
|
|
112
|
+
maxComplexity: 1000,
|
|
113
|
+
allowCatastrophicPatterns: false,
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 📄 Licencia
|
|
118
|
+
|
|
119
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
[
|
|
1
|
+
[Leer en Español](./README.es.md)
|
|
2
|
+
|
|
3
|
+
🌐 **Documentation & Demo**: https://gaspar1992.github.io/angular-helpers/
|
|
2
4
|
|
|
3
5
|
# Angular Security Helpers
|
|
4
6
|
|
|
@@ -7,12 +9,14 @@ Security package for Angular applications that prevents common attacks like ReDo
|
|
|
7
9
|
## 🛡️ Features
|
|
8
10
|
|
|
9
11
|
### **ReDoS Prevention**
|
|
12
|
+
|
|
10
13
|
- **Web Worker Execution**: Regular expressions are executed in a separate thread.
|
|
11
14
|
- **Configurable Timeout**: Prevents infinite executions.
|
|
12
15
|
- **Complexity Analysis**: Detects dangerous patterns before execution.
|
|
13
16
|
- **Safe Mode**: Only allows patterns verified as safe.
|
|
14
17
|
|
|
15
18
|
### **Builder Pattern**
|
|
19
|
+
|
|
16
20
|
- **Fluent API**: Intuitively build regular expressions.
|
|
17
21
|
- **Method Chaining**: `.pattern().group().quantifier()`
|
|
18
22
|
- **Real-time Validation**: Security analysis during construction.
|
|
@@ -35,20 +39,20 @@ bootstrapApplication(AppComponent, {
|
|
|
35
39
|
provideSecurity({
|
|
36
40
|
enableRegexSecurity: true,
|
|
37
41
|
defaultTimeout: 5000,
|
|
38
|
-
safeMode: false
|
|
39
|
-
})
|
|
40
|
-
]
|
|
42
|
+
safeMode: false,
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
41
45
|
});
|
|
42
46
|
```
|
|
43
47
|
|
|
44
48
|
### **Service Injection**
|
|
45
49
|
|
|
46
50
|
```typescript
|
|
47
|
-
import { RegexSecurityService } from '@angular-helpers/security';
|
|
51
|
+
import { RegexSecurityService, inject } from '@angular-helpers/security';
|
|
48
52
|
|
|
49
53
|
@Component({...})
|
|
50
54
|
export class MyComponent {
|
|
51
|
-
|
|
55
|
+
private regexSecurity = inject(RegexSecurityService);
|
|
52
56
|
}
|
|
53
57
|
```
|
|
54
58
|
|
|
@@ -63,7 +67,7 @@ async testEmail(email: string): Promise<boolean> {
|
|
|
63
67
|
email,
|
|
64
68
|
{ timeout: 3000 }
|
|
65
69
|
);
|
|
66
|
-
|
|
70
|
+
|
|
67
71
|
return result.match;
|
|
68
72
|
}
|
|
69
73
|
```
|
|
@@ -71,11 +75,10 @@ async testEmail(email: string): Promise<boolean> {
|
|
|
71
75
|
### **2. Builder Pattern**
|
|
72
76
|
|
|
73
77
|
```typescript
|
|
74
|
-
import {
|
|
78
|
+
import { RegexSecurityService } from '@angular-helpers/security';
|
|
75
79
|
|
|
76
80
|
// Fluent regular expression construction
|
|
77
|
-
const
|
|
78
|
-
.builder()
|
|
81
|
+
const { pattern, security } = RegexSecurityService.builder()
|
|
79
82
|
.startOfLine()
|
|
80
83
|
.characterSet('a-zA-Z0-9._%+-')
|
|
81
84
|
.quantifier('+')
|
|
@@ -91,8 +94,7 @@ const emailRegex = RegexSecurityBuilder
|
|
|
91
94
|
.build();
|
|
92
95
|
|
|
93
96
|
// Direct execution
|
|
94
|
-
const result = await
|
|
95
|
-
.builder()
|
|
97
|
+
const result = await RegexSecurityService.builder()
|
|
96
98
|
.pattern('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')
|
|
97
99
|
.timeout(3000)
|
|
98
100
|
.execute(email, this.regexSecurity);
|
|
@@ -103,16 +105,16 @@ const result = await RegexSecurityBuilder
|
|
|
103
105
|
```typescript
|
|
104
106
|
async analyzePattern(pattern: string): Promise<void> {
|
|
105
107
|
const analysis = await this.regexSecurity.analyzePatternSecurity(pattern);
|
|
106
|
-
|
|
108
|
+
|
|
107
109
|
if (!analysis.safe) {
|
|
108
110
|
console.warn('⚠️ Pattern not safe:', analysis.warnings);
|
|
109
111
|
console.info('💡 Recommendations:', analysis.recommendations);
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
if (analysis.risk === 'critical') {
|
|
112
114
|
throw new Error('Pattern rejected due to critical security risk');
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
console.log(`✅ Pattern complexity: ${analysis.complexity}`);
|
|
117
119
|
console.log(`🎯 Risk level: ${analysis.risk}`);
|
|
118
120
|
}
|
|
@@ -124,29 +126,29 @@ async analyzePattern(pattern: string): Promise<void> {
|
|
|
124
126
|
@Component({...})
|
|
125
127
|
export class FormValidationComponent {
|
|
126
128
|
constructor(private regexSecurity: RegexSecurityService) {}
|
|
127
|
-
|
|
129
|
+
|
|
128
130
|
async validateUsername(username: string): Promise<boolean> {
|
|
129
131
|
const result = await this.regexSecurity.testRegex(
|
|
130
132
|
'^[a-zA-Z0-9_]{3,20}$',
|
|
131
133
|
username,
|
|
132
134
|
{ timeout: 1000, safeMode: true }
|
|
133
135
|
);
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
if (result.timeout) {
|
|
136
138
|
throw new Error('Username validation timeout - possible ReDoS attack');
|
|
137
139
|
}
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
if (result.error) {
|
|
140
142
|
console.error('Validation error:', result.error);
|
|
141
143
|
return false;
|
|
142
144
|
}
|
|
143
|
-
|
|
145
|
+
|
|
144
146
|
return result.match;
|
|
145
147
|
}
|
|
146
|
-
|
|
148
|
+
|
|
147
149
|
async validateComplexInput(input: string): Promise<boolean> {
|
|
148
150
|
// Builder pattern for complex validation
|
|
149
|
-
const result = await
|
|
151
|
+
const result = await RegexSecurityService
|
|
150
152
|
.builder()
|
|
151
153
|
.startOfLine()
|
|
152
154
|
.nonCapturingGroup('[a-zA-Z]') // First letter
|
|
@@ -155,7 +157,7 @@ export class FormValidationComponent {
|
|
|
155
157
|
.endOfLine()
|
|
156
158
|
.timeout(2000)
|
|
157
159
|
.execute(input, this.regexSecurity);
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
return result.match;
|
|
160
162
|
}
|
|
161
163
|
}
|
|
@@ -167,10 +169,10 @@ export class FormValidationComponent {
|
|
|
167
169
|
|
|
168
170
|
```typescript
|
|
169
171
|
interface RegexSecurityConfig {
|
|
170
|
-
timeout?: number;
|
|
171
|
-
maxComplexity?: number;
|
|
172
|
+
timeout?: number; // Timeout in ms (default: 5000)
|
|
173
|
+
maxComplexity?: number; // Max complexity (default: 10)
|
|
172
174
|
allowBacktracking?: boolean; // Allow backtracking (default: false)
|
|
173
|
-
safeMode?: boolean;
|
|
175
|
+
safeMode?: boolean; // Safe mode (default: false)
|
|
174
176
|
}
|
|
175
177
|
```
|
|
176
178
|
|
|
@@ -178,12 +180,12 @@ interface RegexSecurityConfig {
|
|
|
178
180
|
|
|
179
181
|
```typescript
|
|
180
182
|
interface RegexBuilderOptions {
|
|
181
|
-
global?: boolean;
|
|
182
|
-
ignoreCase?: boolean;
|
|
183
|
-
multiline?: boolean;
|
|
184
|
-
dotAll?: boolean;
|
|
185
|
-
unicode?: boolean;
|
|
186
|
-
sticky?: boolean;
|
|
183
|
+
global?: boolean; // 'g' flag
|
|
184
|
+
ignoreCase?: boolean; // 'i' flag
|
|
185
|
+
multiline?: boolean; // 'm' flag
|
|
186
|
+
dotAll?: boolean; // 's' flag
|
|
187
|
+
unicode?: boolean; // 'u' flag
|
|
188
|
+
sticky?: boolean; // 'y' flag
|
|
187
189
|
}
|
|
188
190
|
```
|
|
189
191
|
|
|
@@ -220,12 +222,12 @@ The service automatically detects:
|
|
|
220
222
|
|
|
221
223
|
```typescript
|
|
222
224
|
interface RegexTestResult {
|
|
223
|
-
match: boolean;
|
|
225
|
+
match: boolean; // If there was a match
|
|
224
226
|
matches?: RegExpMatchArray[]; // All matches found
|
|
225
227
|
groups?: { [key: string]: string }; // Captured groups
|
|
226
|
-
executionTime: number;
|
|
227
|
-
timeout: boolean;
|
|
228
|
-
error?: string;
|
|
228
|
+
executionTime: number; // Execution time in ms
|
|
229
|
+
timeout: boolean; // If there was a timeout
|
|
230
|
+
error?: string; // Error if one occurred
|
|
229
231
|
}
|
|
230
232
|
```
|
|
231
233
|
|
|
@@ -233,10 +235,10 @@ interface RegexTestResult {
|
|
|
233
235
|
|
|
234
236
|
```typescript
|
|
235
237
|
interface RegexSecurityResult {
|
|
236
|
-
safe: boolean;
|
|
237
|
-
complexity: number;
|
|
238
|
+
safe: boolean; // If the pattern is safe
|
|
239
|
+
complexity: number; // Complexity level (0-∞)
|
|
238
240
|
risk: 'low' | 'medium' | 'high' | 'critical';
|
|
239
|
-
warnings: string[];
|
|
241
|
+
warnings: string[]; // Security warnings
|
|
240
242
|
recommendations: string[]; // Improvement recommendations
|
|
241
243
|
}
|
|
242
244
|
```
|
|
@@ -250,27 +252,27 @@ import { AbstractControl, ValidationErrors } from '@angular/forms';
|
|
|
250
252
|
|
|
251
253
|
export class SecurityValidators {
|
|
252
254
|
constructor(private regexSecurity: RegexSecurityService) {}
|
|
253
|
-
|
|
255
|
+
|
|
254
256
|
async securePattern(pattern: string, config?: RegexSecurityConfig) {
|
|
255
257
|
return async (control: AbstractControl): Promise<ValidationErrors | null> => {
|
|
256
258
|
const value = control.value;
|
|
257
|
-
|
|
259
|
+
|
|
258
260
|
if (!value) return null;
|
|
259
|
-
|
|
261
|
+
|
|
260
262
|
try {
|
|
261
263
|
const result = await this.regexSecurity.testRegex(pattern, value, config);
|
|
262
|
-
|
|
264
|
+
|
|
263
265
|
if (!result.match) {
|
|
264
266
|
return { securePattern: { value, reason: 'Pattern does not match' } };
|
|
265
267
|
}
|
|
266
|
-
|
|
268
|
+
|
|
267
269
|
if (result.timeout) {
|
|
268
270
|
return { securePattern: { value, reason: 'Pattern execution timeout' } };
|
|
269
271
|
}
|
|
270
|
-
|
|
272
|
+
|
|
271
273
|
return null;
|
|
272
274
|
} catch (error) {
|
|
273
|
-
return { securePattern: { value, reason: error.message } };
|
|
275
|
+
return { securePattern: { value, reason: (error as Error).message } };
|
|
274
276
|
}
|
|
275
277
|
};
|
|
276
278
|
}
|
|
@@ -282,11 +284,9 @@ export class SecurityValidators {
|
|
|
282
284
|
```typescript
|
|
283
285
|
@Component({...})
|
|
284
286
|
export class SecureFormComponent {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
) {}
|
|
289
|
-
|
|
287
|
+
private regexSecurity = inject(RegexSecurityService);
|
|
288
|
+
private securityValidators = inject(SecurityValidators);
|
|
289
|
+
|
|
290
290
|
emailValidator = this.securityValidators.securePattern(
|
|
291
291
|
'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
|
|
292
292
|
{ timeout: 3000, safeMode: true }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DestroyRef, Injectable } from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, Injectable, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Security service for regular expressions that prevents ReDoS
|
|
@@ -28,14 +28,14 @@ class RegexSecurityService {
|
|
|
28
28
|
match: false,
|
|
29
29
|
executionTime: performance.now() - startTime,
|
|
30
30
|
timeout: false,
|
|
31
|
-
error: `Pattern rejected: ${securityCheck.warnings.join(', ')}
|
|
31
|
+
error: `Pattern rejected: ${securityCheck.warnings.join(', ')}`,
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
// Execute in Web Worker with timeout
|
|
35
35
|
const result = await this.executeInWorker(pattern, text, finalConfig);
|
|
36
36
|
return {
|
|
37
37
|
...result,
|
|
38
|
-
executionTime: performance.now() - startTime
|
|
38
|
+
executionTime: performance.now() - startTime,
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
catch (error) {
|
|
@@ -43,7 +43,7 @@ class RegexSecurityService {
|
|
|
43
43
|
match: false,
|
|
44
44
|
executionTime: performance.now() - startTime,
|
|
45
45
|
timeout: false,
|
|
46
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
46
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -57,7 +57,11 @@ class RegexSecurityService {
|
|
|
57
57
|
let risk = 'low';
|
|
58
58
|
// Analysis of dangerous patterns
|
|
59
59
|
const dangerousPatterns = [
|
|
60
|
-
{
|
|
60
|
+
{
|
|
61
|
+
pattern: /\*\*/,
|
|
62
|
+
risk: 'high',
|
|
63
|
+
message: 'Nested quantifiers (catastrophic backtracking)',
|
|
64
|
+
},
|
|
61
65
|
{ pattern: /\+\+/, risk: 'high', message: 'Nested plus quantifiers' },
|
|
62
66
|
{ pattern: /\(\?\=/, risk: 'medium', message: 'Lookahead assertions' },
|
|
63
67
|
{ pattern: /\(\?\!/, risk: 'medium', message: 'Negative lookahead' },
|
|
@@ -65,8 +69,16 @@ class RegexSecurityService {
|
|
|
65
69
|
{ pattern: /\(\?\</, risk: 'high', message: 'Lookbehind assertions' },
|
|
66
70
|
{ pattern: /\(\?\(\?\)/, risk: 'critical', message: 'Recursive patterns' },
|
|
67
71
|
{ pattern: /(\{(\d+,)?\d+\})/, risk: 'medium', message: 'Quantified repetition' },
|
|
68
|
-
{
|
|
69
|
-
|
|
72
|
+
{
|
|
73
|
+
pattern: /(\.\*)|(\.+)|(\.\?)/,
|
|
74
|
+
risk: 'medium',
|
|
75
|
+
message: 'Greedy quantifiers with dot',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
pattern: /(\[.*\*.*\])|(\[.*\+.*\])/,
|
|
79
|
+
risk: 'medium',
|
|
80
|
+
message: 'Character classes with quantifiers',
|
|
81
|
+
},
|
|
70
82
|
];
|
|
71
83
|
// Calculate complexity
|
|
72
84
|
complexity = this.calculateComplexity(pattern);
|
|
@@ -96,7 +108,7 @@ class RegexSecurityService {
|
|
|
96
108
|
complexity,
|
|
97
109
|
risk,
|
|
98
110
|
warnings,
|
|
99
|
-
recommendations
|
|
111
|
+
recommendations,
|
|
100
112
|
};
|
|
101
113
|
}
|
|
102
114
|
/**
|
|
@@ -118,8 +130,8 @@ class RegexSecurityService {
|
|
|
118
130
|
data: {
|
|
119
131
|
pattern,
|
|
120
132
|
text,
|
|
121
|
-
timeout: config.timeout || 5000
|
|
122
|
-
}
|
|
133
|
+
timeout: config.timeout || 5000,
|
|
134
|
+
},
|
|
123
135
|
};
|
|
124
136
|
// Timeout for execution
|
|
125
137
|
const timeoutId = setTimeout(() => {
|
|
@@ -129,7 +141,7 @@ class RegexSecurityService {
|
|
|
129
141
|
match: false,
|
|
130
142
|
executionTime: 0,
|
|
131
143
|
timeout: true,
|
|
132
|
-
error: 'Execution timeout'
|
|
144
|
+
error: 'Execution timeout',
|
|
133
145
|
});
|
|
134
146
|
}, config.timeout || 5000);
|
|
135
147
|
worker.onmessage = (event) => {
|
|
@@ -148,7 +160,7 @@ class RegexSecurityService {
|
|
|
148
160
|
match: false,
|
|
149
161
|
executionTime: 0,
|
|
150
162
|
timeout: false,
|
|
151
|
-
error: `Worker error: ${error.message || 'Unknown error'}
|
|
163
|
+
error: `Worker error: ${error.message || 'Unknown error'}`,
|
|
152
164
|
});
|
|
153
165
|
};
|
|
154
166
|
worker.postMessage(task);
|
|
@@ -158,7 +170,7 @@ class RegexSecurityService {
|
|
|
158
170
|
match: false,
|
|
159
171
|
executionTime: 0,
|
|
160
172
|
timeout: false,
|
|
161
|
-
error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}
|
|
173
|
+
error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
162
174
|
});
|
|
163
175
|
}
|
|
164
176
|
});
|
|
@@ -248,7 +260,7 @@ class RegexSecurityService {
|
|
|
248
260
|
* Gets numeric risk level
|
|
249
261
|
*/
|
|
250
262
|
getRiskLevel(risk) {
|
|
251
|
-
const levels = {
|
|
263
|
+
const levels = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
252
264
|
return levels[risk] || 0;
|
|
253
265
|
}
|
|
254
266
|
/**
|
|
@@ -259,14 +271,14 @@ class RegexSecurityService {
|
|
|
259
271
|
timeout: config.timeout || 5000,
|
|
260
272
|
maxComplexity: config.maxComplexity || 10,
|
|
261
273
|
allowBacktracking: config.allowBacktracking || false,
|
|
262
|
-
safeMode: config.safeMode || false
|
|
274
|
+
safeMode: config.safeMode || false,
|
|
263
275
|
};
|
|
264
276
|
}
|
|
265
277
|
/**
|
|
266
278
|
* Cleans up resources when the service is destroyed
|
|
267
279
|
*/
|
|
268
280
|
ngOnDestroy() {
|
|
269
|
-
this.workers.forEach(worker => {
|
|
281
|
+
this.workers.forEach((worker) => {
|
|
270
282
|
worker.terminate();
|
|
271
283
|
});
|
|
272
284
|
this.workers.clear();
|
|
@@ -387,7 +399,7 @@ class RegexSecurityBuilder {
|
|
|
387
399
|
return {
|
|
388
400
|
pattern: this.patternValue,
|
|
389
401
|
options: this.optionsValue,
|
|
390
|
-
security: this.securityConfigValue
|
|
402
|
+
security: this.securityConfigValue,
|
|
391
403
|
};
|
|
392
404
|
}
|
|
393
405
|
/**
|
|
@@ -399,9 +411,25 @@ class RegexSecurityBuilder {
|
|
|
399
411
|
}
|
|
400
412
|
}
|
|
401
413
|
|
|
414
|
+
const defaultSecurityConfig = {
|
|
415
|
+
enableRegexSecurity: true,
|
|
416
|
+
defaultTimeout: 5000,
|
|
417
|
+
safeMode: false,
|
|
418
|
+
};
|
|
419
|
+
function provideSecurity(config = {}) {
|
|
420
|
+
const mergedConfig = { ...defaultSecurityConfig, ...config };
|
|
421
|
+
const providers = [];
|
|
422
|
+
if (mergedConfig.enableRegexSecurity) {
|
|
423
|
+
providers.push(RegexSecurityService);
|
|
424
|
+
}
|
|
425
|
+
return makeEnvironmentProviders(providers);
|
|
426
|
+
}
|
|
427
|
+
function provideRegexSecurity() {
|
|
428
|
+
return makeEnvironmentProviders([RegexSecurityService]);
|
|
429
|
+
}
|
|
430
|
+
|
|
402
431
|
/**
|
|
403
432
|
* Generated bundle index. Do not edit.
|
|
404
433
|
*/
|
|
405
434
|
|
|
406
|
-
export { RegexSecurityBuilder, RegexSecurityService };
|
|
407
|
-
//# sourceMappingURL=angular-helpers-security.mjs.map
|
|
435
|
+
export { RegexSecurityBuilder, RegexSecurityService, defaultSecurityConfig, provideRegexSecurity, provideSecurity };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/security",
|
|
3
|
-
"version": "21.0.
|
|
3
|
+
"version": "21.0.3",
|
|
4
4
|
"description": "Angular security helpers for preventing ReDoS and other security vulnerabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@angular/common": "^21.0.0",
|
|
29
29
|
"@angular/core": "^21.0.0",
|
|
30
|
-
"@angular-helpers/browser-web-apis": "21.0.1",
|
|
31
30
|
"rxjs": "^7.0.0 || ^8.0.0"
|
|
32
31
|
},
|
|
33
32
|
"dependencies": {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
+
import { EnvironmentProviders } from '@angular/core';
|
|
2
3
|
|
|
3
4
|
interface RegexSecurityConfig {
|
|
4
5
|
timeout?: number;
|
|
@@ -150,5 +151,14 @@ declare class RegexSecurityBuilder {
|
|
|
150
151
|
execute(text: string, service: RegexSecurityService): Promise<RegexTestResult>;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
interface SecurityConfig {
|
|
155
|
+
enableRegexSecurity?: boolean;
|
|
156
|
+
defaultTimeout?: number;
|
|
157
|
+
safeMode?: boolean;
|
|
158
|
+
}
|
|
159
|
+
declare const defaultSecurityConfig: SecurityConfig;
|
|
160
|
+
declare function provideSecurity(config?: SecurityConfig): EnvironmentProviders;
|
|
161
|
+
declare function provideRegexSecurity(): EnvironmentProviders;
|
|
162
|
+
|
|
163
|
+
export { RegexSecurityBuilder, RegexSecurityService, defaultSecurityConfig, provideRegexSecurity, provideSecurity };
|
|
164
|
+
export type { RegexBuilderOptions, RegexSecurityConfig, RegexSecurityResult, RegexTestResult, SecurityConfig };
|
|
@@ -1 +0,0 @@
|
|
|
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":"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"}
|