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.
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/bin/doorman.js +444 -0
- package/package.json +74 -0
- package/src/ai-fixer.js +559 -0
- package/src/ast-scanner.js +434 -0
- package/src/auth.js +149 -0
- package/src/baseline.js +48 -0
- package/src/compliance.js +539 -0
- package/src/config.js +466 -0
- package/src/custom-rules.js +32 -0
- package/src/dashboard.js +202 -0
- package/src/detector.js +142 -0
- package/src/fix-engine.js +48 -0
- package/src/fix-registry-extra.js +95 -0
- package/src/fix-registry-go-rust.js +77 -0
- package/src/fix-registry-java-csharp.js +77 -0
- package/src/fix-registry-js.js +99 -0
- package/src/fix-registry-mcp-ai.js +57 -0
- package/src/fix-registry-python.js +87 -0
- package/src/fixer-ruby-php.js +608 -0
- package/src/fixer.js +2113 -0
- package/src/hooks.js +115 -0
- package/src/ignore.js +176 -0
- package/src/index.js +384 -0
- package/src/metrics.js +126 -0
- package/src/monorepo.js +65 -0
- package/src/presets.js +54 -0
- package/src/reporter.js +975 -0
- package/src/rule-worker.js +36 -0
- package/src/rules/ast-rules.js +756 -0
- package/src/rules/bugs/accessibility.js +235 -0
- package/src/rules/bugs/ai-codegen-fixable.js +172 -0
- package/src/rules/bugs/ai-codegen.js +365 -0
- package/src/rules/bugs/code-smell-bugs.js +247 -0
- package/src/rules/bugs/crypto-bugs.js +195 -0
- package/src/rules/bugs/docker-bugs.js +158 -0
- package/src/rules/bugs/general.js +361 -0
- package/src/rules/bugs/go-bugs.js +279 -0
- package/src/rules/bugs/index.js +73 -0
- package/src/rules/bugs/js-api.js +257 -0
- package/src/rules/bugs/js-array-object.js +210 -0
- package/src/rules/bugs/js-async-fixable.js +223 -0
- package/src/rules/bugs/js-async.js +211 -0
- package/src/rules/bugs/js-closure-scope.js +182 -0
- package/src/rules/bugs/js-database.js +203 -0
- package/src/rules/bugs/js-error-handling.js +148 -0
- package/src/rules/bugs/js-logic.js +261 -0
- package/src/rules/bugs/js-memory.js +214 -0
- package/src/rules/bugs/js-node.js +361 -0
- package/src/rules/bugs/js-react.js +373 -0
- package/src/rules/bugs/js-regex.js +200 -0
- package/src/rules/bugs/js-state.js +272 -0
- package/src/rules/bugs/js-type-coercion.js +318 -0
- package/src/rules/bugs/nextjs-bugs.js +242 -0
- package/src/rules/bugs/nextjs-fixable.js +120 -0
- package/src/rules/bugs/node-fixable.js +178 -0
- package/src/rules/bugs/python-advanced.js +245 -0
- package/src/rules/bugs/python-fixable.js +98 -0
- package/src/rules/bugs/python.js +284 -0
- package/src/rules/bugs/react-fixable.js +207 -0
- package/src/rules/bugs/ruby-bugs.js +182 -0
- package/src/rules/bugs/shell-bugs.js +181 -0
- package/src/rules/bugs/silent-failures.js +261 -0
- package/src/rules/bugs/ts-bugs.js +235 -0
- package/src/rules/bugs/unused-vars.js +65 -0
- package/src/rules/compliance/accessibility-ext.js +468 -0
- package/src/rules/compliance/education.js +322 -0
- package/src/rules/compliance/financial.js +421 -0
- package/src/rules/compliance/frameworks.js +507 -0
- package/src/rules/compliance/healthcare.js +520 -0
- package/src/rules/compliance/index.js +2714 -0
- package/src/rules/compliance/regional-eu.js +480 -0
- package/src/rules/compliance/regional-international.js +903 -0
- package/src/rules/cost/index.js +1993 -0
- package/src/rules/data/index.js +2503 -0
- package/src/rules/dependencies/index.js +1684 -0
- package/src/rules/deployment/index.js +2050 -0
- package/src/rules/index.js +71 -0
- package/src/rules/infrastructure/index.js +3048 -0
- package/src/rules/performance/index.js +3455 -0
- package/src/rules/quality/index.js +3175 -0
- package/src/rules/reliability/index.js +3040 -0
- package/src/rules/scope-rules.js +815 -0
- package/src/rules/security/ai-api.js +1177 -0
- package/src/rules/security/auth.js +1328 -0
- package/src/rules/security/cors.js +127 -0
- package/src/rules/security/crypto.js +527 -0
- package/src/rules/security/csharp.js +862 -0
- package/src/rules/security/csrf.js +193 -0
- package/src/rules/security/dart.js +835 -0
- package/src/rules/security/deserialization.js +291 -0
- package/src/rules/security/file-upload.js +187 -0
- package/src/rules/security/go.js +850 -0
- package/src/rules/security/headers.js +235 -0
- package/src/rules/security/index.js +65 -0
- package/src/rules/security/injection.js +1639 -0
- package/src/rules/security/mcp-server.js +71 -0
- package/src/rules/security/misconfiguration.js +660 -0
- package/src/rules/security/oauth-jwt.js +329 -0
- package/src/rules/security/path-traversal.js +295 -0
- package/src/rules/security/php.js +1054 -0
- package/src/rules/security/prototype-pollution.js +283 -0
- package/src/rules/security/rate-limiting.js +208 -0
- package/src/rules/security/ruby.js +1061 -0
- package/src/rules/security/rust.js +693 -0
- package/src/rules/security/secrets.js +747 -0
- package/src/rules/security/shell.js +647 -0
- package/src/rules/security/ssrf.js +298 -0
- package/src/rules/security/supply-chain-advanced.js +393 -0
- package/src/rules/security/supply-chain.js +734 -0
- package/src/rules/security/swift.js +835 -0
- package/src/rules/security/taint.js +27 -0
- package/src/rules/security/xss.js +520 -0
- package/src/scan-cache.js +71 -0
- package/src/scanner.js +710 -0
- package/src/scope-analyzer.js +685 -0
- package/src/share.js +88 -0
- package/src/taint.js +300 -0
- package/src/telemetry.js +183 -0
- package/src/tracer.js +190 -0
- package/src/upload.js +35 -0
- 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;
|