@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 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
- [English](README.en.md) | [Español](README.md)
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
- constructor(private regexSecurity: RegexSecurityService) {}
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 { RegexSecurityBuilder } from '@angular-helpers/security';
85
+ import { RegexSecurityService } from '@angular-helpers/security';
75
86
 
76
87
  // Fluent regular expression construction
77
- const emailRegex = RegexSecurityBuilder
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 RegexSecurityBuilder
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 RegexSecurityBuilder
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; // Timeout in ms (default: 5000)
171
- maxComplexity?: number; // Max complexity (default: 10)
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; // Safe mode (default: false)
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; // 'g' flag
182
- ignoreCase?: boolean; // 'i' flag
183
- multiline?: boolean; // 'm' flag
184
- dotAll?: boolean; // 's' flag
185
- unicode?: boolean; // 'u' flag
186
- sticky?: boolean; // 'y' flag
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; // If there was a match
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; // Execution time in ms
227
- timeout: boolean; // If there was a timeout
228
- error?: string; // Error if one occurred
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; // If the pattern is safe
237
- complexity: number; // Complexity level (0-∞)
290
+ safe: boolean; // If the pattern is safe
291
+ complexity: number; // Complexity level (0-∞)
238
292
  risk: 'low' | 'medium' | 'high' | 'critical';
239
- warnings: string[]; // Security warnings
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
- constructor(
286
- private regexSecurity: RegexSecurityService,
287
- private securityValidators: SecurityValidators
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
- { pattern: /\*\*/, risk: 'high', message: 'Nested quantifiers (catastrophic backtracking)' },
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
- { pattern: /(\.\*)|(\.+)|(\.\?)/, risk: 'medium', message: 'Greedy quantifiers with dot' },
69
- { pattern: /(\[.*\*.*\])|(\[.*\+.*\])/, risk: 'medium', message: 'Character classes with quantifiers' }
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 = { 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
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.2",
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
- export { RegexSecurityBuilder, RegexSecurityService };
154
- export type { RegexBuilderOptions, RegexSecurityConfig, RegexSecurityResult, RegexTestResult };
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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"}