@angular-helpers/security 21.0.2 → 21.0.4
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 +171 -0
- package/README.md +102 -50
- package/fesm2022/angular-helpers-security.mjs +125 -19
- package/package.json +1 -2
- package/types/angular-helpers-security.d.ts +38 -2
- package/fesm2022/angular-helpers-security.mjs.map +0 -1
package/README.es.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
### **Web Crypto API**
|
|
19
|
+
|
|
20
|
+
- **Cifrado/Descifrado**: Soporte AES-GCM para manejo seguro de datos
|
|
21
|
+
- **Hashing**: SHA-256 y otros algoritmos
|
|
22
|
+
- **Gestión de Claves**: Generar, importar y exportar claves criptográficas
|
|
23
|
+
- **Aleatorio Seguro**: Valores aleatorios criptográficamente seguros
|
|
24
|
+
|
|
25
|
+
### **Patrón Builder**
|
|
26
|
+
|
|
27
|
+
- **API Fluida**: Construye expresiones regulares de forma intuitiva.
|
|
28
|
+
- **Encadenamiento de Métodos**: `.pattern().group().quantifier()`
|
|
29
|
+
- **Validación en Tiempo Real**: Análisis de seguridad durante la construcción.
|
|
30
|
+
|
|
31
|
+
## 📦 Instalación
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @angular-helpers/security
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 🚀 Uso Básico
|
|
38
|
+
|
|
39
|
+
### **Configuración**
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { provideSecurity } from '@angular-helpers/security';
|
|
43
|
+
|
|
44
|
+
bootstrapApplication(AppComponent, {
|
|
45
|
+
providers: [
|
|
46
|
+
provideSecurity({
|
|
47
|
+
enableRegexSecurity: true,
|
|
48
|
+
defaultTimeout: 5000,
|
|
49
|
+
safeMode: false,
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### **Inyección de Servicios**
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { RegexSecurityService } from '@angular-helpers/security';
|
|
59
|
+
|
|
60
|
+
export class MyComponent {
|
|
61
|
+
private securityService = inject(RegexSecurityService);
|
|
62
|
+
|
|
63
|
+
async validateInput() {
|
|
64
|
+
const pattern = '(.+)+'; // Patrón potencialmente peligroso
|
|
65
|
+
const text = 'texto de prueba';
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = await this.securityService.testRegex(pattern, text, {
|
|
69
|
+
timeout: 5000,
|
|
70
|
+
safeMode: true,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log('Coincidencia:', result.match);
|
|
74
|
+
console.log('Tiempo de ejecución:', result.executionTime);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('Error de validación:', error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### **Uso del Builder**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { RegexSecurityService } from '@angular-helpers/security';
|
|
86
|
+
|
|
87
|
+
const { pattern, security } = RegexSecurityService.builder()
|
|
88
|
+
.startOfLine()
|
|
89
|
+
.characterSet('0-9')
|
|
90
|
+
.quantifier('+')
|
|
91
|
+
.endOfLine()
|
|
92
|
+
.timeout(3000)
|
|
93
|
+
.safeMode()
|
|
94
|
+
.build();
|
|
95
|
+
|
|
96
|
+
// Ejecutar usando el servicio
|
|
97
|
+
const result = await RegexSecurityService.builder()
|
|
98
|
+
.pattern('\\d+')
|
|
99
|
+
.timeout(3000)
|
|
100
|
+
.execute('12345', this.securityService);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### **WebCryptoService**
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { WebCryptoService } from '@angular-helpers/security';
|
|
107
|
+
|
|
108
|
+
export class SecureStorageComponent {
|
|
109
|
+
private cryptoService = inject(WebCryptoService);
|
|
110
|
+
|
|
111
|
+
async hashPassword(password: string): Promise<string> {
|
|
112
|
+
return await this.cryptoService.hash(password, 'SHA-256');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async encryptData(
|
|
116
|
+
data: string,
|
|
117
|
+
): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array; key: CryptoKey }> {
|
|
118
|
+
const key = await this.cryptoService.generateAesKey(256);
|
|
119
|
+
const { ciphertext, iv } = await this.cryptoService.encryptAes(key, data);
|
|
120
|
+
return { ciphertext, iv, key };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async decryptData(ciphertext: ArrayBuffer, iv: Uint8Array, key: CryptoKey): Promise<string> {
|
|
124
|
+
return await this.cryptoService.decryptAes(key, ciphertext, iv);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async exportKeyForStorage(key: CryptoKey): Promise<JsonWebKey> {
|
|
128
|
+
return await this.cryptoService.exportKey(key);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async importKeyFromStorage(jwk: JsonWebKey): Promise<CryptoKey> {
|
|
132
|
+
return await this.cryptoService.importAesKey(jwk);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
generateSecureToken(length: number = 32): string {
|
|
136
|
+
const bytes = this.cryptoService.generateRandomBytes(length);
|
|
137
|
+
return Array.from(bytes)
|
|
138
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
139
|
+
.join('');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
generateUUID(): string {
|
|
143
|
+
return this.cryptoService.randomUUID();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 📊 Niveles de Riesgo
|
|
149
|
+
|
|
150
|
+
| Nivel | Descripción | Acción |
|
|
151
|
+
| -------------- | -------------------- | ---------------------------------------- |
|
|
152
|
+
| 🟢 **Bajo** | Patrones seguros | Ejecución normal |
|
|
153
|
+
| 🟡 **Medio** | Posible riesgo | Advertencia + timeout |
|
|
154
|
+
| 🟠 **Alto** | Riesgo significativo | Timeout estricto + safe mode recomendado |
|
|
155
|
+
| 🔴 **Crítico** | Patrones peligrosos | Bloqueo por defecto |
|
|
156
|
+
|
|
157
|
+
## 🔧 Configuración Avanzada
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
provideSecurity({
|
|
161
|
+
enableRegexSecurity: true,
|
|
162
|
+
defaultTimeout: 5000,
|
|
163
|
+
safeMode: false,
|
|
164
|
+
maxComplexity: 1000,
|
|
165
|
+
allowCatastrophicPatterns: false,
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 📄 Licencia
|
|
170
|
+
|
|
171
|
+
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,21 @@ 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
|
|
|
18
|
+
### **Web Crypto API**
|
|
19
|
+
|
|
20
|
+
- **Encryption/Decryption**: AES-GCM support for secure data handling
|
|
21
|
+
- **Hashing**: SHA-256 and other algorithms
|
|
22
|
+
- **Key Management**: Generate, import, and export cryptographic keys
|
|
23
|
+
- **Secure Random**: Cryptographically secure random values
|
|
24
|
+
|
|
15
25
|
### **Builder Pattern**
|
|
26
|
+
|
|
16
27
|
- **Fluent API**: Intuitively build regular expressions.
|
|
17
28
|
- **Method Chaining**: `.pattern().group().quantifier()`
|
|
18
29
|
- **Real-time Validation**: Security analysis during construction.
|
|
@@ -35,20 +46,20 @@ bootstrapApplication(AppComponent, {
|
|
|
35
46
|
provideSecurity({
|
|
36
47
|
enableRegexSecurity: true,
|
|
37
48
|
defaultTimeout: 5000,
|
|
38
|
-
safeMode: false
|
|
39
|
-
})
|
|
40
|
-
]
|
|
49
|
+
safeMode: false,
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
41
52
|
});
|
|
42
53
|
```
|
|
43
54
|
|
|
44
55
|
### **Service Injection**
|
|
45
56
|
|
|
46
57
|
```typescript
|
|
47
|
-
import { RegexSecurityService } from '@angular-helpers/security';
|
|
58
|
+
import { RegexSecurityService, inject } from '@angular-helpers/security';
|
|
48
59
|
|
|
49
60
|
@Component({...})
|
|
50
61
|
export class MyComponent {
|
|
51
|
-
|
|
62
|
+
private regexSecurity = inject(RegexSecurityService);
|
|
52
63
|
}
|
|
53
64
|
```
|
|
54
65
|
|
|
@@ -63,7 +74,7 @@ async testEmail(email: string): Promise<boolean> {
|
|
|
63
74
|
email,
|
|
64
75
|
{ timeout: 3000 }
|
|
65
76
|
);
|
|
66
|
-
|
|
77
|
+
|
|
67
78
|
return result.match;
|
|
68
79
|
}
|
|
69
80
|
```
|
|
@@ -71,11 +82,10 @@ async testEmail(email: string): Promise<boolean> {
|
|
|
71
82
|
### **2. Builder Pattern**
|
|
72
83
|
|
|
73
84
|
```typescript
|
|
74
|
-
import {
|
|
85
|
+
import { RegexSecurityService } from '@angular-helpers/security';
|
|
75
86
|
|
|
76
87
|
// Fluent regular expression construction
|
|
77
|
-
const
|
|
78
|
-
.builder()
|
|
88
|
+
const { pattern, security } = RegexSecurityService.builder()
|
|
79
89
|
.startOfLine()
|
|
80
90
|
.characterSet('a-zA-Z0-9._%+-')
|
|
81
91
|
.quantifier('+')
|
|
@@ -91,8 +101,7 @@ const emailRegex = RegexSecurityBuilder
|
|
|
91
101
|
.build();
|
|
92
102
|
|
|
93
103
|
// Direct execution
|
|
94
|
-
const result = await
|
|
95
|
-
.builder()
|
|
104
|
+
const result = await RegexSecurityService.builder()
|
|
96
105
|
.pattern('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')
|
|
97
106
|
.timeout(3000)
|
|
98
107
|
.execute(email, this.regexSecurity);
|
|
@@ -103,16 +112,16 @@ const result = await RegexSecurityBuilder
|
|
|
103
112
|
```typescript
|
|
104
113
|
async analyzePattern(pattern: string): Promise<void> {
|
|
105
114
|
const analysis = await this.regexSecurity.analyzePatternSecurity(pattern);
|
|
106
|
-
|
|
115
|
+
|
|
107
116
|
if (!analysis.safe) {
|
|
108
117
|
console.warn('⚠️ Pattern not safe:', analysis.warnings);
|
|
109
118
|
console.info('💡 Recommendations:', analysis.recommendations);
|
|
110
|
-
|
|
119
|
+
|
|
111
120
|
if (analysis.risk === 'critical') {
|
|
112
121
|
throw new Error('Pattern rejected due to critical security risk');
|
|
113
122
|
}
|
|
114
123
|
}
|
|
115
|
-
|
|
124
|
+
|
|
116
125
|
console.log(`✅ Pattern complexity: ${analysis.complexity}`);
|
|
117
126
|
console.log(`🎯 Risk level: ${analysis.risk}`);
|
|
118
127
|
}
|
|
@@ -124,29 +133,29 @@ async analyzePattern(pattern: string): Promise<void> {
|
|
|
124
133
|
@Component({...})
|
|
125
134
|
export class FormValidationComponent {
|
|
126
135
|
constructor(private regexSecurity: RegexSecurityService) {}
|
|
127
|
-
|
|
136
|
+
|
|
128
137
|
async validateUsername(username: string): Promise<boolean> {
|
|
129
138
|
const result = await this.regexSecurity.testRegex(
|
|
130
139
|
'^[a-zA-Z0-9_]{3,20}$',
|
|
131
140
|
username,
|
|
132
141
|
{ timeout: 1000, safeMode: true }
|
|
133
142
|
);
|
|
134
|
-
|
|
143
|
+
|
|
135
144
|
if (result.timeout) {
|
|
136
145
|
throw new Error('Username validation timeout - possible ReDoS attack');
|
|
137
146
|
}
|
|
138
|
-
|
|
147
|
+
|
|
139
148
|
if (result.error) {
|
|
140
149
|
console.error('Validation error:', result.error);
|
|
141
150
|
return false;
|
|
142
151
|
}
|
|
143
|
-
|
|
152
|
+
|
|
144
153
|
return result.match;
|
|
145
154
|
}
|
|
146
|
-
|
|
155
|
+
|
|
147
156
|
async validateComplexInput(input: string): Promise<boolean> {
|
|
148
157
|
// Builder pattern for complex validation
|
|
149
|
-
const result = await
|
|
158
|
+
const result = await RegexSecurityService
|
|
150
159
|
.builder()
|
|
151
160
|
.startOfLine()
|
|
152
161
|
.nonCapturingGroup('[a-zA-Z]') // First letter
|
|
@@ -155,22 +164,67 @@ export class FormValidationComponent {
|
|
|
155
164
|
.endOfLine()
|
|
156
165
|
.timeout(2000)
|
|
157
166
|
.execute(input, this.regexSecurity);
|
|
158
|
-
|
|
167
|
+
|
|
159
168
|
return result.match;
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
```
|
|
163
172
|
|
|
173
|
+
### **WebCryptoService**
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { WebCryptoService } from '@angular-helpers/security';
|
|
177
|
+
|
|
178
|
+
export class SecureStorageComponent {
|
|
179
|
+
private cryptoService = inject(WebCryptoService);
|
|
180
|
+
|
|
181
|
+
async hashPassword(password: string): Promise<string> {
|
|
182
|
+
return await this.cryptoService.hash(password, 'SHA-256');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async encryptData(
|
|
186
|
+
data: string,
|
|
187
|
+
): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array; key: CryptoKey }> {
|
|
188
|
+
const key = await this.cryptoService.generateAesKey(256);
|
|
189
|
+
const { ciphertext, iv } = await this.cryptoService.encryptAes(key, data);
|
|
190
|
+
return { ciphertext, iv, key };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async decryptData(ciphertext: ArrayBuffer, iv: Uint8Array, key: CryptoKey): Promise<string> {
|
|
194
|
+
return await this.cryptoService.decryptAes(key, ciphertext, iv);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async exportKeyForStorage(key: CryptoKey): Promise<JsonWebKey> {
|
|
198
|
+
return await this.cryptoService.exportKey(key);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async importKeyFromStorage(jwk: JsonWebKey): Promise<CryptoKey> {
|
|
202
|
+
return await this.cryptoService.importAesKey(jwk);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
generateSecureToken(length: number = 32): string {
|
|
206
|
+
const bytes = this.cryptoService.generateRandomBytes(length);
|
|
207
|
+
return Array.from(bytes)
|
|
208
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
209
|
+
.join('');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
generateUUID(): string {
|
|
213
|
+
return this.cryptoService.randomUUID();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
164
218
|
## 🔧 Advanced Configuration
|
|
165
219
|
|
|
166
220
|
### **Security Options**
|
|
167
221
|
|
|
168
222
|
```typescript
|
|
169
223
|
interface RegexSecurityConfig {
|
|
170
|
-
timeout?: number;
|
|
171
|
-
maxComplexity?: number;
|
|
224
|
+
timeout?: number; // Timeout in ms (default: 5000)
|
|
225
|
+
maxComplexity?: number; // Max complexity (default: 10)
|
|
172
226
|
allowBacktracking?: boolean; // Allow backtracking (default: false)
|
|
173
|
-
safeMode?: boolean;
|
|
227
|
+
safeMode?: boolean; // Safe mode (default: false)
|
|
174
228
|
}
|
|
175
229
|
```
|
|
176
230
|
|
|
@@ -178,12 +232,12 @@ interface RegexSecurityConfig {
|
|
|
178
232
|
|
|
179
233
|
```typescript
|
|
180
234
|
interface RegexBuilderOptions {
|
|
181
|
-
global?: boolean;
|
|
182
|
-
ignoreCase?: boolean;
|
|
183
|
-
multiline?: boolean;
|
|
184
|
-
dotAll?: boolean;
|
|
185
|
-
unicode?: boolean;
|
|
186
|
-
sticky?: boolean;
|
|
235
|
+
global?: boolean; // 'g' flag
|
|
236
|
+
ignoreCase?: boolean; // 'i' flag
|
|
237
|
+
multiline?: boolean; // 'm' flag
|
|
238
|
+
dotAll?: boolean; // 's' flag
|
|
239
|
+
unicode?: boolean; // 'u' flag
|
|
240
|
+
sticky?: boolean; // 'y' flag
|
|
187
241
|
}
|
|
188
242
|
```
|
|
189
243
|
|
|
@@ -220,12 +274,12 @@ The service automatically detects:
|
|
|
220
274
|
|
|
221
275
|
```typescript
|
|
222
276
|
interface RegexTestResult {
|
|
223
|
-
match: boolean;
|
|
277
|
+
match: boolean; // If there was a match
|
|
224
278
|
matches?: RegExpMatchArray[]; // All matches found
|
|
225
279
|
groups?: { [key: string]: string }; // Captured groups
|
|
226
|
-
executionTime: number;
|
|
227
|
-
timeout: boolean;
|
|
228
|
-
error?: string;
|
|
280
|
+
executionTime: number; // Execution time in ms
|
|
281
|
+
timeout: boolean; // If there was a timeout
|
|
282
|
+
error?: string; // Error if one occurred
|
|
229
283
|
}
|
|
230
284
|
```
|
|
231
285
|
|
|
@@ -233,10 +287,10 @@ interface RegexTestResult {
|
|
|
233
287
|
|
|
234
288
|
```typescript
|
|
235
289
|
interface RegexSecurityResult {
|
|
236
|
-
safe: boolean;
|
|
237
|
-
complexity: number;
|
|
290
|
+
safe: boolean; // If the pattern is safe
|
|
291
|
+
complexity: number; // Complexity level (0-∞)
|
|
238
292
|
risk: 'low' | 'medium' | 'high' | 'critical';
|
|
239
|
-
warnings: string[];
|
|
293
|
+
warnings: string[]; // Security warnings
|
|
240
294
|
recommendations: string[]; // Improvement recommendations
|
|
241
295
|
}
|
|
242
296
|
```
|
|
@@ -250,27 +304,27 @@ import { AbstractControl, ValidationErrors } from '@angular/forms';
|
|
|
250
304
|
|
|
251
305
|
export class SecurityValidators {
|
|
252
306
|
constructor(private regexSecurity: RegexSecurityService) {}
|
|
253
|
-
|
|
307
|
+
|
|
254
308
|
async securePattern(pattern: string, config?: RegexSecurityConfig) {
|
|
255
309
|
return async (control: AbstractControl): Promise<ValidationErrors | null> => {
|
|
256
310
|
const value = control.value;
|
|
257
|
-
|
|
311
|
+
|
|
258
312
|
if (!value) return null;
|
|
259
|
-
|
|
313
|
+
|
|
260
314
|
try {
|
|
261
315
|
const result = await this.regexSecurity.testRegex(pattern, value, config);
|
|
262
|
-
|
|
316
|
+
|
|
263
317
|
if (!result.match) {
|
|
264
318
|
return { securePattern: { value, reason: 'Pattern does not match' } };
|
|
265
319
|
}
|
|
266
|
-
|
|
320
|
+
|
|
267
321
|
if (result.timeout) {
|
|
268
322
|
return { securePattern: { value, reason: 'Pattern execution timeout' } };
|
|
269
323
|
}
|
|
270
|
-
|
|
324
|
+
|
|
271
325
|
return null;
|
|
272
326
|
} catch (error) {
|
|
273
|
-
return { securePattern: { value, reason: error.message } };
|
|
327
|
+
return { securePattern: { value, reason: (error as Error).message } };
|
|
274
328
|
}
|
|
275
329
|
};
|
|
276
330
|
}
|
|
@@ -282,11 +336,9 @@ export class SecurityValidators {
|
|
|
282
336
|
```typescript
|
|
283
337
|
@Component({...})
|
|
284
338
|
export class SecureFormComponent {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
) {}
|
|
289
|
-
|
|
339
|
+
private regexSecurity = inject(RegexSecurityService);
|
|
340
|
+
private securityValidators = inject(SecurityValidators);
|
|
341
|
+
|
|
290
342
|
emailValidator = this.securityValidators.securePattern(
|
|
291
343
|
'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
|
|
292
344
|
{ timeout: 3000, safeMode: true }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DestroyRef, Injectable } from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, Injectable, PLATFORM_ID, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Security service for regular expressions that prevents ReDoS
|
|
@@ -28,14 +29,14 @@ class RegexSecurityService {
|
|
|
28
29
|
match: false,
|
|
29
30
|
executionTime: performance.now() - startTime,
|
|
30
31
|
timeout: false,
|
|
31
|
-
error: `Pattern rejected: ${securityCheck.warnings.join(', ')}
|
|
32
|
+
error: `Pattern rejected: ${securityCheck.warnings.join(', ')}`,
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
// Execute in Web Worker with timeout
|
|
35
36
|
const result = await this.executeInWorker(pattern, text, finalConfig);
|
|
36
37
|
return {
|
|
37
38
|
...result,
|
|
38
|
-
executionTime: performance.now() - startTime
|
|
39
|
+
executionTime: performance.now() - startTime,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
catch (error) {
|
|
@@ -43,7 +44,7 @@ class RegexSecurityService {
|
|
|
43
44
|
match: false,
|
|
44
45
|
executionTime: performance.now() - startTime,
|
|
45
46
|
timeout: false,
|
|
46
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
47
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -57,7 +58,11 @@ class RegexSecurityService {
|
|
|
57
58
|
let risk = 'low';
|
|
58
59
|
// Analysis of dangerous patterns
|
|
59
60
|
const dangerousPatterns = [
|
|
60
|
-
{
|
|
61
|
+
{
|
|
62
|
+
pattern: /\*\*/,
|
|
63
|
+
risk: 'high',
|
|
64
|
+
message: 'Nested quantifiers (catastrophic backtracking)',
|
|
65
|
+
},
|
|
61
66
|
{ pattern: /\+\+/, risk: 'high', message: 'Nested plus quantifiers' },
|
|
62
67
|
{ pattern: /\(\?\=/, risk: 'medium', message: 'Lookahead assertions' },
|
|
63
68
|
{ pattern: /\(\?\!/, risk: 'medium', message: 'Negative lookahead' },
|
|
@@ -65,8 +70,16 @@ class RegexSecurityService {
|
|
|
65
70
|
{ pattern: /\(\?\</, risk: 'high', message: 'Lookbehind assertions' },
|
|
66
71
|
{ pattern: /\(\?\(\?\)/, risk: 'critical', message: 'Recursive patterns' },
|
|
67
72
|
{ pattern: /(\{(\d+,)?\d+\})/, risk: 'medium', message: 'Quantified repetition' },
|
|
68
|
-
{
|
|
69
|
-
|
|
73
|
+
{
|
|
74
|
+
pattern: /(\.\*)|(\.+)|(\.\?)/,
|
|
75
|
+
risk: 'medium',
|
|
76
|
+
message: 'Greedy quantifiers with dot',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
pattern: /(\[.*\*.*\])|(\[.*\+.*\])/,
|
|
80
|
+
risk: 'medium',
|
|
81
|
+
message: 'Character classes with quantifiers',
|
|
82
|
+
},
|
|
70
83
|
];
|
|
71
84
|
// Calculate complexity
|
|
72
85
|
complexity = this.calculateComplexity(pattern);
|
|
@@ -96,7 +109,7 @@ class RegexSecurityService {
|
|
|
96
109
|
complexity,
|
|
97
110
|
risk,
|
|
98
111
|
warnings,
|
|
99
|
-
recommendations
|
|
112
|
+
recommendations,
|
|
100
113
|
};
|
|
101
114
|
}
|
|
102
115
|
/**
|
|
@@ -118,8 +131,8 @@ class RegexSecurityService {
|
|
|
118
131
|
data: {
|
|
119
132
|
pattern,
|
|
120
133
|
text,
|
|
121
|
-
timeout: config.timeout || 5000
|
|
122
|
-
}
|
|
134
|
+
timeout: config.timeout || 5000,
|
|
135
|
+
},
|
|
123
136
|
};
|
|
124
137
|
// Timeout for execution
|
|
125
138
|
const timeoutId = setTimeout(() => {
|
|
@@ -129,7 +142,7 @@ class RegexSecurityService {
|
|
|
129
142
|
match: false,
|
|
130
143
|
executionTime: 0,
|
|
131
144
|
timeout: true,
|
|
132
|
-
error: 'Execution timeout'
|
|
145
|
+
error: 'Execution timeout',
|
|
133
146
|
});
|
|
134
147
|
}, config.timeout || 5000);
|
|
135
148
|
worker.onmessage = (event) => {
|
|
@@ -148,7 +161,7 @@ class RegexSecurityService {
|
|
|
148
161
|
match: false,
|
|
149
162
|
executionTime: 0,
|
|
150
163
|
timeout: false,
|
|
151
|
-
error: `Worker error: ${error.message || 'Unknown error'}
|
|
164
|
+
error: `Worker error: ${error.message || 'Unknown error'}`,
|
|
152
165
|
});
|
|
153
166
|
};
|
|
154
167
|
worker.postMessage(task);
|
|
@@ -158,7 +171,7 @@ class RegexSecurityService {
|
|
|
158
171
|
match: false,
|
|
159
172
|
executionTime: 0,
|
|
160
173
|
timeout: false,
|
|
161
|
-
error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}
|
|
174
|
+
error: `Failed to create worker: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
162
175
|
});
|
|
163
176
|
}
|
|
164
177
|
});
|
|
@@ -248,7 +261,7 @@ class RegexSecurityService {
|
|
|
248
261
|
* Gets numeric risk level
|
|
249
262
|
*/
|
|
250
263
|
getRiskLevel(risk) {
|
|
251
|
-
const levels = {
|
|
264
|
+
const levels = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
252
265
|
return levels[risk] || 0;
|
|
253
266
|
}
|
|
254
267
|
/**
|
|
@@ -259,14 +272,14 @@ class RegexSecurityService {
|
|
|
259
272
|
timeout: config.timeout || 5000,
|
|
260
273
|
maxComplexity: config.maxComplexity || 10,
|
|
261
274
|
allowBacktracking: config.allowBacktracking || false,
|
|
262
|
-
safeMode: config.safeMode || false
|
|
275
|
+
safeMode: config.safeMode || false,
|
|
263
276
|
};
|
|
264
277
|
}
|
|
265
278
|
/**
|
|
266
279
|
* Cleans up resources when the service is destroyed
|
|
267
280
|
*/
|
|
268
281
|
ngOnDestroy() {
|
|
269
|
-
this.workers.forEach(worker => {
|
|
282
|
+
this.workers.forEach((worker) => {
|
|
270
283
|
worker.terminate();
|
|
271
284
|
});
|
|
272
285
|
this.workers.clear();
|
|
@@ -387,7 +400,7 @@ class RegexSecurityBuilder {
|
|
|
387
400
|
return {
|
|
388
401
|
pattern: this.patternValue,
|
|
389
402
|
options: this.optionsValue,
|
|
390
|
-
security: this.securityConfigValue
|
|
403
|
+
security: this.securityConfigValue,
|
|
391
404
|
};
|
|
392
405
|
}
|
|
393
406
|
/**
|
|
@@ -399,9 +412,102 @@ class RegexSecurityBuilder {
|
|
|
399
412
|
}
|
|
400
413
|
}
|
|
401
414
|
|
|
415
|
+
class WebCryptoService {
|
|
416
|
+
platformId = inject(PLATFORM_ID);
|
|
417
|
+
isSupported() {
|
|
418
|
+
return isPlatformBrowser(this.platformId) && 'crypto' in window && 'subtle' in crypto;
|
|
419
|
+
}
|
|
420
|
+
get subtle() {
|
|
421
|
+
if (!this.isSupported()) {
|
|
422
|
+
throw new Error('Web Crypto API not supported in this environment');
|
|
423
|
+
}
|
|
424
|
+
return crypto.subtle;
|
|
425
|
+
}
|
|
426
|
+
ensureSecureContext() {
|
|
427
|
+
if (!window.isSecureContext) {
|
|
428
|
+
throw new Error('Web Crypto API requires a secure context (HTTPS)');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async hash(data, algorithm = 'SHA-256') {
|
|
432
|
+
this.ensureSecureContext();
|
|
433
|
+
const buffer = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
434
|
+
const hashBuffer = await this.subtle.digest(algorithm, buffer);
|
|
435
|
+
return this.bufferToHex(hashBuffer);
|
|
436
|
+
}
|
|
437
|
+
async generateAesKey(length = 256) {
|
|
438
|
+
this.ensureSecureContext();
|
|
439
|
+
return this.subtle.generateKey({ name: 'AES-GCM', length }, true, ['encrypt', 'decrypt']);
|
|
440
|
+
}
|
|
441
|
+
async encryptAes(key, data) {
|
|
442
|
+
this.ensureSecureContext();
|
|
443
|
+
const buffer = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
444
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
445
|
+
const ciphertext = await this.subtle.encrypt({ name: 'AES-GCM', iv }, key, buffer);
|
|
446
|
+
return { ciphertext, iv };
|
|
447
|
+
}
|
|
448
|
+
async decryptAes(key, ciphertext, iv) {
|
|
449
|
+
this.ensureSecureContext();
|
|
450
|
+
const decrypted = await this.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
|
|
451
|
+
return new TextDecoder().decode(decrypted);
|
|
452
|
+
}
|
|
453
|
+
async exportKey(key) {
|
|
454
|
+
this.ensureSecureContext();
|
|
455
|
+
return this.subtle.exportKey('jwk', key);
|
|
456
|
+
}
|
|
457
|
+
async importAesKey(jwk) {
|
|
458
|
+
this.ensureSecureContext();
|
|
459
|
+
return this.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
|
|
460
|
+
}
|
|
461
|
+
generateRandomBytes(length) {
|
|
462
|
+
if (!this.isSupported()) {
|
|
463
|
+
throw new Error('Web Crypto API not supported');
|
|
464
|
+
}
|
|
465
|
+
return crypto.getRandomValues(new Uint8Array(length));
|
|
466
|
+
}
|
|
467
|
+
randomUUID() {
|
|
468
|
+
if (!this.isSupported()) {
|
|
469
|
+
throw new Error('Web Crypto API not supported');
|
|
470
|
+
}
|
|
471
|
+
return crypto.randomUUID();
|
|
472
|
+
}
|
|
473
|
+
bufferToHex(buffer) {
|
|
474
|
+
return Array.from(new Uint8Array(buffer))
|
|
475
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
476
|
+
.join('');
|
|
477
|
+
}
|
|
478
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebCryptoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
479
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebCryptoService });
|
|
480
|
+
}
|
|
481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebCryptoService, decorators: [{
|
|
482
|
+
type: Injectable
|
|
483
|
+
}] });
|
|
484
|
+
|
|
485
|
+
const defaultSecurityConfig = {
|
|
486
|
+
enableRegexSecurity: true,
|
|
487
|
+
enableWebCrypto: true,
|
|
488
|
+
defaultTimeout: 5000,
|
|
489
|
+
safeMode: false,
|
|
490
|
+
};
|
|
491
|
+
function provideSecurity(config = {}) {
|
|
492
|
+
const mergedConfig = { ...defaultSecurityConfig, ...config };
|
|
493
|
+
const providers = [];
|
|
494
|
+
if (mergedConfig.enableRegexSecurity) {
|
|
495
|
+
providers.push(RegexSecurityService);
|
|
496
|
+
}
|
|
497
|
+
if (mergedConfig.enableWebCrypto) {
|
|
498
|
+
providers.push(WebCryptoService);
|
|
499
|
+
}
|
|
500
|
+
return makeEnvironmentProviders(providers);
|
|
501
|
+
}
|
|
502
|
+
function provideRegexSecurity() {
|
|
503
|
+
return makeEnvironmentProviders([RegexSecurityService]);
|
|
504
|
+
}
|
|
505
|
+
function provideWebCrypto() {
|
|
506
|
+
return makeEnvironmentProviders([WebCryptoService]);
|
|
507
|
+
}
|
|
508
|
+
|
|
402
509
|
/**
|
|
403
510
|
* Generated bundle index. Do not edit.
|
|
404
511
|
*/
|
|
405
512
|
|
|
406
|
-
export { RegexSecurityBuilder, RegexSecurityService };
|
|
407
|
-
//# sourceMappingURL=angular-helpers-security.mjs.map
|
|
513
|
+
export { RegexSecurityBuilder, RegexSecurityService, WebCryptoService, defaultSecurityConfig, provideRegexSecurity, provideSecurity, provideWebCrypto };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/security",
|
|
3
|
-
"version": "21.0.
|
|
3
|
+
"version": "21.0.4",
|
|
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.2",
|
|
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,40 @@ declare class RegexSecurityBuilder {
|
|
|
150
151
|
execute(text: string, service: RegexSecurityService): Promise<RegexTestResult>;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
type HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';
|
|
155
|
+
type AesKeyLength = 128 | 192 | 256;
|
|
156
|
+
interface AesEncryptResult {
|
|
157
|
+
ciphertext: ArrayBuffer;
|
|
158
|
+
iv: Uint8Array;
|
|
159
|
+
}
|
|
160
|
+
declare class WebCryptoService {
|
|
161
|
+
private readonly platformId;
|
|
162
|
+
isSupported(): boolean;
|
|
163
|
+
private get subtle();
|
|
164
|
+
private ensureSecureContext;
|
|
165
|
+
hash(data: string | ArrayBuffer, algorithm?: HashAlgorithm): Promise<string>;
|
|
166
|
+
generateAesKey(length?: AesKeyLength): Promise<CryptoKey>;
|
|
167
|
+
encryptAes(key: CryptoKey, data: string | ArrayBuffer): Promise<AesEncryptResult>;
|
|
168
|
+
decryptAes(key: CryptoKey, ciphertext: ArrayBuffer, iv: Uint8Array<ArrayBuffer>): Promise<string>;
|
|
169
|
+
exportKey(key: CryptoKey): Promise<JsonWebKey>;
|
|
170
|
+
importAesKey(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
171
|
+
generateRandomBytes(length: number): Uint8Array;
|
|
172
|
+
randomUUID(): string;
|
|
173
|
+
private bufferToHex;
|
|
174
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<WebCryptoService, never>;
|
|
175
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<WebCryptoService>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface SecurityConfig {
|
|
179
|
+
enableRegexSecurity?: boolean;
|
|
180
|
+
enableWebCrypto?: boolean;
|
|
181
|
+
defaultTimeout?: number;
|
|
182
|
+
safeMode?: boolean;
|
|
183
|
+
}
|
|
184
|
+
declare const defaultSecurityConfig: SecurityConfig;
|
|
185
|
+
declare function provideSecurity(config?: SecurityConfig): EnvironmentProviders;
|
|
186
|
+
declare function provideRegexSecurity(): EnvironmentProviders;
|
|
187
|
+
declare function provideWebCrypto(): EnvironmentProviders;
|
|
188
|
+
|
|
189
|
+
export { RegexSecurityBuilder, RegexSecurityService, WebCryptoService, defaultSecurityConfig, provideRegexSecurity, provideSecurity, provideWebCrypto };
|
|
190
|
+
export type { AesEncryptResult, AesKeyLength, HashAlgorithm, 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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"}
|