getdoorman 1.0.0

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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/bin/doorman.js +444 -0
  4. package/package.json +74 -0
  5. package/src/ai-fixer.js +559 -0
  6. package/src/ast-scanner.js +434 -0
  7. package/src/auth.js +149 -0
  8. package/src/baseline.js +48 -0
  9. package/src/compliance.js +539 -0
  10. package/src/config.js +466 -0
  11. package/src/custom-rules.js +32 -0
  12. package/src/dashboard.js +202 -0
  13. package/src/detector.js +142 -0
  14. package/src/fix-engine.js +48 -0
  15. package/src/fix-registry-extra.js +95 -0
  16. package/src/fix-registry-go-rust.js +77 -0
  17. package/src/fix-registry-java-csharp.js +77 -0
  18. package/src/fix-registry-js.js +99 -0
  19. package/src/fix-registry-mcp-ai.js +57 -0
  20. package/src/fix-registry-python.js +87 -0
  21. package/src/fixer-ruby-php.js +608 -0
  22. package/src/fixer.js +2113 -0
  23. package/src/hooks.js +115 -0
  24. package/src/ignore.js +176 -0
  25. package/src/index.js +384 -0
  26. package/src/metrics.js +126 -0
  27. package/src/monorepo.js +65 -0
  28. package/src/presets.js +54 -0
  29. package/src/reporter.js +975 -0
  30. package/src/rule-worker.js +36 -0
  31. package/src/rules/ast-rules.js +756 -0
  32. package/src/rules/bugs/accessibility.js +235 -0
  33. package/src/rules/bugs/ai-codegen-fixable.js +172 -0
  34. package/src/rules/bugs/ai-codegen.js +365 -0
  35. package/src/rules/bugs/code-smell-bugs.js +247 -0
  36. package/src/rules/bugs/crypto-bugs.js +195 -0
  37. package/src/rules/bugs/docker-bugs.js +158 -0
  38. package/src/rules/bugs/general.js +361 -0
  39. package/src/rules/bugs/go-bugs.js +279 -0
  40. package/src/rules/bugs/index.js +73 -0
  41. package/src/rules/bugs/js-api.js +257 -0
  42. package/src/rules/bugs/js-array-object.js +210 -0
  43. package/src/rules/bugs/js-async-fixable.js +223 -0
  44. package/src/rules/bugs/js-async.js +211 -0
  45. package/src/rules/bugs/js-closure-scope.js +182 -0
  46. package/src/rules/bugs/js-database.js +203 -0
  47. package/src/rules/bugs/js-error-handling.js +148 -0
  48. package/src/rules/bugs/js-logic.js +261 -0
  49. package/src/rules/bugs/js-memory.js +214 -0
  50. package/src/rules/bugs/js-node.js +361 -0
  51. package/src/rules/bugs/js-react.js +373 -0
  52. package/src/rules/bugs/js-regex.js +200 -0
  53. package/src/rules/bugs/js-state.js +272 -0
  54. package/src/rules/bugs/js-type-coercion.js +318 -0
  55. package/src/rules/bugs/nextjs-bugs.js +242 -0
  56. package/src/rules/bugs/nextjs-fixable.js +120 -0
  57. package/src/rules/bugs/node-fixable.js +178 -0
  58. package/src/rules/bugs/python-advanced.js +245 -0
  59. package/src/rules/bugs/python-fixable.js +98 -0
  60. package/src/rules/bugs/python.js +284 -0
  61. package/src/rules/bugs/react-fixable.js +207 -0
  62. package/src/rules/bugs/ruby-bugs.js +182 -0
  63. package/src/rules/bugs/shell-bugs.js +181 -0
  64. package/src/rules/bugs/silent-failures.js +261 -0
  65. package/src/rules/bugs/ts-bugs.js +235 -0
  66. package/src/rules/bugs/unused-vars.js +65 -0
  67. package/src/rules/compliance/accessibility-ext.js +468 -0
  68. package/src/rules/compliance/education.js +322 -0
  69. package/src/rules/compliance/financial.js +421 -0
  70. package/src/rules/compliance/frameworks.js +507 -0
  71. package/src/rules/compliance/healthcare.js +520 -0
  72. package/src/rules/compliance/index.js +2714 -0
  73. package/src/rules/compliance/regional-eu.js +480 -0
  74. package/src/rules/compliance/regional-international.js +903 -0
  75. package/src/rules/cost/index.js +1993 -0
  76. package/src/rules/data/index.js +2503 -0
  77. package/src/rules/dependencies/index.js +1684 -0
  78. package/src/rules/deployment/index.js +2050 -0
  79. package/src/rules/index.js +71 -0
  80. package/src/rules/infrastructure/index.js +3048 -0
  81. package/src/rules/performance/index.js +3455 -0
  82. package/src/rules/quality/index.js +3175 -0
  83. package/src/rules/reliability/index.js +3040 -0
  84. package/src/rules/scope-rules.js +815 -0
  85. package/src/rules/security/ai-api.js +1177 -0
  86. package/src/rules/security/auth.js +1328 -0
  87. package/src/rules/security/cors.js +127 -0
  88. package/src/rules/security/crypto.js +527 -0
  89. package/src/rules/security/csharp.js +862 -0
  90. package/src/rules/security/csrf.js +193 -0
  91. package/src/rules/security/dart.js +835 -0
  92. package/src/rules/security/deserialization.js +291 -0
  93. package/src/rules/security/file-upload.js +187 -0
  94. package/src/rules/security/go.js +850 -0
  95. package/src/rules/security/headers.js +235 -0
  96. package/src/rules/security/index.js +65 -0
  97. package/src/rules/security/injection.js +1639 -0
  98. package/src/rules/security/mcp-server.js +71 -0
  99. package/src/rules/security/misconfiguration.js +660 -0
  100. package/src/rules/security/oauth-jwt.js +329 -0
  101. package/src/rules/security/path-traversal.js +295 -0
  102. package/src/rules/security/php.js +1054 -0
  103. package/src/rules/security/prototype-pollution.js +283 -0
  104. package/src/rules/security/rate-limiting.js +208 -0
  105. package/src/rules/security/ruby.js +1061 -0
  106. package/src/rules/security/rust.js +693 -0
  107. package/src/rules/security/secrets.js +747 -0
  108. package/src/rules/security/shell.js +647 -0
  109. package/src/rules/security/ssrf.js +298 -0
  110. package/src/rules/security/supply-chain-advanced.js +393 -0
  111. package/src/rules/security/supply-chain.js +734 -0
  112. package/src/rules/security/swift.js +835 -0
  113. package/src/rules/security/taint.js +27 -0
  114. package/src/rules/security/xss.js +520 -0
  115. package/src/scan-cache.js +71 -0
  116. package/src/scanner.js +710 -0
  117. package/src/scope-analyzer.js +685 -0
  118. package/src/share.js +88 -0
  119. package/src/taint.js +300 -0
  120. package/src/telemetry.js +183 -0
  121. package/src/tracer.js +190 -0
  122. package/src/upload.js +35 -0
  123. package/src/worker.js +31 -0
