@capgo/capgo-sec 1.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.
@@ -0,0 +1,435 @@
1
+ import type { Rule, Finding } from '../types.js';
2
+
3
+ export const capacitorRules: Rule[] = [
4
+ {
5
+ id: 'CAP001',
6
+ name: 'WebView Debug Mode Enabled',
7
+ description: 'Detects web debugging enabled in production Capacitor configuration',
8
+ severity: 'critical',
9
+ category: 'capacitor',
10
+ filePatterns: ['**/capacitor.config.ts', '**/capacitor.config.js', '**/capacitor.config.json'],
11
+ check: (content: string, filePath: string): Finding[] => {
12
+ const findings: Finding[] = [];
13
+ const lines = content.split('\n');
14
+
15
+ const debugPattern = /webContentsDebuggingEnabled\s*:\s*true/i;
16
+
17
+ if (debugPattern.test(content)) {
18
+ const match = content.match(debugPattern);
19
+ if (match) {
20
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
21
+ findings.push({
22
+ ruleId: 'CAP001',
23
+ ruleName: 'WebView Debug Mode Enabled',
24
+ severity: 'critical',
25
+ category: 'capacitor',
26
+ message: 'WebView debugging is enabled, allowing remote code inspection',
27
+ filePath,
28
+ line: lineNum,
29
+ codeSnippet: lines[lineNum - 1]?.trim(),
30
+ remediation: 'Set webContentsDebuggingEnabled to false for production builds.',
31
+ references: ['https://capacitorjs.com/docs/config']
32
+ });
33
+ }
34
+ }
35
+
36
+ return findings;
37
+ },
38
+ remediation: 'Disable WebView debugging in production: webContentsDebuggingEnabled: false'
39
+ },
40
+ {
41
+ id: 'CAP002',
42
+ name: 'Insecure Plugin Configuration',
43
+ description: 'Detects insecure Capacitor plugin configurations',
44
+ severity: 'high',
45
+ category: 'capacitor',
46
+ filePatterns: ['**/capacitor.config.ts', '**/capacitor.config.js', '**/capacitor.config.json'],
47
+ check: (content: string, filePath: string): Finding[] => {
48
+ const findings: Finding[] = [];
49
+ const lines = content.split('\n');
50
+
51
+ // Check for AllowMixedContent on Android
52
+ if (/allowMixedContent\s*:\s*true/i.test(content)) {
53
+ const match = content.match(/allowMixedContent\s*:\s*true/i);
54
+ if (match) {
55
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
56
+ findings.push({
57
+ ruleId: 'CAP002',
58
+ ruleName: 'Insecure Plugin Configuration',
59
+ severity: 'high',
60
+ category: 'capacitor',
61
+ message: 'allowMixedContent enables loading HTTP resources over HTTPS',
62
+ filePath,
63
+ line: lineNum,
64
+ codeSnippet: lines[lineNum - 1]?.trim(),
65
+ remediation: 'Remove allowMixedContent: true. All content should be served over HTTPS.',
66
+ references: ['https://capacitorjs.com/docs/config/android']
67
+ });
68
+ }
69
+ }
70
+
71
+ // Check for capture input (keyboard capture)
72
+ if (/captureInput\s*:\s*true/i.test(content)) {
73
+ const match = content.match(/captureInput\s*:\s*true/i);
74
+ if (match) {
75
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
76
+ findings.push({
77
+ ruleId: 'CAP002',
78
+ ruleName: 'Insecure Plugin Configuration',
79
+ severity: 'medium',
80
+ category: 'capacitor',
81
+ message: 'captureInput is enabled, which captures all keyboard input',
82
+ filePath,
83
+ line: lineNum,
84
+ codeSnippet: lines[lineNum - 1]?.trim(),
85
+ remediation: 'Only enable captureInput if specifically required for your use case.',
86
+ references: ['https://capacitorjs.com/docs/config/android']
87
+ });
88
+ }
89
+ }
90
+
91
+ return findings;
92
+ },
93
+ remediation: 'Review and secure Capacitor plugin configurations.'
94
+ },
95
+ {
96
+ id: 'CAP003',
97
+ name: 'Verbose Logging in Production',
98
+ description: 'Detects verbose logging configuration that may expose sensitive data',
99
+ severity: 'medium',
100
+ category: 'capacitor',
101
+ filePatterns: ['**/capacitor.config.ts', '**/capacitor.config.js', '**/capacitor.config.json'],
102
+ check: (content: string, filePath: string): Finding[] => {
103
+ const findings: Finding[] = [];
104
+ const lines = content.split('\n');
105
+
106
+ const loggingPattern = /loggingBehavior\s*:\s*['"](?:debug|verbose)['"]/i;
107
+
108
+ if (loggingPattern.test(content)) {
109
+ const match = content.match(loggingPattern);
110
+ if (match) {
111
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
112
+ findings.push({
113
+ ruleId: 'CAP003',
114
+ ruleName: 'Verbose Logging in Production',
115
+ severity: 'medium',
116
+ category: 'capacitor',
117
+ message: 'Verbose logging is enabled which may expose sensitive information',
118
+ filePath,
119
+ line: lineNum,
120
+ codeSnippet: lines[lineNum - 1]?.trim(),
121
+ remediation: 'Set loggingBehavior to "production" or "none" for production builds.',
122
+ references: ['https://capacitorjs.com/docs/config']
123
+ });
124
+ }
125
+ }
126
+
127
+ return findings;
128
+ },
129
+ remediation: 'Use production-appropriate logging levels.'
130
+ },
131
+ {
132
+ id: 'CAP004',
133
+ name: 'Insecure allowNavigation',
134
+ description: 'Detects overly permissive navigation allowlist',
135
+ severity: 'high',
136
+ category: 'capacitor',
137
+ filePatterns: ['**/capacitor.config.ts', '**/capacitor.config.js', '**/capacitor.config.json'],
138
+ check: (content: string, filePath: string): Finding[] => {
139
+ const findings: Finding[] = [];
140
+ const lines = content.split('\n');
141
+
142
+ // Check for wildcard or overly broad navigation patterns
143
+ const patterns = [
144
+ { pattern: /allowNavigation.*\*\.\*/, message: 'Wildcard navigation pattern allows any domain' },
145
+ { pattern: /allowNavigation.*http:\/\//, message: 'HTTP URLs in allowNavigation are insecure' },
146
+ { pattern: /allowNavigation.*\['"\*['"]/, message: 'Wildcard * allows navigation to any URL' }
147
+ ];
148
+
149
+ for (const { pattern, message } of patterns) {
150
+ if (pattern.test(content)) {
151
+ const match = content.match(pattern);
152
+ if (match) {
153
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
154
+ findings.push({
155
+ ruleId: 'CAP004',
156
+ ruleName: 'Insecure allowNavigation',
157
+ severity: 'high',
158
+ category: 'capacitor',
159
+ message,
160
+ filePath,
161
+ line: lineNum,
162
+ codeSnippet: lines[lineNum - 1]?.trim(),
163
+ remediation: 'Specify exact HTTPS domains in allowNavigation.',
164
+ references: ['https://capacitorjs.com/docs/config']
165
+ });
166
+ }
167
+ }
168
+ }
169
+
170
+ return findings;
171
+ },
172
+ remediation: 'Use explicit domain allowlists for navigation.'
173
+ },
174
+ {
175
+ id: 'CAP005',
176
+ name: 'Native Bridge Exposure',
177
+ description: 'Detects potential exposure of native bridge to untrusted content',
178
+ severity: 'critical',
179
+ category: 'capacitor',
180
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
181
+ check: (content: string, filePath: string): Finding[] => {
182
+ const findings: Finding[] = [];
183
+ const lines = content.split('\n');
184
+
185
+ // Check for exposing Capacitor to window
186
+ const exposurePatterns = [
187
+ /window\.Capacitor\s*=/,
188
+ /globalThis\.Capacitor\s*=/,
189
+ /eval\s*\([^)]*Capacitor/,
190
+ /Function\s*\([^)]*Capacitor/
191
+ ];
192
+
193
+ for (const pattern of exposurePatterns) {
194
+ let match;
195
+ const regex = new RegExp(pattern.source, 'gi');
196
+ while ((match = regex.exec(content)) !== null) {
197
+ const lineNum = content.substring(0, match.index).split('\n').length;
198
+ findings.push({
199
+ ruleId: 'CAP005',
200
+ ruleName: 'Native Bridge Exposure',
201
+ severity: 'critical',
202
+ category: 'capacitor',
203
+ message: 'Capacitor native bridge may be exposed to untrusted code',
204
+ filePath,
205
+ line: lineNum,
206
+ codeSnippet: lines[lineNum - 1]?.trim(),
207
+ remediation: 'Never expose the Capacitor bridge to dynamically loaded or untrusted content.',
208
+ references: ['https://capacitor-sec.dev/docs/rules/native-bridge']
209
+ });
210
+ }
211
+ }
212
+
213
+ return findings;
214
+ },
215
+ remediation: 'Keep native bridge access restricted to trusted code only.'
216
+ },
217
+ {
218
+ id: 'CAP006',
219
+ name: 'Eval Usage with User Input',
220
+ description: 'Detects usage of eval() or similar functions that could execute arbitrary code',
221
+ severity: 'critical',
222
+ category: 'capacitor',
223
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
224
+ check: (content: string, filePath: string): Finding[] => {
225
+ const findings: Finding[] = [];
226
+ const lines = content.split('\n');
227
+
228
+ // Skip node_modules
229
+ if (filePath.includes('node_modules')) {
230
+ return findings;
231
+ }
232
+
233
+ const evalPatterns = [
234
+ /\beval\s*\(/g,
235
+ /new\s+Function\s*\(/g,
236
+ /setTimeout\s*\(\s*['"`][^'"`]+['"`]/g,
237
+ /setInterval\s*\(\s*['"`][^'"`]+['"`]/g
238
+ ];
239
+
240
+ for (const pattern of evalPatterns) {
241
+ let match;
242
+ while ((match = pattern.exec(content)) !== null) {
243
+ const lineNum = content.substring(0, match.index).split('\n').length;
244
+ findings.push({
245
+ ruleId: 'CAP006',
246
+ ruleName: 'Eval Usage with User Input',
247
+ severity: 'critical',
248
+ category: 'capacitor',
249
+ message: 'Usage of eval() or equivalent can lead to code injection attacks',
250
+ filePath,
251
+ line: lineNum,
252
+ codeSnippet: lines[lineNum - 1]?.trim(),
253
+ remediation: 'Avoid eval() and new Function(). Use safer alternatives like JSON.parse() for data.',
254
+ references: [
255
+ 'https://owasp.org/www-community/attacks/Code_Injection',
256
+ 'https://capacitor-sec.dev/docs/rules/eval'
257
+ ]
258
+ });
259
+ }
260
+ }
261
+
262
+ return findings;
263
+ },
264
+ remediation: 'Remove all usage of eval() and new Function(). Use JSON.parse() for data parsing.'
265
+ },
266
+ {
267
+ id: 'CAP007',
268
+ name: 'Missing Root/Jailbreak Detection',
269
+ description: 'No root or jailbreak detection found for sensitive operations',
270
+ severity: 'medium',
271
+ category: 'capacitor',
272
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
273
+ check: (content: string, filePath: string): Finding[] => {
274
+ const findings: Finding[] = [];
275
+
276
+ // Only check entry points or main files
277
+ if (!filePath.includes('App.') && !filePath.includes('main.') && !filePath.includes('index.')) {
278
+ return findings;
279
+ }
280
+
281
+ // Check for sensitive operations
282
+ const hasSensitiveOps = /(?:payment|banking|wallet|crypto|biometric|auth)/i.test(content);
283
+
284
+ // Check for root/jailbreak detection
285
+ const hasRootDetection = /(?:isRooted|isJailbroken|rootDetection|jailbreakDetection|freeRASP|appIntegrity)/i.test(content);
286
+
287
+ if (hasSensitiveOps && !hasRootDetection) {
288
+ findings.push({
289
+ ruleId: 'CAP007',
290
+ ruleName: 'Missing Root/Jailbreak Detection',
291
+ severity: 'medium',
292
+ category: 'capacitor',
293
+ message: 'Sensitive operations detected without root/jailbreak detection',
294
+ filePath,
295
+ line: 1,
296
+ remediation: 'Implement root/jailbreak detection for apps handling sensitive data. Consider using @niclas-niclas/capacitor-freerasp.',
297
+ references: [
298
+ 'https://github.com/niclas-niclas/capacitor-freerasp',
299
+ 'https://capacitor-sec.dev/docs/rules/root-detection'
300
+ ]
301
+ });
302
+ }
303
+
304
+ return findings;
305
+ },
306
+ remediation: 'Add root/jailbreak detection for sensitive applications.'
307
+ },
308
+ {
309
+ id: 'CAP008',
310
+ name: 'Insecure Plugin Import',
311
+ description: 'Detects import of known insecure or deprecated Capacitor plugins',
312
+ severity: 'medium',
313
+ category: 'capacitor',
314
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/package.json'],
315
+ check: (content: string, filePath: string): Finding[] => {
316
+ const findings: Finding[] = [];
317
+ const lines = content.split('\n');
318
+
319
+ // List of deprecated or insecure plugins
320
+ const insecurePlugins = [
321
+ { name: '@capacitor/browser', check: /webview|iframe/i, message: 'Using Browser plugin for sensitive content may be insecure' },
322
+ { name: 'cordova-plugin-', check: /cordova-plugin-/i, message: 'Cordova plugins may have security issues in Capacitor context' }
323
+ ];
324
+
325
+ for (const plugin of insecurePlugins) {
326
+ if (plugin.check.test(content)) {
327
+ const match = content.match(plugin.check);
328
+ if (match) {
329
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
330
+ findings.push({
331
+ ruleId: 'CAP008',
332
+ ruleName: 'Insecure Plugin Import',
333
+ severity: 'low',
334
+ category: 'capacitor',
335
+ message: plugin.message,
336
+ filePath,
337
+ line: lineNum,
338
+ codeSnippet: lines[lineNum - 1]?.trim(),
339
+ remediation: 'Review plugin security implications. Prefer Capacitor-native plugins.',
340
+ references: ['https://capacitor-sec.dev/docs/rules/plugins']
341
+ });
342
+ }
343
+ }
344
+ }
345
+
346
+ return findings;
347
+ },
348
+ remediation: 'Use official Capacitor plugins when available.'
349
+ },
350
+ {
351
+ id: 'CAP009',
352
+ name: 'Live Update Security',
353
+ description: 'Detects insecure live update configurations',
354
+ severity: 'high',
355
+ category: 'capacitor',
356
+ filePatterns: ['**/capacitor.config.ts', '**/capacitor.config.js', '**/capacitor.config.json', '**/*.ts', '**/*.tsx'],
357
+ check: (content: string, filePath: string): Finding[] => {
358
+ const findings: Finding[] = [];
359
+ const lines = content.split('\n');
360
+
361
+ // Check for Capgo/live update without encryption
362
+ const liveUpdatePattern = /(?:@capgo\/capacitor-updater|CapacitorUpdater)/i;
363
+
364
+ if (liveUpdatePattern.test(content)) {
365
+ // Check for encryption configuration
366
+ const hasEncryption = /privateKey|encryptionKey|signatureKey/i.test(content);
367
+
368
+ if (!hasEncryption) {
369
+ const match = content.match(liveUpdatePattern);
370
+ if (match) {
371
+ const lineNum = content.substring(0, content.indexOf(match[0])).split('\n').length;
372
+ findings.push({
373
+ ruleId: 'CAP009',
374
+ ruleName: 'Live Update Security',
375
+ severity: 'high',
376
+ category: 'capacitor',
377
+ message: 'Live update configured without encryption',
378
+ filePath,
379
+ line: lineNum,
380
+ codeSnippet: lines[lineNum - 1]?.trim(),
381
+ remediation: 'Enable encryption for live updates to prevent tampering. Use Capgo end-to-end encryption.',
382
+ references: [
383
+ 'https://capgo.app/docs/plugin/cloud-mode/hybrid-update/',
384
+ 'https://capacitor-sec.dev/docs/rules/live-update'
385
+ ]
386
+ });
387
+ }
388
+ }
389
+ }
390
+
391
+ return findings;
392
+ },
393
+ remediation: 'Enable end-to-end encryption for live updates with Capgo.'
394
+ },
395
+ {
396
+ id: 'CAP010',
397
+ name: 'Insecure postMessage Handler',
398
+ description: 'Detects insecure postMessage handlers that could accept malicious messages',
399
+ severity: 'high',
400
+ category: 'capacitor',
401
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
402
+ check: (content: string, filePath: string): Finding[] => {
403
+ const findings: Finding[] = [];
404
+ const lines = content.split('\n');
405
+
406
+ // Check for postMessage listener without origin validation
407
+ const postMessagePattern = /addEventListener\s*\(\s*['"]message['"]/g;
408
+
409
+ let match;
410
+ while ((match = postMessagePattern.exec(content)) !== null) {
411
+ const context = content.substring(match.index, Math.min(content.length, match.index + 500));
412
+ const hasOriginCheck = /event\.origin|e\.origin|origin\s*[!=]==?\s*['"]/.test(context);
413
+
414
+ if (!hasOriginCheck) {
415
+ const lineNum = content.substring(0, match.index).split('\n').length;
416
+ findings.push({
417
+ ruleId: 'CAP010',
418
+ ruleName: 'Insecure postMessage Handler',
419
+ severity: 'high',
420
+ category: 'capacitor',
421
+ message: 'postMessage handler without origin validation',
422
+ filePath,
423
+ line: lineNum,
424
+ codeSnippet: lines[lineNum - 1]?.trim(),
425
+ remediation: 'Always validate event.origin before processing postMessage data.',
426
+ references: ['https://capacitor-sec.dev/docs/rules/postmessage']
427
+ });
428
+ }
429
+ }
430
+
431
+ return findings;
432
+ },
433
+ remediation: 'Validate message origin before processing postMessage events.'
434
+ }
435
+ ];
@@ -0,0 +1,190 @@
1
+ import type { Rule, Finding } from '../types.js';
2
+
3
+ export const cryptographyRules: Rule[] = [
4
+ {
5
+ id: 'CRY001',
6
+ name: 'Weak Cryptographic Algorithm',
7
+ description: 'Detects usage of weak or deprecated cryptographic algorithms',
8
+ severity: 'high',
9
+ category: 'cryptography',
10
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
11
+ check: (content: string, filePath: string): Finding[] => {
12
+ const findings: Finding[] = [];
13
+ const lines = content.split('\n');
14
+
15
+ const weakAlgorithms = [
16
+ { pattern: /md5|['"]MD5['"]/gi, name: 'MD5', message: 'MD5 is cryptographically broken' },
17
+ { pattern: /sha1|['"]SHA-?1['"]/gi, name: 'SHA-1', message: 'SHA-1 is deprecated and vulnerable to collision attacks' },
18
+ { pattern: /des|['"]DES['"]/gi, name: 'DES', message: 'DES uses 56-bit keys, too weak for modern security' },
19
+ { pattern: /rc4|['"]RC4['"]/gi, name: 'RC4', message: 'RC4 has known biases and should not be used' },
20
+ { pattern: /blowfish/gi, name: 'Blowfish', message: 'Blowfish has a small block size, prefer AES' },
21
+ { pattern: /ECB/g, name: 'ECB mode', message: 'ECB mode does not hide data patterns' }
22
+ ];
23
+
24
+ for (const { pattern, name, message } of weakAlgorithms) {
25
+ let match;
26
+ const regex = new RegExp(pattern.source, pattern.flags);
27
+ while ((match = regex.exec(content)) !== null) {
28
+ // Skip if it's in a comment
29
+ const lineStart = content.lastIndexOf('\n', match.index) + 1;
30
+ const lineContent = content.substring(lineStart, match.index);
31
+ if (/\/\/|\/\*|\*/.test(lineContent)) continue;
32
+
33
+ const lineNum = content.substring(0, match.index).split('\n').length;
34
+ findings.push({
35
+ ruleId: 'CRY001',
36
+ ruleName: 'Weak Cryptographic Algorithm',
37
+ severity: 'high',
38
+ category: 'cryptography',
39
+ message: `${name}: ${message}`,
40
+ filePath,
41
+ line: lineNum,
42
+ codeSnippet: lines[lineNum - 1]?.trim(),
43
+ remediation: 'Use AES-256-GCM for encryption, SHA-256 or SHA-3 for hashing, and Argon2 for password hashing.',
44
+ references: [
45
+ 'https://capacitor-sec.dev/docs/rules/weak-crypto',
46
+ 'https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography'
47
+ ]
48
+ });
49
+ }
50
+ }
51
+
52
+ return findings;
53
+ },
54
+ remediation: 'Use modern algorithms: AES-256-GCM, SHA-256/SHA-3, Argon2.'
55
+ },
56
+ {
57
+ id: 'CRY002',
58
+ name: 'Hardcoded Encryption Key',
59
+ description: 'Detects hardcoded encryption keys or IVs in source code',
60
+ severity: 'critical',
61
+ category: 'cryptography',
62
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
63
+ check: (content: string, filePath: string): Finding[] => {
64
+ const findings: Finding[] = [];
65
+ const lines = content.split('\n');
66
+
67
+ // Skip test files
68
+ if (filePath.includes('.test.') || filePath.includes('.spec.')) {
69
+ return findings;
70
+ }
71
+
72
+ const keyPatterns = [
73
+ /(?:encrypt(?:ion)?Key|aesKey|secretKey|privateKey)\s*[:=]\s*['"][A-Za-z0-9+/=]{16,}['"]/gi,
74
+ /(?:iv|initVector|initialVector)\s*[:=]\s*['"][A-Za-z0-9+/=]{12,}['"]/gi,
75
+ /Buffer\.from\s*\(\s*['"][A-Fa-f0-9]{32,}['"]/g
76
+ ];
77
+
78
+ for (const pattern of keyPatterns) {
79
+ let match;
80
+ while ((match = pattern.exec(content)) !== null) {
81
+ const lineNum = content.substring(0, match.index).split('\n').length;
82
+ findings.push({
83
+ ruleId: 'CRY002',
84
+ ruleName: 'Hardcoded Encryption Key',
85
+ severity: 'critical',
86
+ category: 'cryptography',
87
+ message: 'Hardcoded encryption key or IV detected',
88
+ filePath,
89
+ line: lineNum,
90
+ codeSnippet: lines[lineNum - 1]?.trim().replace(/['"][A-Za-z0-9+/=]{8,}['"]/g, '"***KEY***"'),
91
+ remediation: 'Generate keys securely at runtime. Store keys in secure storage (Keychain/Keystore).',
92
+ references: ['https://capacitor-sec.dev/docs/rules/hardcoded-keys']
93
+ });
94
+ }
95
+ }
96
+
97
+ return findings;
98
+ },
99
+ remediation: 'Never hardcode encryption keys. Use secure key derivation and storage.'
100
+ },
101
+ {
102
+ id: 'CRY003',
103
+ name: 'Insecure Random IV Generation',
104
+ description: 'Detects non-random or predictable IV generation',
105
+ severity: 'high',
106
+ category: 'cryptography',
107
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
108
+ check: (content: string, filePath: string): Finding[] => {
109
+ const findings: Finding[] = [];
110
+ const lines = content.split('\n');
111
+
112
+ // Check for static or predictable IVs
113
+ const staticIvPatterns = [
114
+ { pattern: /iv\s*[:=]\s*(?:new\s+Uint8Array\s*\(\s*\d+\s*\)|Buffer\.alloc\s*\()/, message: 'IV initialized with zeros - must be random' },
115
+ { pattern: /iv\s*[:=]\s*['"][0-9a-fA-F]+['"]/, message: 'Static IV detected - IV must be unique per encryption' }
116
+ ];
117
+
118
+ for (const { pattern, message } of staticIvPatterns) {
119
+ let match;
120
+ const regex = new RegExp(pattern.source, 'gi');
121
+ while ((match = regex.exec(content)) !== null) {
122
+ const lineNum = content.substring(0, match.index).split('\n').length;
123
+ findings.push({
124
+ ruleId: 'CRY003',
125
+ ruleName: 'Insecure Random IV Generation',
126
+ severity: 'high',
127
+ category: 'cryptography',
128
+ message,
129
+ filePath,
130
+ line: lineNum,
131
+ codeSnippet: lines[lineNum - 1]?.trim(),
132
+ remediation: 'Use crypto.getRandomValues() to generate a unique IV for each encryption operation.',
133
+ references: ['https://capacitor-sec.dev/docs/rules/iv-generation']
134
+ });
135
+ }
136
+ }
137
+
138
+ return findings;
139
+ },
140
+ remediation: 'Generate unique random IVs using crypto.getRandomValues().'
141
+ },
142
+ {
143
+ id: 'CRY004',
144
+ name: 'Weak Password Hashing',
145
+ description: 'Detects weak password hashing schemes',
146
+ severity: 'critical',
147
+ category: 'cryptography',
148
+ filePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
149
+ check: (content: string, filePath: string): Finding[] => {
150
+ const findings: Finding[] = [];
151
+ const lines = content.split('\n');
152
+
153
+ // Check for password with weak hashing
154
+ const passwordContext = /password/i.test(content);
155
+
156
+ if (passwordContext) {
157
+ const weakPatterns = [
158
+ { pattern: /(?:sha256|sha512|createHash).*password/gi, message: 'Using raw SHA for password hashing - use bcrypt/argon2' },
159
+ { pattern: /password.*(?:sha256|sha512|createHash)/gi, message: 'Using raw SHA for password hashing - use bcrypt/argon2' },
160
+ { pattern: /md5.*password|password.*md5/gi, message: 'MD5 is completely unsuitable for password hashing' }
161
+ ];
162
+
163
+ for (const { pattern, message } of weakPatterns) {
164
+ let match;
165
+ while ((match = pattern.exec(content)) !== null) {
166
+ const lineNum = content.substring(0, match.index).split('\n').length;
167
+ findings.push({
168
+ ruleId: 'CRY004',
169
+ ruleName: 'Weak Password Hashing',
170
+ severity: 'critical',
171
+ category: 'cryptography',
172
+ message,
173
+ filePath,
174
+ line: lineNum,
175
+ codeSnippet: lines[lineNum - 1]?.trim(),
176
+ remediation: 'Use Argon2, bcrypt, or scrypt for password hashing with appropriate work factors.',
177
+ references: [
178
+ 'https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html',
179
+ 'https://capacitor-sec.dev/docs/rules/password-hashing'
180
+ ]
181
+ });
182
+ }
183
+ }
184
+ }
185
+
186
+ return findings;
187
+ },
188
+ remediation: 'Use Argon2id, bcrypt, or scrypt for password hashing.'
189
+ }
190
+ ];
@@ -0,0 +1,56 @@
1
+ import type { Rule } from '../types.js';
2
+
3
+ import { secretsRules } from './secrets.js';
4
+ import { storageRules } from './storage.js';
5
+ import { networkRules } from './network.js';
6
+ import { capacitorRules } from './capacitor.js';
7
+ import { androidRules } from './android.js';
8
+ import { iosRules } from './ios.js';
9
+ import { authenticationRules } from './authentication.js';
10
+ import { webviewRules } from './webview.js';
11
+ import { loggingRules, debugRules } from './logging.js';
12
+ import { cryptographyRules } from './cryptography.js';
13
+
14
+ export const allRules: Rule[] = [
15
+ ...secretsRules,
16
+ ...storageRules,
17
+ ...networkRules,
18
+ ...capacitorRules,
19
+ ...androidRules,
20
+ ...iosRules,
21
+ ...authenticationRules,
22
+ ...webviewRules,
23
+ ...loggingRules,
24
+ ...debugRules,
25
+ ...cryptographyRules
26
+ ];
27
+
28
+ export const rulesByCategory = {
29
+ secrets: secretsRules,
30
+ storage: storageRules,
31
+ network: networkRules,
32
+ capacitor: capacitorRules,
33
+ android: androidRules,
34
+ ios: iosRules,
35
+ authentication: authenticationRules,
36
+ webview: webviewRules,
37
+ logging: loggingRules,
38
+ debug: debugRules,
39
+ cryptography: cryptographyRules
40
+ };
41
+
42
+ export const ruleCount = allRules.length;
43
+
44
+ export {
45
+ secretsRules,
46
+ storageRules,
47
+ networkRules,
48
+ capacitorRules,
49
+ androidRules,
50
+ iosRules,
51
+ authenticationRules,
52
+ webviewRules,
53
+ loggingRules,
54
+ debugRules,
55
+ cryptographyRules
56
+ };