ios-app-review-plugin 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 (205) hide show
  1. package/.claude/settings.local.json +42 -0
  2. package/.github/actions/ios-review/action.yml +106 -0
  3. package/.github/workflows/ci.yml +103 -0
  4. package/.github/workflows/publish.yml +57 -0
  5. package/CHANGELOG.md +66 -0
  6. package/CONTRIBUTING.md +175 -0
  7. package/LICENSE +21 -0
  8. package/README.md +205 -0
  9. package/bitrise/step.sh +128 -0
  10. package/bitrise/step.yml +101 -0
  11. package/dist/analyzer.d.ts.map +1 -0
  12. package/dist/analyzers/asc-iap.d.ts.map +1 -0
  13. package/dist/analyzers/asc-metadata.d.ts.map +1 -0
  14. package/dist/analyzers/asc-screenshots.d.ts.map +1 -0
  15. package/dist/analyzers/asc-version.d.ts.map +1 -0
  16. package/dist/analyzers/code-scanner.d.ts.map +1 -0
  17. package/dist/analyzers/deprecated-api.d.ts.map +1 -0
  18. package/dist/analyzers/entitlements.d.ts.map +1 -0
  19. package/dist/analyzers/index.d.ts.map +1 -0
  20. package/dist/analyzers/info-plist.d.ts.map +1 -0
  21. package/dist/analyzers/privacy.d.ts.map +1 -0
  22. package/dist/analyzers/private-api.d.ts.map +1 -0
  23. package/dist/analyzers/security.d.ts.map +1 -0
  24. package/dist/analyzers/ui-ux.d.ts.map +1 -0
  25. package/dist/asc/auth.d.ts.map +1 -0
  26. package/dist/asc/client.d.ts.map +1 -0
  27. package/dist/asc/endpoints/apps.d.ts.map +1 -0
  28. package/dist/asc/endpoints/iap.d.ts.map +1 -0
  29. package/dist/asc/endpoints/screenshots.d.ts.map +1 -0
  30. package/dist/asc/endpoints/versions.d.ts.map +1 -0
  31. package/dist/asc/errors.d.ts.map +1 -0
  32. package/dist/asc/index.d.ts.map +1 -0
  33. package/dist/asc/types.d.ts.map +1 -0
  34. package/dist/badge/generator.d.ts.map +1 -0
  35. package/dist/badge/index.d.ts.map +1 -0
  36. package/dist/badge/types.d.ts.map +1 -0
  37. package/dist/cache/file-cache.d.ts.map +1 -0
  38. package/dist/cache/index.d.ts.map +1 -0
  39. package/dist/cache/types.d.ts.map +1 -0
  40. package/dist/cli/commands/help.d.ts.map +1 -0
  41. package/dist/cli/commands/scan.d.ts.map +1 -0
  42. package/dist/cli/commands/version.d.ts.map +1 -0
  43. package/dist/cli/index.d.ts.map +1 -0
  44. package/dist/cli/types.d.ts.map +1 -0
  45. package/dist/git/diff.d.ts.map +1 -0
  46. package/dist/git/index.d.ts.map +1 -0
  47. package/dist/git/types.d.ts.map +1 -0
  48. package/dist/guidelines/database.d.ts.map +1 -0
  49. package/dist/guidelines/index.d.ts.map +1 -0
  50. package/dist/guidelines/matcher.d.ts.map +1 -0
  51. package/dist/guidelines/types.d.ts.map +1 -0
  52. package/dist/history/comparator.d.ts.map +1 -0
  53. package/dist/history/index.d.ts.map +1 -0
  54. package/dist/history/store.d.ts.map +1 -0
  55. package/dist/history/types.d.ts.map +1 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +994 -0
  58. package/dist/parsers/index.d.ts.map +1 -0
  59. package/dist/parsers/plist.d.ts.map +1 -0
  60. package/dist/parsers/xcodeproj.d.ts.map +1 -0
  61. package/dist/progress/index.d.ts.map +1 -0
  62. package/dist/progress/reporter.d.ts.map +1 -0
  63. package/dist/progress/types.d.ts.map +1 -0
  64. package/dist/reports/html.d.ts.map +1 -0
  65. package/dist/reports/index.d.ts.map +1 -0
  66. package/dist/reports/json.d.ts.map +1 -0
  67. package/dist/reports/markdown.d.ts.map +1 -0
  68. package/dist/reports/types.d.ts.map +1 -0
  69. package/dist/rules/engine.d.ts.map +1 -0
  70. package/dist/rules/index.d.ts.map +1 -0
  71. package/dist/rules/loader.d.ts.map +1 -0
  72. package/dist/rules/types.d.ts.map +1 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/docs/ANALYZERS.md +237 -0
  75. package/docs/API.md +308 -0
  76. package/docs/BADGES.md +130 -0
  77. package/docs/CI_CD.md +283 -0
  78. package/docs/CLI.md +140 -0
  79. package/docs/REPORTS.md +212 -0
  80. package/docs/ROADMAP.md +267 -0
  81. package/docs/RULES.md +182 -0
  82. package/docs/SECURITY.md +89 -0
  83. package/docs/TROUBLESHOOTING.md +227 -0
  84. package/docs/tutorials/ASC_SETUP.md +188 -0
  85. package/docs/tutorials/CI_INTEGRATION.md +292 -0
  86. package/docs/tutorials/CUSTOM_RULES.md +291 -0
  87. package/docs/tutorials/GETTING_STARTED.md +226 -0
  88. package/docs/video-scripts/01-introduction.md +106 -0
  89. package/docs/video-scripts/02-cli-usage.md +120 -0
  90. package/docs/video-scripts/03-ci-integration.md +198 -0
  91. package/eslint.config.js +33 -0
  92. package/examples/.ios-review-rules.json +82 -0
  93. package/examples/bitrise-workflow.yml +129 -0
  94. package/examples/fastlane-lane.rb +71 -0
  95. package/examples/github-action.yml +147 -0
  96. package/fastlane/Fastfile.example +114 -0
  97. package/fastlane/README.md +99 -0
  98. package/jest.config.js +36 -0
  99. package/package.json +65 -0
  100. package/scripts/benchmark.ts +112 -0
  101. package/scripts/debug-parser.ts +37 -0
  102. package/scripts/debug-pbxproj.ts +36 -0
  103. package/scripts/debug-specific.ts +47 -0
  104. package/scripts/test-analyze.ts +67 -0
  105. package/scripts/xcode-cloud-review.sh +167 -0
  106. package/src/analyzer.ts +227 -0
  107. package/src/analyzers/asc-iap.ts +300 -0
  108. package/src/analyzers/asc-metadata.ts +326 -0
  109. package/src/analyzers/asc-screenshots.ts +310 -0
  110. package/src/analyzers/asc-version.ts +368 -0
  111. package/src/analyzers/code-scanner.ts +408 -0
  112. package/src/analyzers/deprecated-api.ts +390 -0
  113. package/src/analyzers/entitlements.ts +345 -0
  114. package/src/analyzers/index.ts +12 -0
  115. package/src/analyzers/info-plist.ts +409 -0
  116. package/src/analyzers/privacy.ts +376 -0
  117. package/src/analyzers/private-api.ts +377 -0
  118. package/src/analyzers/security.ts +327 -0
  119. package/src/analyzers/ui-ux.ts +509 -0
  120. package/src/asc/auth.ts +204 -0
  121. package/src/asc/client.ts +258 -0
  122. package/src/asc/endpoints/apps.ts +115 -0
  123. package/src/asc/endpoints/iap.ts +171 -0
  124. package/src/asc/endpoints/screenshots.ts +164 -0
  125. package/src/asc/endpoints/versions.ts +174 -0
  126. package/src/asc/errors.ts +109 -0
  127. package/src/asc/index.ts +108 -0
  128. package/src/asc/types.ts +369 -0
  129. package/src/badge/generator.ts +48 -0
  130. package/src/badge/index.ts +2 -0
  131. package/src/badge/types.ts +5 -0
  132. package/src/cache/file-cache.ts +75 -0
  133. package/src/cache/index.ts +2 -0
  134. package/src/cache/types.ts +10 -0
  135. package/src/cli/commands/help.ts +41 -0
  136. package/src/cli/commands/scan.ts +44 -0
  137. package/src/cli/commands/version.ts +12 -0
  138. package/src/cli/index.ts +92 -0
  139. package/src/cli/types.ts +17 -0
  140. package/src/git/diff.ts +21 -0
  141. package/src/git/index.ts +2 -0
  142. package/src/git/types.ts +5 -0
  143. package/src/guidelines/database.ts +344 -0
  144. package/src/guidelines/index.ts +4 -0
  145. package/src/guidelines/matcher.ts +84 -0
  146. package/src/guidelines/types.ts +28 -0
  147. package/src/history/comparator.ts +114 -0
  148. package/src/history/index.ts +3 -0
  149. package/src/history/store.ts +135 -0
  150. package/src/history/types.ts +40 -0
  151. package/src/index.ts +1113 -0
  152. package/src/parsers/index.ts +3 -0
  153. package/src/parsers/plist.ts +253 -0
  154. package/src/parsers/xcodeproj.ts +265 -0
  155. package/src/progress/index.ts +2 -0
  156. package/src/progress/reporter.ts +65 -0
  157. package/src/progress/types.ts +9 -0
  158. package/src/reports/html.ts +322 -0
  159. package/src/reports/index.ts +20 -0
  160. package/src/reports/json.ts +92 -0
  161. package/src/reports/markdown.ts +187 -0
  162. package/src/reports/types.ts +26 -0
  163. package/src/rules/engine.ts +121 -0
  164. package/src/rules/index.ts +3 -0
  165. package/src/rules/loader.ts +83 -0
  166. package/src/rules/types.ts +25 -0
  167. package/src/types/index.ts +247 -0
  168. package/tests/analyzer.test.ts +142 -0
  169. package/tests/analyzers/asc-iap.test.ts +228 -0
  170. package/tests/analyzers/asc-metadata.test.ts +210 -0
  171. package/tests/analyzers/asc-screenshots.test.ts +135 -0
  172. package/tests/analyzers/asc-version.test.ts +259 -0
  173. package/tests/analyzers/code-scanner.test.ts +745 -0
  174. package/tests/analyzers/deprecated-api.test.ts +286 -0
  175. package/tests/analyzers/entitlements.test.ts +411 -0
  176. package/tests/analyzers/info-plist.test.ts +148 -0
  177. package/tests/analyzers/privacy.test.ts +623 -0
  178. package/tests/analyzers/private-api.test.ts +255 -0
  179. package/tests/analyzers/security.test.ts +300 -0
  180. package/tests/analyzers/ui-ux.test.ts +357 -0
  181. package/tests/asc/auth.test.ts +189 -0
  182. package/tests/asc/client.test.ts +207 -0
  183. package/tests/asc/endpoints.test.ts +1359 -0
  184. package/tests/badge/generator.test.ts +73 -0
  185. package/tests/cache/file-cache.test.ts +124 -0
  186. package/tests/cli/cli-index.test.ts +510 -0
  187. package/tests/cli/commands.test.ts +67 -0
  188. package/tests/cli/scan.test.ts +152 -0
  189. package/tests/git/diff.test.ts +69 -0
  190. package/tests/guidelines/matcher.test.ts +209 -0
  191. package/tests/history/comparator.test.ts +272 -0
  192. package/tests/history/store.test.ts +200 -0
  193. package/tests/integration/cli.test.ts +95 -0
  194. package/tests/integration/e2e.test.ts +130 -0
  195. package/tests/parsers/plist.test.ts +240 -0
  196. package/tests/parsers/xcodeproj.test.ts +289 -0
  197. package/tests/progress/reporter.test.ts +117 -0
  198. package/tests/reports/html.test.ts +176 -0
  199. package/tests/reports/json.test.ts +235 -0
  200. package/tests/reports/markdown.test.ts +196 -0
  201. package/tests/rules/engine.test.ts +229 -0
  202. package/tests/rules/loader.test.ts +187 -0
  203. package/tests/setup.ts +15 -0
  204. package/tsconfig.json +27 -0
  205. package/tsconfig.test.json +9 -0