@@ -0,0 +1,835 @@
1
+ /**
2
+ * Swift / iOS / macOS Security Rules (SEC-SWIFT-001 through SEC-SWIFT-040)
3
+ */
4
+
5
+ const isSwift = (f) => f.endsWith('.swift');
6
+
7
+ const SKIP_PATH = /[/\\](test|tests|__tests__|__mocks__|mocks|fixtures|__fixtures__|spec|__snapshots__|node_modules|vendor|dist|build|Pods|Carthage)[/\\]/i;
8
+ const COMMENT_LINE = /^\s*(\/\/|\/\*|\*)/;
9
+
10
+ function scanLines(content, regex, file, rule) {
11
+ const findings = [];
12
+ const lines = content.split('\n');
13
+ for (let i = 0; i < lines.length; i++) {
14
+ const line = lines[i];
15
+ if (COMMENT_LINE.test(line)) continue;
16
+ if (regex.test(line)) {
17
+ findings.push({
18
+ ruleId: rule.id,
19
+ category: rule.category,
20
+ severity: rule.severity,
21
+ title: rule.title,
22
+ description: rule.description,
23
+ confidence: rule.confidence,
24
+ file,
25
+ line: i + 1,
26
+ fix: rule.fix || null,
27
+ });
28
+ }
29
+ }
30
+ return findings;
31
+ }
32
+
33
+ const rules = [
34
+ // SEC-SWIFT-001: UserDefaults for sensitive data
35
+ {
36
+ id: 'SEC-SWIFT-001',
37
+ category: 'security',
38
+ severity: 'high',
39
+ confidence: 'likely',
40
+ title: 'Sensitive Data in UserDefaults',
41
+ description: 'UserDefaults stores data unencrypted in plist files. Sensitive data such as passwords, tokens, or keys should be stored in the Keychain.',
42
+ fix: { suggestion: 'Use Keychain Services (SecItemAdd) instead of UserDefaults for sensitive data.' },
43
+ check({ files }) {
44
+ const findings = [];
45
+ const pattern = /UserDefaults\s*\..*(?:password|token|secret|apiKey|api_key|credential|auth)/i;
46
+ for (const [path, content] of files) {
47
+ if (SKIP_PATH.test(path)) continue;
48
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
49
+ }
50
+ return findings;
51
+ },
52
+ },
53
+
54
+ // SEC-SWIFT-002: Hardcoded API keys
55
+ {
56
+ id: 'SEC-SWIFT-002',
57
+ category: 'security',
58
+ severity: 'critical',
59
+ confidence: 'likely',
60
+ title: 'Hardcoded API Key or Secret',
61
+ description: 'API keys or secrets hardcoded in source code can be extracted from compiled binaries.',
62
+ fix: { suggestion: 'Store secrets in a secure configuration file excluded from source control, or use a secrets manager.' },
63
+ check({ files }) {
64
+ const findings = [];
65
+ const pattern = /(?:let|var)\s+\w*(?:apiKey|api_key|secret|password|token|apiSecret|api_secret)\w*\s*[:=]\s*"/i;
66
+ for (const [path, content] of files) {
67
+ if (SKIP_PATH.test(path)) continue;
68
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
69
+ }
70
+ return findings;
71
+ },
72
+ },
73
+
74
+ // SEC-SWIFT-003: Insecure keychain accessibility
75
+ {
76
+ id: 'SEC-SWIFT-003',
77
+ category: 'security',
78
+ severity: 'high',
79
+ confidence: 'likely',
80
+ title: 'Insecure Keychain Accessibility (kSecAttrAccessibleAlways)',
81
+ description: 'kSecAttrAccessibleAlways makes keychain items accessible even when the device is locked, weakening data protection.',
82
+ fix: { suggestion: 'Use kSecAttrAccessibleWhenUnlockedThisDeviceOnly or kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.' },
83
+ check({ files }) {
84
+ const findings = [];
85
+ const pattern = /kSecAttrAccessibleAlways(?:ThisDeviceOnly)?(?!After)/;
86
+ for (const [path, content] of files) {
87
+ if (SKIP_PATH.test(path)) continue;
88
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
89
+ }
90
+ return findings;
91
+ },
92
+ },
93
+
94
+ // SEC-SWIFT-004: App Transport Security disabled
95
+ {
96
+ id: 'SEC-SWIFT-004',
97
+ category: 'security',
98
+ severity: 'high',
99
+ confidence: 'likely',
100
+ title: 'App Transport Security Exception',
101
+ description: 'NSAllowsArbitraryLoads disables ATS, allowing insecure HTTP connections.',
102
+ fix: { suggestion: 'Remove NSAllowsArbitraryLoads or limit exceptions to specific domains with NSExceptionDomains.' },
103
+ check({ files }) {
104
+ const findings = [];
105
+ const pattern = /NSAllowsArbitraryLoads\s*.*true/i;
106
+ for (const [path, content] of files) {
107
+ if (SKIP_PATH.test(path)) continue;
108
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
109
+ }
110
+ return findings;
111
+ },
112
+ },
113
+
114
+ // SEC-SWIFT-005: Missing certificate pinning
115
+ {
116
+ id: 'SEC-SWIFT-005',
117
+ category: 'security',
118
+ severity: 'medium',
119
+ confidence: 'suggestion',
120
+ title: 'Missing Certificate Pinning',
121
+ description: 'URLSession delegate that always trusts server certificates disables certificate validation.',
122
+ fix: { suggestion: 'Implement proper certificate pinning using SecTrust evaluation or use a library like TrustKit.' },
123
+ check({ files }) {
124
+ const findings = [];
125
+ const pattern = /\.serverTrust\b|completionHandler\s*\(\s*\.useCredential|urlSession.*didReceive.*challenge.*completionHandler/;
126
+ for (const [path, content] of files) {
127
+ if (SKIP_PATH.test(path)) continue;
128
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
129
+ }
130
+ return findings;
131
+ },
132
+ },
133
+
134
+ // SEC-SWIFT-006: WebView JavaScript enabled
135
+ {
136
+ id: 'SEC-SWIFT-006',
137
+ category: 'security',
138
+ severity: 'medium',
139
+ confidence: 'suggestion',
140
+ title: 'WebView JavaScript Enabled Without Safeguards',
141
+ description: 'Enabling JavaScript in WKWebView without proper content restrictions can lead to XSS or script injection.',
142
+ fix: { suggestion: 'Set javaScriptEnabled only when needed and implement WKContentRuleList or WKScriptMessageHandler for control.' },
143
+ check({ files }) {
144
+ const findings = [];
145
+ const pattern = /javaScriptEnabled\s*=\s*true|\.javaScriptEnabled\s*=\s*true/;
146
+ for (const [path, content] of files) {
147
+ if (SKIP_PATH.test(path)) continue;
148
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
149
+ }
150
+ return findings;
151
+ },
152
+ },
153
+
154
+ // SEC-SWIFT-007: UIWebView usage
155
+ {
156
+ id: 'SEC-SWIFT-007',
157
+ category: 'security',
158
+ severity: 'high',
159
+ confidence: 'likely',
160
+ title: 'Deprecated UIWebView Usage',
161
+ description: 'UIWebView is deprecated and has known security vulnerabilities. Apple rejects apps using UIWebView.',
162
+ fix: { suggestion: 'Migrate to WKWebView which provides better security and performance.' },
163
+ check({ files }) {
164
+ const findings = [];
165
+ const pattern = /UIWebView/;
166
+ for (const [path, content] of files) {
167
+ if (SKIP_PATH.test(path)) continue;
168
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
169
+ }
170
+ return findings;
171
+ },
172
+ },
173
+
174
+ // SEC-SWIFT-008: Clipboard data leak
175
+ {
176
+ id: 'SEC-SWIFT-008',
177
+ category: 'security',
178
+ severity: 'medium',
179
+ confidence: 'suggestion',
180
+ title: 'Clipboard Data Leak via UIPasteboard',
181
+ description: 'Copying sensitive data to UIPasteboard makes it accessible to other apps.',
182
+ fix: { suggestion: 'Use UIPasteboard with localOnly and expirationDate options, or avoid copying sensitive data.' },
183
+ check({ files }) {
184
+ const findings = [];
185
+ const pattern = /UIPasteboard\.general\.(?:string|setValue|setItems|setObjects)/;
186
+ for (const [path, content] of files) {
187
+ if (SKIP_PATH.test(path)) continue;
188
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
189
+ }
190
+ return findings;
191
+ },
192
+ },
193
+
194
+ // SEC-SWIFT-009: NSLog with sensitive data
195
+ {
196
+ id: 'SEC-SWIFT-009',
197
+ category: 'security',
198
+ severity: 'medium',
199
+ confidence: 'likely',
200
+ title: 'NSLog with Sensitive Data',
201
+ description: 'NSLog output is written to the system log which can be read by other apps on the device.',
202
+ fix: { suggestion: 'Remove NSLog calls containing sensitive data or use os_log with appropriate privacy levels.' },
203
+ check({ files }) {
204
+ const findings = [];
205
+ const pattern = /NSLog\s*\(.*(?:password|token|secret|key|credential|auth|ssn|credit)/i;
206
+ for (const [path, content] of files) {
207
+ if (SKIP_PATH.test(path)) continue;
208
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
209
+ }
210
+ return findings;
211
+ },
212
+ },
213
+
214
+ // SEC-SWIFT-010: print() with sensitive data
215
+ {
216
+ id: 'SEC-SWIFT-010',
217
+ category: 'security',
218
+ severity: 'medium',
219
+ confidence: 'suggestion',
220
+ title: 'print() Statement with Sensitive Data',
221
+ description: 'print() statements with sensitive data remain in release builds and can leak information.',
222
+ fix: { suggestion: 'Use #if DEBUG guards around print statements or use os_log with privacy annotations.' },
223
+ check({ files }) {
224
+ const findings = [];
225
+ const pattern = /print\s*\(.*(?:password|token|secret|key|credential|auth)/i;
226
+ for (const [path, content] of files) {
227
+ if (SKIP_PATH.test(path)) continue;
228
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
229
+ }
230
+ return findings;
231
+ },
232
+ },
233
+
234
+ // SEC-SWIFT-011: Weak crypto CC_MD5
235
+ {
236
+ id: 'SEC-SWIFT-011',
237
+ category: 'security',
238
+ severity: 'high',
239
+ confidence: 'likely',
240
+ title: 'Weak Hashing Algorithm (MD5)',
241
+ description: 'CC_MD5 is cryptographically broken and should not be used for security purposes.',
242
+ fix: { suggestion: 'Use SHA-256 or higher via CC_SHA256 or CryptoKit (SHA256).' },
243
+ check({ files }) {
244
+ const findings = [];
245
+ const pattern = /CC_MD5\s*\(|\.md5\b|Insecure\.MD5/;
246
+ for (const [path, content] of files) {
247
+ if (SKIP_PATH.test(path)) continue;
248
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
249
+ }
250
+ return findings;
251
+ },
252
+ },
253
+
254
+ // SEC-SWIFT-012: Weak crypto CC_SHA1
255
+ {
256
+ id: 'SEC-SWIFT-012',
257
+ category: 'security',
258
+ severity: 'high',
259
+ confidence: 'likely',
260
+ title: 'Weak Hashing Algorithm (SHA1)',
261
+ description: 'CC_SHA1 is cryptographically weak and collision attacks are practical.',
262
+ fix: { suggestion: 'Use SHA-256 or higher via CC_SHA256 or CryptoKit (SHA256).' },
263
+ check({ files }) {
264
+ const findings = [];
265
+ const pattern = /CC_SHA1\s*\(|\.sha1\b|Insecure\.SHA1/;
266
+ for (const [path, content] of files) {
267
+ if (SKIP_PATH.test(path)) continue;
268
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
269
+ }
270
+ return findings;
271
+ },
272
+ },
273
+
274
+ // SEC-SWIFT-013: Force unwrap on network data
275
+ {
276
+ id: 'SEC-SWIFT-013',
277
+ category: 'security',
278
+ severity: 'medium',
279
+ confidence: 'suggestion',
280
+ title: 'Force Unwrap on Network Data',
281
+ description: 'Force unwrapping (!) network response data can crash the app if the server returns unexpected data.',
282
+ fix: { suggestion: 'Use optional binding (if let / guard let) or try? for safer unwrapping.' },
283
+ check({ files }) {
284
+ const findings = [];
285
+ const pattern = /(?:data|response|json|result)\s*!\s*\.|(?:JSONSerialization|JSONDecoder).*!$/;
286
+ for (const [path, content] of files) {
287
+ if (SKIP_PATH.test(path)) continue;
288
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
289
+ }
290
+ return findings;
291
+ },
292
+ },
293
+
294
+ // SEC-SWIFT-014: Insecure HTTP URL
295
+ {
296
+ id: 'SEC-SWIFT-014',
297
+ category: 'security',
298
+ severity: 'high',
299
+ confidence: 'likely',
300
+ title: 'Insecure HTTP URL',
301
+ description: 'Using http:// instead of https:// transmits data in plaintext, vulnerable to interception.',
302
+ fix: { suggestion: 'Use https:// for all network connections.' },
303
+ check({ files }) {
304
+ const findings = [];
305
+ const pattern = /URL\s*\(\s*string\s*:\s*"http:\/\//;
306
+ for (const [path, content] of files) {
307
+ if (SKIP_PATH.test(path)) continue;
308
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
309
+ }
310
+ return findings;
311
+ },
312
+ },
313
+
314
+ // SEC-SWIFT-015: Jailbreak detection bypass
315
+ {
316
+ id: 'SEC-SWIFT-015',
317
+ category: 'security',
318
+ severity: 'medium',
319
+ confidence: 'suggestion',
320
+ title: 'Weak Jailbreak Detection',
321
+ description: 'Checking only for Cydia or specific file paths is easily bypassed on jailbroken devices.',
322
+ fix: { suggestion: 'Use multiple jailbreak detection checks including fork(), file existence, dylib injection, and sandbox integrity.' },
323
+ check({ files }) {
324
+ const findings = [];
325
+ const pattern = /\/Applications\/Cydia\.app|\/private\/var\/lib\/apt|canOpenURL.*cydia/;
326
+ for (const [path, content] of files) {
327
+ if (SKIP_PATH.test(path)) continue;
328
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
329
+ }
330
+ return findings;
331
+ },
332
+ },
333
+
334
+ // SEC-SWIFT-016: Insecure biometric auth
335
+ {
336
+ id: 'SEC-SWIFT-016',
337
+ category: 'security',
338
+ severity: 'high',
339
+ confidence: 'likely',
340
+ title: 'Insecure Biometric Authentication',
341
+ description: 'Using evaluatePolicy alone for biometric auth without Keychain integration allows bypass via runtime manipulation.',
342
+ fix: { suggestion: 'Combine LAContext.evaluatePolicy with Keychain access control (SecAccessControlCreateWithFlags) for defense in depth.' },
343
+ check({ files }) {
344
+ const findings = [];
345
+ const pattern = /evaluatePolicy\s*\(\s*\.deviceOwnerAuthentication/;
346
+ for (const [path, content] of files) {
347
+ if (SKIP_PATH.test(path)) continue;
348
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
349
+ }
350
+ return findings;
351
+ },
352
+ },
353
+
354
+ // SEC-SWIFT-017: URL scheme injection
355
+ {
356
+ id: 'SEC-SWIFT-017',
357
+ category: 'security',
358
+ severity: 'medium',
359
+ confidence: 'suggestion',
360
+ title: 'URL Scheme Injection',
361
+ description: 'Custom URL scheme handlers that do not validate input can be exploited by malicious apps.',
362
+ fix: { suggestion: 'Validate the source application and sanitize URL parameters in application(_:open:options:).' },
363
+ check({ files }) {
364
+ const findings = [];
365
+ const pattern = /func\s+application.*open\s+url.*options.*->.*Bool|openURL\s*\(/;
366
+ for (const [path, content] of files) {
367
+ if (SKIP_PATH.test(path)) continue;
368
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
369
+ }
370
+ return findings;
371
+ },
372
+ },
373
+
374
+ // SEC-SWIFT-018: Insecure file permissions
375
+ {
376
+ id: 'SEC-SWIFT-018',
377
+ category: 'security',
378
+ severity: 'medium',
379
+ confidence: 'likely',
380
+ title: 'Insecure File Protection',
381
+ description: 'FileProtectionType.none or .completeUntilFirstUserAuthentication leaves files accessible when locked.',
382
+ fix: { suggestion: 'Use .completeUnlessOpen or .complete for sensitive files.' },
383
+ check({ files }) {
384
+ const findings = [];
385
+ const pattern = /FileProtectionType\.none|\.protectionNone|\.completeUntilFirstUserAuthentication/;
386
+ for (const [path, content] of files) {
387
+ if (SKIP_PATH.test(path)) continue;
388
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
389
+ }
390
+ return findings;
391
+ },
392
+ },
393
+
394
+ // SEC-SWIFT-019: Background snapshot leak
395
+ {
396
+ id: 'SEC-SWIFT-019',
397
+ category: 'security',
398
+ severity: 'low',
399
+ confidence: 'suggestion',
400
+ title: 'Background Snapshot Information Leak',
401
+ description: 'iOS takes a screenshot of the app when backgrounded. Sensitive data may be visible in the app switcher.',
402
+ fix: { suggestion: 'Implement applicationDidEnterBackground to hide sensitive content with a blur or placeholder view.' },
403
+ check({ files }) {
404
+ const findings = [];
405
+ const pattern = /applicationDidEnterBackground|sceneDidEnterBackground/;
406
+ for (const [path, content] of files) {
407
+ if (SKIP_PATH.test(path)) continue;
408
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
409
+ }
410
+ return findings;
411
+ },
412
+ },
413
+
414
+ // SEC-SWIFT-020: Missing input validation
415
+ {
416
+ id: 'SEC-SWIFT-020',
417
+ category: 'security',
418
+ severity: 'medium',
419
+ confidence: 'suggestion',
420
+ title: 'Missing Input Validation on Text Fields',
421
+ description: 'UITextField/UITextView content used directly without validation or sanitization.',
422
+ fix: { suggestion: 'Validate and sanitize text field input before using it in queries, URLs, or display.' },
423
+ check({ files }) {
424
+ const findings = [];
425
+ const pattern = /\.text\s*!\s*.*(?:URL|query|sql|exec|eval|request)/i;
426
+ for (const [path, content] of files) {
427
+ if (SKIP_PATH.test(path)) continue;
428
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
429
+ }
430
+ return findings;
431
+ },
432
+ },
433
+
434
+ // SEC-SWIFT-021: Insecure random number generation
435
+ {
436
+ id: 'SEC-SWIFT-021',
437
+ category: 'security',
438
+ severity: 'medium',
439
+ confidence: 'likely',
440
+ title: 'Insecure Random Number Generation',
441
+ description: 'arc4random() or rand()/srand() may not provide cryptographically secure randomness.',
442
+ fix: { suggestion: 'Use SecRandomCopyBytes or SystemRandomNumberGenerator for security-sensitive contexts.' },
443
+ check({ files }) {
444
+ const findings = [];
445
+ const pattern = /\b(?:arc4random\b|srand\s*\(|rand\s*\(\s*\))/;
446
+ for (const [path, content] of files) {
447
+ if (SKIP_PATH.test(path)) continue;
448
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
449
+ }
450
+ return findings;
451
+ },
452
+ },
453
+
454
+ // SEC-SWIFT-022: Hardcoded encryption key
455
+ {
456
+ id: 'SEC-SWIFT-022',
457
+ category: 'security',
458
+ severity: 'critical',
459
+ confidence: 'likely',
460
+ title: 'Hardcoded Encryption Key',
461
+ description: 'Encryption keys hardcoded in source code can be extracted from the binary.',
462
+ fix: { suggestion: 'Generate keys at runtime and store in Keychain, or use key derivation functions.' },
463
+ check({ files }) {
464
+ const findings = [];
465
+ const pattern = /(?:let|var)\s+\w*(?:encryptionKey|aesKey|privateKey|symmetricKey)\w*\s*[:=]\s*"/i;
466
+ for (const [path, content] of files) {
467
+ if (SKIP_PATH.test(path)) continue;
468
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
469
+ }
470
+ return findings;
471
+ },
472
+ },
473
+
474
+ // SEC-SWIFT-023: Disabled SSL pinning in Alamofire
475
+ {
476
+ id: 'SEC-SWIFT-023',
477
+ category: 'security',
478
+ severity: 'high',
479
+ confidence: 'likely',
480
+ title: 'Disabled SSL Pinning in Alamofire',
481
+ description: 'DisabledTrustEvaluator or ServerTrustPolicy.disableEvaluation disables certificate validation.',
482
+ fix: { suggestion: 'Use PinnedCertificatesTrustEvaluator or PublicKeysTrustEvaluator for proper pinning.' },
483
+ check({ files }) {
484
+ const findings = [];
485
+ const pattern = /DisabledTrustEvaluator|DisabledEvaluator|\.disableEvaluation/;
486
+ for (const [path, content] of files) {
487
+ if (SKIP_PATH.test(path)) continue;
488
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
489
+ }
490
+ return findings;
491
+ },
492
+ },
493
+
494
+ // SEC-SWIFT-024: CoreData unencrypted store
495
+ {
496
+ id: 'SEC-SWIFT-024',
497
+ category: 'security',
498
+ severity: 'medium',
499
+ confidence: 'suggestion',
500
+ title: 'Unencrypted CoreData Persistent Store',
501
+ description: 'CoreData SQLite stores are unencrypted by default, exposing data at rest.',
502
+ fix: { suggestion: 'Use NSPersistentStoreFileProtectionKey or an encrypted store like EncryptedCoreData.' },
503
+ check({ files }) {
504
+ const findings = [];
505
+ const pattern = /NSSQLiteStoreType|addPersistentStore.*NSSQLiteStoreType/;
506
+ for (const [path, content] of files) {
507
+ if (SKIP_PATH.test(path)) continue;
508
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
509
+ }
510
+ return findings;
511
+ },
512
+ },
513
+
514
+ // SEC-SWIFT-025: Realm unencrypted
515
+ {
516
+ id: 'SEC-SWIFT-025',
517
+ category: 'security',
518
+ severity: 'medium',
519
+ confidence: 'suggestion',
520
+ title: 'Unencrypted Realm Database',
521
+ description: 'Realm databases are unencrypted by default, exposing sensitive data at rest.',
522
+ fix: { suggestion: 'Set Realm.Configuration encryptionKey with a key stored in the Keychain.' },
523
+ check({ files }) {
524
+ const findings = [];
525
+ const pattern = /Realm\s*\(\s*\)|Realm\.Configuration\s*\(\s*\)/;
526
+ for (const [path, content] of files) {
527
+ if (SKIP_PATH.test(path)) continue;
528
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
529
+ }
530
+ return findings;
531
+ },
532
+ },
533
+
534
+ // SEC-SWIFT-026: Insecure deserialization
535
+ {
536
+ id: 'SEC-SWIFT-026',
537
+ category: 'security',
538
+ severity: 'high',
539
+ confidence: 'likely',
540
+ title: 'Insecure Deserialization (NSKeyedUnarchiver)',
541
+ description: 'NSKeyedUnarchiver.unarchiveObject is deprecated and can lead to arbitrary code execution.',
542
+ fix: { suggestion: 'Use unarchivedObject(ofClass:from:) with NSSecureCoding and requiresSecureCoding = true.' },
543
+ check({ files }) {
544
+ const findings = [];
545
+ const pattern = /unarchiveObject\s*\(with|NSKeyedUnarchiver\s*\(\s*forReadingWith/;
546
+ for (const [path, content] of files) {
547
+ if (SKIP_PATH.test(path)) continue;
548
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
549
+ }
550
+ return findings;
551
+ },
552
+ },
553
+
554
+ // SEC-SWIFT-027: SQL injection in SQLite
555
+ {
556
+ id: 'SEC-SWIFT-027',
557
+ category: 'security',
558
+ severity: 'critical',
559
+ confidence: 'likely',
560
+ title: 'SQL Injection in SQLite',
561
+ description: 'String interpolation in SQLite queries allows SQL injection.',
562
+ fix: { suggestion: 'Use parameterized queries with sqlite3_bind_text or a safe wrapper like GRDB/SQLite.swift.' },
563
+ check({ files }) {
564
+ const findings = [];
565
+ const pattern = /sqlite3_exec\s*\(.*\\?\(|sqlite3_prepare.*".*\\?\(/;
566
+ for (const [path, content] of files) {
567
+ if (SKIP_PATH.test(path)) continue;
568
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
569
+ }
570
+ return findings;
571
+ },
572
+ },
573
+
574
+ // SEC-SWIFT-028: Insecure cookie storage
575
+ {
576
+ id: 'SEC-SWIFT-028',
577
+ category: 'security',
578
+ severity: 'medium',
579
+ confidence: 'suggestion',
580
+ title: 'Insecure Cookie Storage',
581
+ description: 'HTTPCookieStorage.shared stores cookies that may be accessible to other apps.',
582
+ fix: { suggestion: 'Use ephemeral URLSession configuration or set cookie security attributes (Secure, HttpOnly).' },
583
+ check({ files }) {
584
+ const findings = [];
585
+ const pattern = /HTTPCookieStorage\.shared|\.httpShouldHandleCookies\s*=\s*true/;
586
+ for (const [path, content] of files) {
587
+ if (SKIP_PATH.test(path)) continue;
588
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
589
+ }
590
+ return findings;
591
+ },
592
+ },
593
+
594
+ // SEC-SWIFT-029: Cleartext traffic
595
+ {
596
+ id: 'SEC-SWIFT-029',
597
+ category: 'security',
598
+ severity: 'high',
599
+ confidence: 'likely',
600
+ title: 'Cleartext Network Traffic',
601
+ description: 'NSExceptionAllowsInsecureHTTPLoads permits cleartext HTTP traffic to specific domains.',
602
+ fix: { suggestion: 'Use HTTPS for all connections. If HTTP is required, document the reason and limit the exception scope.' },
603
+ check({ files }) {
604
+ const findings = [];
605
+ const pattern = /NSExceptionAllowsInsecureHTTPLoads|NSTemporaryExceptionAllowsInsecureHTTPLoads/;
606
+ for (const [path, content] of files) {
607
+ if (SKIP_PATH.test(path)) continue;
608
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
609
+ }
610
+ return findings;
611
+ },
612
+ },
613
+
614
+ // SEC-SWIFT-030: Logging sensitive data with os_log
615
+ {
616
+ id: 'SEC-SWIFT-030',
617
+ category: 'security',
618
+ severity: 'medium',
619
+ confidence: 'suggestion',
620
+ title: 'Sensitive Data in os_log Without Privacy',
621
+ description: 'os_log with %{public} format specifier makes sensitive data visible in Console.app.',
622
+ fix: { suggestion: 'Use %{private} for sensitive data or omit the public specifier.' },
623
+ check({ files }) {
624
+ const findings = [];
625
+ const pattern = /os_log.*%\{public\}.*(?:password|token|secret|key|credential)/i;
626
+ for (const [path, content] of files) {
627
+ if (SKIP_PATH.test(path)) continue;
628
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
629
+ }
630
+ return findings;
631
+ },
632
+ },
633
+
634
+ // SEC-SWIFT-031: Disabled code signing
635
+ {
636
+ id: 'SEC-SWIFT-031',
637
+ category: 'security',
638
+ severity: 'high',
639
+ confidence: 'likely',
640
+ title: 'Code Signing Disabled or Ad-Hoc',
641
+ description: 'Missing or ad-hoc code signing weakens binary integrity verification.',
642
+ fix: { suggestion: 'Enable proper code signing with a valid Apple Developer certificate.' },
643
+ check({ files }) {
644
+ const findings = [];
645
+ const pattern = /CODE_SIGNING_ALLOWED\s*=\s*NO|CODE_SIGN_IDENTITY\s*=\s*""/;
646
+ for (const [path, content] of files) {
647
+ if (SKIP_PATH.test(path)) continue;
648
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
649
+ }
650
+ return findings;
651
+ },
652
+ },
653
+
654
+ // SEC-SWIFT-032: WebView loadHTMLString with user data
655
+ {
656
+ id: 'SEC-SWIFT-032',
657
+ category: 'security',
658
+ severity: 'high',
659
+ confidence: 'likely',
660
+ title: 'WebView XSS via loadHTMLString',
661
+ description: 'Loading unsanitized HTML content into a WebView can lead to cross-site scripting.',
662
+ fix: { suggestion: 'Sanitize HTML content before loading, or use loadFileURL with a safe directory.' },
663
+ check({ files }) {
664
+ const findings = [];
665
+ const pattern = /loadHTMLString\s*\(/;
666
+ for (const [path, content] of files) {
667
+ if (SKIP_PATH.test(path)) continue;
668
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
669
+ }
670
+ return findings;
671
+ },
672
+ },
673
+
674
+ // SEC-SWIFT-033: Insecure IPC via pasteboard
675
+ {
676
+ id: 'SEC-SWIFT-033',
677
+ category: 'security',
678
+ severity: 'medium',
679
+ confidence: 'suggestion',
680
+ title: 'Insecure Inter-Process Communication via Pasteboard',
681
+ description: 'Using UIPasteboard for IPC exposes data to all apps on the device.',
682
+ fix: { suggestion: 'Use App Groups with shared UserDefaults or Keychain sharing for secure IPC.' },
683
+ check({ files }) {
684
+ const findings = [];
685
+ const pattern = /UIPasteboard\s*\(\s*name\s*:/;
686
+ for (const [path, content] of files) {
687
+ if (SKIP_PATH.test(path)) continue;
688
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
689
+ }
690
+ return findings;
691
+ },
692
+ },
693
+
694
+ // SEC-SWIFT-034: ECB mode encryption
695
+ {
696
+ id: 'SEC-SWIFT-034',
697
+ category: 'security',
698
+ severity: 'high',
699
+ confidence: 'likely',
700
+ title: 'ECB Mode Encryption',
701
+ description: 'ECB mode preserves data patterns and should not be used for encrypting data.',
702
+ fix: { suggestion: 'Use GCM (recommended) or CBC mode with a random IV.' },
703
+ check({ files }) {
704
+ const findings = [];
705
+ const pattern = /\.ECBMode|kCCOptionECBMode|\.ecb\b/;
706
+ for (const [path, content] of files) {
707
+ if (SKIP_PATH.test(path)) continue;
708
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
709
+ }
710
+ return findings;
711
+ },
712
+ },
713
+
714
+ // SEC-SWIFT-035: Keychain without access group
715
+ {
716
+ id: 'SEC-SWIFT-035',
717
+ category: 'security',
718
+ severity: 'low',
719
+ confidence: 'suggestion',
720
+ title: 'Keychain Without Access Group Restriction',
721
+ description: 'Keychain items without explicit access group may be shared unintentionally across apps.',
722
+ fix: { suggestion: 'Set kSecAttrAccessGroup to restrict Keychain item access to specific apps.' },
723
+ check({ files }) {
724
+ const findings = [];
725
+ const pattern = /SecItemAdd\s*\((?!.*kSecAttrAccessGroup)/;
726
+ for (const [path, content] of files) {
727
+ if (SKIP_PATH.test(path)) continue;
728
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
729
+ }
730
+ return findings;
731
+ },
732
+ },
733
+
734
+ // SEC-SWIFT-036: Dynamic library loading
735
+ {
736
+ id: 'SEC-SWIFT-036',
737
+ category: 'security',
738
+ severity: 'high',
739
+ confidence: 'likely',
740
+ title: 'Dynamic Library Loading',
741
+ description: 'dlopen can load malicious libraries at runtime, bypassing code signing.',
742
+ fix: { suggestion: 'Avoid dynamic library loading. If necessary, verify the loaded library integrity.' },
743
+ check({ files }) {
744
+ const findings = [];
745
+ const pattern = /dlopen\s*\(|NSBundle.*load\b/;
746
+ for (const [path, content] of files) {
747
+ if (SKIP_PATH.test(path)) continue;
748
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
749
+ }
750
+ return findings;
751
+ },
752
+ },
753
+
754
+ // SEC-SWIFT-037: Swizzling usage
755
+ {
756
+ id: 'SEC-SWIFT-037',
757
+ category: 'security',
758
+ severity: 'medium',
759
+ confidence: 'suggestion',
760
+ title: 'Method Swizzling Detected',
761
+ description: 'Method swizzling can be used to intercept sensitive method calls at runtime.',
762
+ fix: { suggestion: 'Avoid swizzling security-critical methods. Use protocol-based approaches instead.' },
763
+ check({ files }) {
764
+ const findings = [];
765
+ const pattern = /method_exchangeImplementations|class_replaceMethod|method_setImplementation/;
766
+ for (const [path, content] of files) {
767
+ if (SKIP_PATH.test(path)) continue;
768
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
769
+ }
770
+ return findings;
771
+ },
772
+ },
773
+
774
+ // SEC-SWIFT-038: Debug assertions in release
775
+ {
776
+ id: 'SEC-SWIFT-038',
777
+ category: 'security',
778
+ severity: 'low',
779
+ confidence: 'suggestion',
780
+ title: 'Assertion May Be Stripped in Release',
781
+ description: 'assert() is removed in release builds. Security checks using assert will not execute.',
782
+ fix: { suggestion: 'Use precondition() or preconditionFailure() for security-critical checks that must remain in release builds.' },
783
+ check({ files }) {
784
+ const findings = [];
785
+ const pattern = /\bassert\s*\(.*(?:auth|permission|access|security|valid)/i;
786
+ for (const [path, content] of files) {
787
+ if (SKIP_PATH.test(path)) continue;
788
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
789
+ }
790
+ return findings;
791
+ },
792
+ },
793
+
794
+ // SEC-SWIFT-039: Keyboard cache for sensitive fields
795
+ {
796
+ id: 'SEC-SWIFT-039',
797
+ category: 'security',
798
+ severity: 'low',
799
+ confidence: 'suggestion',
800
+ title: 'Keyboard Cache for Sensitive Input',
801
+ description: 'iOS caches keyboard input for autocorrect. Sensitive fields should disable autocorrection.',
802
+ fix: { suggestion: 'Set autocorrectionType = .no and isSecureTextEntry = true for sensitive text fields.' },
803
+ check({ files }) {
804
+ const findings = [];
805
+ const pattern = /\.isSecureTextEntry\s*=\s*false|secureTextEntry.*false/;
806
+ for (const [path, content] of files) {
807
+ if (SKIP_PATH.test(path)) continue;
808
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
809
+ }
810
+ return findings;
811
+ },
812
+ },
813
+
814
+ // SEC-SWIFT-040: Hardcoded certificate
815
+ {
816
+ id: 'SEC-SWIFT-040',
817
+ category: 'security',
818
+ severity: 'medium',
819
+ confidence: 'likely',
820
+ title: 'Hardcoded Certificate or Public Key',
821
+ description: 'Hardcoded certificates make rotation difficult and may include private key material.',
822
+ fix: { suggestion: 'Load certificates from the app bundle or fetch public keys dynamically with fallback pinning.' },
823
+ check({ files }) {
824
+ const findings = [];
825
+ const pattern = /-----BEGIN\s+(?:CERTIFICATE|RSA\s+PRIVATE\s+KEY|PUBLIC\s+KEY)-----/;
826
+ for (const [path, content] of files) {
827
+ if (SKIP_PATH.test(path)) continue;
828
+ if (isSwift(path)) findings.push(...scanLines(content, pattern, path, this));
829
+ }
830
+ return findings;
831
+ },
832
+ },
833
+ ];
834
+
835
+ export default rules;