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.
- package/.claude/settings.local.json +42 -0
- package/.github/actions/ios-review/action.yml +106 -0
- package/.github/workflows/ci.yml +103 -0
- package/.github/workflows/publish.yml +57 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +175 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/bitrise/step.sh +128 -0
- package/bitrise/step.yml +101 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzers/asc-iap.d.ts.map +1 -0
- package/dist/analyzers/asc-metadata.d.ts.map +1 -0
- package/dist/analyzers/asc-screenshots.d.ts.map +1 -0
- package/dist/analyzers/asc-version.d.ts.map +1 -0
- package/dist/analyzers/code-scanner.d.ts.map +1 -0
- package/dist/analyzers/deprecated-api.d.ts.map +1 -0
- package/dist/analyzers/entitlements.d.ts.map +1 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/info-plist.d.ts.map +1 -0
- package/dist/analyzers/privacy.d.ts.map +1 -0
- package/dist/analyzers/private-api.d.ts.map +1 -0
- package/dist/analyzers/security.d.ts.map +1 -0
- package/dist/analyzers/ui-ux.d.ts.map +1 -0
- package/dist/asc/auth.d.ts.map +1 -0
- package/dist/asc/client.d.ts.map +1 -0
- package/dist/asc/endpoints/apps.d.ts.map +1 -0
- package/dist/asc/endpoints/iap.d.ts.map +1 -0
- package/dist/asc/endpoints/screenshots.d.ts.map +1 -0
- package/dist/asc/endpoints/versions.d.ts.map +1 -0
- package/dist/asc/errors.d.ts.map +1 -0
- package/dist/asc/index.d.ts.map +1 -0
- package/dist/asc/types.d.ts.map +1 -0
- package/dist/badge/generator.d.ts.map +1 -0
- package/dist/badge/index.d.ts.map +1 -0
- package/dist/badge/types.d.ts.map +1 -0
- package/dist/cache/file-cache.d.ts.map +1 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/version.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/git/diff.d.ts.map +1 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/types.d.ts.map +1 -0
- package/dist/guidelines/database.d.ts.map +1 -0
- package/dist/guidelines/index.d.ts.map +1 -0
- package/dist/guidelines/matcher.d.ts.map +1 -0
- package/dist/guidelines/types.d.ts.map +1 -0
- package/dist/history/comparator.d.ts.map +1 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +994 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/plist.d.ts.map +1 -0
- package/dist/parsers/xcodeproj.d.ts.map +1 -0
- package/dist/progress/index.d.ts.map +1 -0
- package/dist/progress/reporter.d.ts.map +1 -0
- package/dist/progress/types.d.ts.map +1 -0
- package/dist/reports/html.d.ts.map +1 -0
- package/dist/reports/index.d.ts.map +1 -0
- package/dist/reports/json.d.ts.map +1 -0
- package/dist/reports/markdown.d.ts.map +1 -0
- package/dist/reports/types.d.ts.map +1 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/loader.d.ts.map +1 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/docs/ANALYZERS.md +237 -0
- package/docs/API.md +308 -0
- package/docs/BADGES.md +130 -0
- package/docs/CI_CD.md +283 -0
- package/docs/CLI.md +140 -0
- package/docs/REPORTS.md +212 -0
- package/docs/ROADMAP.md +267 -0
- package/docs/RULES.md +182 -0
- package/docs/SECURITY.md +89 -0
- package/docs/TROUBLESHOOTING.md +227 -0
- package/docs/tutorials/ASC_SETUP.md +188 -0
- package/docs/tutorials/CI_INTEGRATION.md +292 -0
- package/docs/tutorials/CUSTOM_RULES.md +291 -0
- package/docs/tutorials/GETTING_STARTED.md +226 -0
- package/docs/video-scripts/01-introduction.md +106 -0
- package/docs/video-scripts/02-cli-usage.md +120 -0
- package/docs/video-scripts/03-ci-integration.md +198 -0
- package/eslint.config.js +33 -0
- package/examples/.ios-review-rules.json +82 -0
- package/examples/bitrise-workflow.yml +129 -0
- package/examples/fastlane-lane.rb +71 -0
- package/examples/github-action.yml +147 -0
- package/fastlane/Fastfile.example +114 -0
- package/fastlane/README.md +99 -0
- package/jest.config.js +36 -0
- package/package.json +65 -0
- package/scripts/benchmark.ts +112 -0
- package/scripts/debug-parser.ts +37 -0
- package/scripts/debug-pbxproj.ts +36 -0
- package/scripts/debug-specific.ts +47 -0
- package/scripts/test-analyze.ts +67 -0
- package/scripts/xcode-cloud-review.sh +167 -0
- package/src/analyzer.ts +227 -0
- package/src/analyzers/asc-iap.ts +300 -0
- package/src/analyzers/asc-metadata.ts +326 -0
- package/src/analyzers/asc-screenshots.ts +310 -0
- package/src/analyzers/asc-version.ts +368 -0
- package/src/analyzers/code-scanner.ts +408 -0
- package/src/analyzers/deprecated-api.ts +390 -0
- package/src/analyzers/entitlements.ts +345 -0
- package/src/analyzers/index.ts +12 -0
- package/src/analyzers/info-plist.ts +409 -0
- package/src/analyzers/privacy.ts +376 -0
- package/src/analyzers/private-api.ts +377 -0
- package/src/analyzers/security.ts +327 -0
- package/src/analyzers/ui-ux.ts +509 -0
- package/src/asc/auth.ts +204 -0
- package/src/asc/client.ts +258 -0
- package/src/asc/endpoints/apps.ts +115 -0
- package/src/asc/endpoints/iap.ts +171 -0
- package/src/asc/endpoints/screenshots.ts +164 -0
- package/src/asc/endpoints/versions.ts +174 -0
- package/src/asc/errors.ts +109 -0
- package/src/asc/index.ts +108 -0
- package/src/asc/types.ts +369 -0
- package/src/badge/generator.ts +48 -0
- package/src/badge/index.ts +2 -0
- package/src/badge/types.ts +5 -0
- package/src/cache/file-cache.ts +75 -0
- package/src/cache/index.ts +2 -0
- package/src/cache/types.ts +10 -0
- package/src/cli/commands/help.ts +41 -0
- package/src/cli/commands/scan.ts +44 -0
- package/src/cli/commands/version.ts +12 -0
- package/src/cli/index.ts +92 -0
- package/src/cli/types.ts +17 -0
- package/src/git/diff.ts +21 -0
- package/src/git/index.ts +2 -0
- package/src/git/types.ts +5 -0
- package/src/guidelines/database.ts +344 -0
- package/src/guidelines/index.ts +4 -0
- package/src/guidelines/matcher.ts +84 -0
- package/src/guidelines/types.ts +28 -0
- package/src/history/comparator.ts +114 -0
- package/src/history/index.ts +3 -0
- package/src/history/store.ts +135 -0
- package/src/history/types.ts +40 -0
- package/src/index.ts +1113 -0
- package/src/parsers/index.ts +3 -0
- package/src/parsers/plist.ts +253 -0
- package/src/parsers/xcodeproj.ts +265 -0
- package/src/progress/index.ts +2 -0
- package/src/progress/reporter.ts +65 -0
- package/src/progress/types.ts +9 -0
- package/src/reports/html.ts +322 -0
- package/src/reports/index.ts +20 -0
- package/src/reports/json.ts +92 -0
- package/src/reports/markdown.ts +187 -0
- package/src/reports/types.ts +26 -0
- package/src/rules/engine.ts +121 -0
- package/src/rules/index.ts +3 -0
- package/src/rules/loader.ts +83 -0
- package/src/rules/types.ts +25 -0
- package/src/types/index.ts +247 -0
- package/tests/analyzer.test.ts +142 -0
- package/tests/analyzers/asc-iap.test.ts +228 -0
- package/tests/analyzers/asc-metadata.test.ts +210 -0
- package/tests/analyzers/asc-screenshots.test.ts +135 -0
- package/tests/analyzers/asc-version.test.ts +259 -0
- package/tests/analyzers/code-scanner.test.ts +745 -0
- package/tests/analyzers/deprecated-api.test.ts +286 -0
- package/tests/analyzers/entitlements.test.ts +411 -0
- package/tests/analyzers/info-plist.test.ts +148 -0
- package/tests/analyzers/privacy.test.ts +623 -0
- package/tests/analyzers/private-api.test.ts +255 -0
- package/tests/analyzers/security.test.ts +300 -0
- package/tests/analyzers/ui-ux.test.ts +357 -0
- package/tests/asc/auth.test.ts +189 -0
- package/tests/asc/client.test.ts +207 -0
- package/tests/asc/endpoints.test.ts +1359 -0
- package/tests/badge/generator.test.ts +73 -0
- package/tests/cache/file-cache.test.ts +124 -0
- package/tests/cli/cli-index.test.ts +510 -0
- package/tests/cli/commands.test.ts +67 -0
- package/tests/cli/scan.test.ts +152 -0
- package/tests/git/diff.test.ts +69 -0
- package/tests/guidelines/matcher.test.ts +209 -0
- package/tests/history/comparator.test.ts +272 -0
- package/tests/history/store.test.ts +200 -0
- package/tests/integration/cli.test.ts +95 -0
- package/tests/integration/e2e.test.ts +130 -0
- package/tests/parsers/plist.test.ts +240 -0
- package/tests/parsers/xcodeproj.test.ts +289 -0
- package/tests/progress/reporter.test.ts +117 -0
- package/tests/reports/html.test.ts +176 -0
- package/tests/reports/json.test.ts +235 -0
- package/tests/reports/markdown.test.ts +196 -0
- package/tests/rules/engine.test.ts +229 -0
- package/tests/rules/loader.test.ts +187 -0
- package/tests/setup.ts +15 -0
- package/tsconfig.json +27 -0
- 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
|
+
}
|