@@ -0,0 +1,327 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import fg from 'fast-glob';
4
+ import type {
5
+ Analyzer,
6
+ AnalysisResult,
7
+ AnalyzerOptions,
8
+ Issue,
9
+ XcodeProject,
10
+ } from '../types/index.js';
11
+
12
+ /**
13
+ * A security pattern to detect
14
+ */
15
+ interface SecurityPattern {
16
+ id: string;
17
+ title: string;
18
+ description: string;
19
+ pattern: RegExp;
20
+ severity: 'error' | 'warning' | 'info';
21
+ guideline: string;
22
+ suggestion: string;
23
+ fileTypes: string[];
24
+ }
25
+
26
+ /**
27
+ * Security patterns to scan for in source code
28
+ */
29
+ const SECURITY_PATTERNS: SecurityPattern[] = [
30
+ // Weak cryptography
31
+ {
32
+ id: 'security-md5',
33
+ title: 'Weak hash function (MD5)',
34
+ description: 'MD5 is cryptographically broken. Do not use it for security-sensitive hashing.',
35
+ pattern: /\b(?:CC_MD5|MD5\s*\(|\.md5|kCCHmacAlgMD5)\b/g,
36
+ severity: 'warning',
37
+ guideline: 'Guideline 2.5.4 - Security',
38
+ suggestion: 'Use SHA-256 or stronger hash functions (CC_SHA256, CryptoKit SHA256).',
39
+ fileTypes: ['.swift', '.m', '.mm', '.c', '.cpp'],
40
+ },
41
+ {
42
+ id: 'security-sha1',
43
+ title: 'Weak hash function (SHA-1)',
44
+ description: 'SHA-1 is deprecated for security purposes. Use SHA-256 or stronger.',
45
+ pattern: /\b(?:CC_SHA1|\.sha1|kCCHmacAlgSHA1)\b/g,
46
+ severity: 'warning',
47
+ guideline: 'Guideline 2.5.4 - Security',
48
+ suggestion: 'Use SHA-256 or stronger (CC_SHA256, CryptoKit SHA256).',
49
+ fileTypes: ['.swift', '.m', '.mm', '.c', '.cpp'],
50
+ },
51
+ {
52
+ id: 'security-des',
53
+ title: 'Weak encryption algorithm (DES/3DES)',
54
+ description: 'DES and 3DES are considered weak encryption. Use AES instead.',
55
+ pattern: /\b(?:kCCAlgorithmDES|kCCAlgorithm3DES|CCAlgorithm\.des)\b/g,
56
+ severity: 'error',
57
+ guideline: 'Guideline 2.5.4 - Security',
58
+ suggestion: 'Use AES-256 encryption (kCCAlgorithmAES, CryptoKit AES.GCM).',
59
+ fileTypes: ['.swift', '.m', '.mm', '.c', '.cpp'],
60
+ },
61
+ {
62
+ id: 'security-ecb-mode',
63
+ title: 'Insecure ECB encryption mode',
64
+ description: 'ECB mode does not provide serious message confidentiality. Use CBC or GCM.',
65
+ pattern: /\b(?:kCCOptionECBMode|\.ecb)\b/g,
66
+ severity: 'error',
67
+ guideline: 'Guideline 2.5.4 - Security',
68
+ suggestion: 'Use CBC mode with random IV, or preferably GCM mode (CryptoKit AES.GCM).',
69
+ fileTypes: ['.swift', '.m', '.mm', '.c', '.cpp'],
70
+ },
71
+ // Insecure data storage
72
+ {
73
+ id: 'security-userdefaults-sensitive',
74
+ title: 'Sensitive data in UserDefaults',
75
+ description: 'UserDefaults is not encrypted. Do not store sensitive data like passwords or tokens.',
76
+ pattern: /UserDefaults\b[^}]*\b(?:password|token|secret|apiKey|api_key|credential|auth)\b/gi,
77
+ severity: 'error',
78
+ guideline: 'Guideline 2.5.4 - Security',
79
+ suggestion: 'Store sensitive data in the Keychain using Security framework.',
80
+ fileTypes: ['.swift'],
81
+ },
82
+ {
83
+ id: 'security-userdefaults-sensitive-set',
84
+ title: 'Storing sensitive value in UserDefaults',
85
+ description: 'UserDefaults is not secure storage. Sensitive data should use Keychain.',
86
+ pattern: /\.set\([^)]+,\s*forKey\s*:\s*["'`](?:password|token|secret|apiKey|api_key|credential|auth\w*|session\w*|private\w*Key)["'`]\)/gi,
87
+ severity: 'error',
88
+ guideline: 'Guideline 2.5.4 - Security',
89
+ suggestion: 'Use Keychain Services (SecItemAdd) to store sensitive data securely.',
90
+ fileTypes: ['.swift'],
91
+ },
92
+ // Insecure random number generation
93
+ {
94
+ id: 'security-insecure-random',
95
+ title: 'Insecure random number generation',
96
+ description: 'rand()/srand()/random() are not cryptographically secure.',
97
+ pattern: /\b(?:srand|(?<!arc4)rand|srandom)\s*\(/g,
98
+ severity: 'warning',
99
+ guideline: 'Guideline 2.5.4 - Security',
100
+ suggestion: 'Use SecRandomCopyBytes or SystemRandomNumberGenerator for security-sensitive randomness.',
101
+ fileTypes: ['.swift', '.m', '.mm', '.c', '.cpp'],
102
+ },
103
+ // Insecure keychain configuration
104
+ {
105
+ id: 'security-keychain-accessible-always',
106
+ title: 'Insecure Keychain accessibility',
107
+ description: 'kSecAttrAccessibleAlways is deprecated and insecure. Data is accessible even when device is locked.',
108
+ pattern: /\bkSecAttrAccessibleAlways\b/g,
109
+ severity: 'error',
110
+ guideline: 'Guideline 2.5.4 - Security',
111
+ suggestion: 'Use kSecAttrAccessibleWhenUnlocked or kSecAttrAccessibleAfterFirstUnlock.',
112
+ fileTypes: ['.swift', '.m', '.mm'],
113
+ },
114
+ {
115
+ id: 'security-keychain-accessible-always-this-device',
116
+ title: 'Insecure Keychain accessibility (ThisDeviceOnly)',
117
+ description: 'kSecAttrAccessibleAlwaysThisDeviceOnly is deprecated and insecure.',
118
+ pattern: /\bkSecAttrAccessibleAlwaysThisDeviceOnly\b/g,
119
+ severity: 'error',
120
+ guideline: 'Guideline 2.5.4 - Security',
121
+ suggestion: 'Use kSecAttrAccessibleWhenUnlockedThisDeviceOnly or kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.',
122
+ fileTypes: ['.swift', '.m', '.mm'],
123
+ },
124
+ // Clipboard sensitive data
125
+ {
126
+ id: 'security-clipboard-sensitive',
127
+ title: 'Sensitive data on clipboard',
128
+ description: 'Copying sensitive data to the clipboard exposes it to other apps.',
129
+ pattern: /UIPasteboard\b[^}]*\b(?:password|token|secret|credential)\b/gi,
130
+ severity: 'warning',
131
+ guideline: 'Guideline 2.5.4 - Security',
132
+ suggestion: 'Avoid putting sensitive data on the clipboard. If needed, set expiration with localOnly option.',
133
+ fileTypes: ['.swift', '.m', '.mm'],
134
+ },
135
+ // SQL injection
136
+ {
137
+ id: 'security-sql-injection',
138
+ title: 'Potential SQL injection',
139
+ description: 'String interpolation in SQL queries can lead to SQL injection attacks.',
140
+ pattern: /["'`](?:SELECT|INSERT|UPDATE|DELETE|DROP)\s[^"'`]*\\?\([^"'`]*\)/gi,
141
+ severity: 'error',
142
+ guideline: 'Guideline 2.5.4 - Security',
143
+ suggestion: 'Use parameterized queries or prepared statements instead of string interpolation.',
144
+ fileTypes: ['.swift', '.m', '.mm'],
145
+ },
146
+ // Logging sensitive data
147
+ {
148
+ id: 'security-logging-sensitive',
149
+ title: 'Logging potentially sensitive data',
150
+ description: 'Logging sensitive information like passwords or tokens can expose them.',
151
+ pattern: /(?:print|NSLog|os_log|Logger\.\w+)\s*\([^)]*\b(?:password|token|secret|apiKey|credential|ssn|creditCard)\b/gi,
152
+ severity: 'warning',
153
+ guideline: 'Guideline 2.5.4 - Security',
154
+ suggestion: 'Never log sensitive data. Redact or mask sensitive values in log output.',
155
+ fileTypes: ['.swift', '.m', '.mm'],
156
+ },
157
+ // Hardcoded encryption keys
158
+ {
159
+ id: 'security-hardcoded-encryption-key',
160
+ title: 'Hardcoded encryption key',
161
+ description: 'Encryption keys should not be hardcoded in source code.',
162
+ pattern: /(?:encryptionKey|aesKey|cryptKey|cipherKey|symmetricKey)\s*[:=]\s*["'`][A-Za-z0-9+/=]{16,}["'`]/gi,
163
+ severity: 'error',
164
+ guideline: 'Guideline 2.5.4 - Security',
165
+ suggestion: 'Derive encryption keys dynamically or store them securely in the Keychain.',
166
+ fileTypes: ['.swift', '.m', '.mm'],
167
+ },
168
+ // WebView JavaScript injection
169
+ {
170
+ id: 'security-webview-js-injection',
171
+ title: 'WebView JavaScript evaluation',
172
+ description: 'Evaluating JavaScript in WebViews with user-supplied data can lead to XSS attacks.',
173
+ pattern: /\.evaluateJavaScript\s*\(\s*["'`].*\\?\(/g,
174
+ severity: 'warning',
175
+ guideline: 'Guideline 2.5.4 - Security',
176
+ suggestion: 'Sanitize any user input before evaluating JavaScript. Consider using WKScriptMessageHandler instead.',
177
+ fileTypes: ['.swift'],
178
+ },
179
+ // Disabled certificate validation
180
+ {
181
+ id: 'security-disabled-ssl',
182
+ title: 'Disabled SSL/TLS certificate validation',
183
+ description: 'Disabling certificate validation makes connections vulnerable to MITM attacks.',
184
+ pattern: /(?:\.serverTrust|\.performDefaultHandling|allowsExpiredCertificates|allowsExpiredRoots|validatesDomainName\s*=\s*false|NSExceptionAllowsInsecureHTTPLoads)/g,
185
+ severity: 'warning',
186
+ guideline: 'Guideline 2.5.4 - Security',
187
+ suggestion: 'Always validate SSL certificates in production. Implement proper certificate pinning.',
188
+ fileTypes: ['.swift', '.m', '.mm'],
189
+ },
190
+ ];
191
+
192
+ /**
193
+ * Security analyzer for detecting common security vulnerabilities
194
+ */
195
+ export class SecurityAnalyzer implements Analyzer {
196
+ name = 'Security Analyzer';
197
+ description = 'Detects security vulnerabilities and insecure patterns';
198
+
199
+ async analyze(project: XcodeProject, options: AnalyzerOptions): Promise<AnalysisResult> {
200
+ const startTime = Date.now();
201
+
202
+ const targets = options.targetName
203
+ ? project.targets.filter((t) => t.name === options.targetName)
204
+ : project.targets.filter((t) => t.type === 'application');
205
+
206
+ let sourceFiles: string[] = [];
207
+ for (const target of targets) {
208
+ sourceFiles.push(...target.sourceFiles);
209
+ }
210
+
211
+ if (sourceFiles.length === 0) {
212
+ sourceFiles = await this.findSourceFiles(options.basePath);
213
+ }
214
+
215
+ // Filter to changed files for incremental scanning
216
+ if (options.changedFiles) {
217
+ const changedSet = new Set(options.changedFiles);
218
+ sourceFiles = sourceFiles.filter((f) => changedSet.has(f));
219
+ }
220
+
221
+ const issues = await this.scanFiles(sourceFiles);
222
+
223
+ return {
224
+ analyzer: this.name,
225
+ passed: issues.filter((i) => i.severity === 'error').length === 0,
226
+ issues,
227
+ duration: Date.now() - startTime,
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Scan a specific path for security issues
233
+ */
234
+ async scanPath(scanPath: string): Promise<AnalysisResult> {
235
+ const startTime = Date.now();
236
+
237
+ const stats = await fs.stat(scanPath);
238
+ const files = stats.isDirectory()
239
+ ? await this.findSourceFiles(scanPath)
240
+ : [scanPath];
241
+
242
+ const issues = await this.scanFiles(files);
243
+
244
+ return {
245
+ analyzer: this.name,
246
+ passed: issues.filter((i) => i.severity === 'error').length === 0,
247
+ issues,
248
+ duration: Date.now() - startTime,
249
+ };
250
+ }
251
+
252
+ private async findSourceFiles(basePath: string): Promise<string[]> {
253
+ return fg(['**/*.swift', '**/*.m', '**/*.mm', '**/*.h', '**/*.c', '**/*.cpp'], {
254
+ cwd: basePath,
255
+ absolute: true,
256
+ ignore: [
257
+ '**/Pods/**',
258
+ '**/Carthage/**',
259
+ '**/build/**',
260
+ '**/DerivedData/**',
261
+ '**/*.generated.swift',
262
+ '**/Tests/**',
263
+ '**/UITests/**',
264
+ ],
265
+ });
266
+ }
267
+
268
+ private async scanFiles(files: string[]): Promise<Issue[]> {
269
+ const issues: Issue[] = [];
270
+ const seenIssues = new Set<string>();
271
+
272
+ for (const file of files) {
273
+ const ext = path.extname(file);
274
+
275
+ try {
276
+ const content = await fs.readFile(file, 'utf-8');
277
+ const lines = content.split('\n');
278
+
279
+ for (const pattern of SECURITY_PATTERNS) {
280
+ if (!pattern.fileTypes.includes(ext)) continue;
281
+
282
+ pattern.pattern.lastIndex = 0;
283
+
284
+ let match: RegExpExecArray | null;
285
+ while ((match = pattern.pattern.exec(content)) !== null) {
286
+ const lineNumber = content.substring(0, match.index).split('\n').length;
287
+ const issueKey = `${pattern.id}:${file}:${lineNumber}`;
288
+
289
+ if (seenIssues.has(issueKey)) continue;
290
+ seenIssues.add(issueKey);
291
+
292
+ // Skip commented lines
293
+ const line = lines[lineNumber - 1] ?? '';
294
+ const trimmed = line.trim();
295
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
296
+ continue;
297
+ }
298
+
299
+ issues.push({
300
+ id: pattern.id,
301
+ title: pattern.title,
302
+ description: `${pattern.description}\n\nFound: \`${this.truncate(match[0], 60)}\``,
303
+ severity: pattern.severity,
304
+ filePath: file,
305
+ lineNumber,
306
+ category: 'security',
307
+ guideline: pattern.guideline,
308
+ suggestion: pattern.suggestion,
309
+ });
310
+
311
+ const count = issues.filter((i) => i.id === pattern.id && i.filePath === file).length;
312
+ if (count >= 5) break;
313
+ }
314
+ }
315
+ } catch {
316
+ // Skip files that can't be read
317
+ }
318
+ }
319
+
320
+ return issues;
321
+ }
322
+
323
+ private truncate(str: string, maxLength: number): string {
324
+ if (str.length <= maxLength) return str;
325
+ return str.substring(0, maxLength - 3) + '...';
326
+ }
327
+ }