pumuki 6.3.362 → 6.3.363
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/core/facts/detectors/text/ios.test.ts +37 -0
- package/core/facts/detectors/text/ios.ts +21 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.test.ts +2 -1
- package/core/rules/presets/heuristics/ios.ts +18 -0
- package/integrations/config/skillsDetectorRegistry.ts +8 -0
- package/package.json +1 -1
- package/skills.lock.json +1 -1
|
@@ -180,9 +180,11 @@ import {
|
|
|
180
180
|
hasSwiftSensitiveLoggingUsage,
|
|
181
181
|
hasSwiftSelfPrintChangesUsage,
|
|
182
182
|
collectSwiftInsecureTransportLines,
|
|
183
|
+
collectSwiftUrlSessionTrustBypassLines,
|
|
183
184
|
collectSwiftSensitiveUserDefaultsStorageLines,
|
|
184
185
|
hasSwiftSensitiveUserDefaultsStorageUsage,
|
|
185
186
|
hasSwiftInsecureTransportUsage,
|
|
187
|
+
hasSwiftUrlSessionTrustBypassUsage,
|
|
186
188
|
hasSwiftJSONSerializationUsage,
|
|
187
189
|
collectSwiftJSONSerializationLines,
|
|
188
190
|
hasSwiftExplicitColorStaticMemberUsage,
|
|
@@ -229,6 +231,41 @@ let value = loadUser()!
|
|
|
229
231
|
assert.deepEqual(collectSwiftForceUnwrapLines(source), [2, 3]);
|
|
230
232
|
});
|
|
231
233
|
|
|
234
|
+
test('hasSwiftUrlSessionTrustBypassUsage detecta delegate que acepta cualquier certificado', () => {
|
|
235
|
+
const source = `
|
|
236
|
+
final class InsecureDelegate: NSObject, URLSessionDelegate {
|
|
237
|
+
func urlSession(
|
|
238
|
+
_ session: URLSession,
|
|
239
|
+
didReceive challenge: URLAuthenticationChallenge,
|
|
240
|
+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
|
241
|
+
) {
|
|
242
|
+
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
assert.equal(hasSwiftUrlSessionTrustBypassUsage(source), true);
|
|
248
|
+
assert.deepEqual(collectSwiftUrlSessionTrustBypassLines(source), [8]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('hasSwiftUrlSessionTrustBypassUsage preserva cancelacion o manejo por defecto del challenge TLS', () => {
|
|
252
|
+
const source = `
|
|
253
|
+
final class SecureDelegate: NSObject, URLSessionDelegate {
|
|
254
|
+
func urlSession(
|
|
255
|
+
_ session: URLSession,
|
|
256
|
+
didReceive challenge: URLAuthenticationChallenge,
|
|
257
|
+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
|
258
|
+
) {
|
|
259
|
+
completionHandler(.performDefaultHandling, nil)
|
|
260
|
+
completionHandler(.cancelAuthenticationChallenge, nil)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
`;
|
|
264
|
+
|
|
265
|
+
assert.equal(hasSwiftUrlSessionTrustBypassUsage(source), false);
|
|
266
|
+
assert.deepEqual(collectSwiftUrlSessionTrustBypassLines(source), []);
|
|
267
|
+
});
|
|
268
|
+
|
|
232
269
|
test('hasSwiftForceUnwrap excluye type annotations, force cast y operadores', () => {
|
|
233
270
|
const source = `
|
|
234
271
|
let name: String!
|
|
@@ -1940,6 +1940,27 @@ export const collectSwiftInsecureTransportLines = (source: string): readonly num
|
|
|
1940
1940
|
return sortedUniqueLines(result);
|
|
1941
1941
|
};
|
|
1942
1942
|
|
|
1943
|
+
export const collectSwiftUrlSessionTrustBypassLines = (source: string): readonly number[] => {
|
|
1944
|
+
const result: number[] = [];
|
|
1945
|
+
|
|
1946
|
+
source.split(/\r?\n/).forEach((line, index) => {
|
|
1947
|
+
const sanitized = stripSwiftLineForSemanticScan(line);
|
|
1948
|
+
if (
|
|
1949
|
+
/completionHandler\s*\(\s*\.useCredential\s*,\s*URLCredential\s*\(\s*trust\s*:/.test(
|
|
1950
|
+
sanitized
|
|
1951
|
+
)
|
|
1952
|
+
) {
|
|
1953
|
+
result.push(index + 1);
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
return sortedUniqueLines(result);
|
|
1958
|
+
};
|
|
1959
|
+
|
|
1960
|
+
export const hasSwiftUrlSessionTrustBypassUsage = (source: string): boolean => {
|
|
1961
|
+
return collectSwiftUrlSessionTrustBypassLines(source).length > 0;
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1943
1964
|
const swiftUiLiteralTextPatterns = [
|
|
1944
1965
|
/\b(?:Text|Button|Label|TextField|SecureField)\s*\(\s*"((?:\\.|[^"\\])*)"/,
|
|
1945
1966
|
/\.navigationTitle\s*\(\s*"((?:\\.|[^"\\])*)"/,
|
|
@@ -812,6 +812,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
812
812
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, locateLines: TextIOS.collectSwiftJSONSerializationLines, primaryNode: (lines) => ({ kind: 'call', name: 'JSONSerialization call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: Codable decoder or encoder', lines }], why: 'JSONSerialization bypasses typed decoding and makes payload contracts harder to validate than Codable models.', impact: 'Runtime casts and dictionary traversal can hide API drift until production and make the gate report vague file-level serialization debt.', expected_fix: 'Replace JSONSerialization calls with Codable DTOs using JSONDecoder or JSONEncoder at the repository networking boundary.', ruleId: 'heuristics.ios.json.jsonserialization.ast', code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST', message: 'AST heuristic detected JSONSerialization usage in iOS production code; Codable remains the preferred baseline for new code.' },
|
|
813
813
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveUserDefaultsStorageUsage, locateLines: TextIOS.collectSwiftSensitiveUserDefaultsStorageLines, primaryNode: (lines) => ({ kind: 'call', name: 'sensitive UserDefaults/AppStorage storage', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: native Keychain or secure secret store', lines }], why: 'Tokens, passwords, credentials and session identifiers must not be stored in UserDefaults or AppStorage because those stores are not the approved secret boundary.', impact: 'Secrets persisted in UserDefaults/AppStorage can leak through backups, diagnostics or simple local inspection, and the gate needs the exact storage node to avoid whole-file remediation.', expected_fix: 'Move tokens, passwords, credentials and session identifiers to native Keychain or the repository-approved secure storage adapter. Keep UserDefaults/AppStorage only for non-sensitive preferences.', ruleId: 'heuristics.ios.security.userdefaults-sensitive-data.ast', code: 'HEURISTICS_IOS_SECURITY_USERDEFAULTS_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data stored in UserDefaults/AppStorage; native Keychain remains the preferred baseline for secrets.' },
|
|
814
814
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInsecureTransportUsage, locateLines: TextIOS.collectSwiftInsecureTransportLines, primaryNode: (lines) => ({ kind: 'call', name: 'insecure HTTP URL literal', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: HTTPS URLSession endpoint with ATS enabled', lines }], why: 'Production iOS networking must use HTTPS and keep App Transport Security enabled by default.', impact: 'HTTP endpoints can expose credentials, session data or payloads in transit and create release-blocking transport exceptions.', expected_fix: 'Replace http:// endpoints with https:// endpoints and keep ATS enabled. If a temporary exception is unavoidable, isolate it to a documented debug-only configuration, not production code.', ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected insecure HTTP transport in iOS production code.' },
|
|
815
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUrlSessionTrustBypassUsage, locateLines: TextIOS.collectSwiftUrlSessionTrustBypassLines, primaryNode: (lines) => ({ kind: 'call', name: 'URLSession trust challenge accepted without validation', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: validate server trust or use default URLSession trust handling', lines }], why: 'Production iOS networking must not accept arbitrary TLS server trust in URLSessionDelegate callbacks.', impact: 'Accepting URLCredential(trust:) directly for the server trust challenge disables effective certificate validation and can expose sessions to man-in-the-middle attacks.', expected_fix: 'Remove unconditional .useCredential handling. Use default trust handling, cancel invalid challenges, or implement explicit certificate/public-key pinning with a documented trust evaluator.', ruleId: 'heuristics.ios.security.urlsession-trust-bypass.ast', code: 'HEURISTICS_IOS_SECURITY_URLSESSION_TRUST_BYPASS_AST', message: 'AST heuristic detected URLSession trust challenge bypass in iOS production code.' },
|
|
815
816
|
{ platform: 'ios', pathCheck: isIOSInfoPlistPath, excludePaths: [], detect: TextIOS.hasSwiftInsecureTransportUsage, locateLines: TextIOS.collectSwiftInsecureTransportLines, primaryNode: (lines) => ({ kind: 'property', name: 'ATS NSAllowsArbitraryLoads enabled', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: ATS enabled with explicit HTTPS-only exceptions', lines }], why: 'NSAllowsArbitraryLoads disables the default iOS App Transport Security protection and should not be enabled for production builds.', impact: 'Permissive ATS configuration allows insecure transport globally, making networking violations harder to audit per endpoint.', expected_fix: 'Remove NSAllowsArbitraryLoads=true. Keep ATS enabled by default and add the narrowest documented domain exception only when the production requirement is approved.', ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected permissive App Transport Security configuration.' },
|
|
816
817
|
{ platform: 'ios', pathCheck: isIOSLocalizableStringsPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.localization.localizable-strings.ast', code: 'HEURISTICS_IOS_LOCALIZATION_LOCALIZABLE_STRINGS_AST', message: 'AST heuristic detected Localizable.strings usage; String Catalogs (.xcstrings) remain the preferred baseline for new localization work.' },
|
|
817
818
|
{ platform: 'ios', pathCheck: isIOSInterfaceBuilderPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.interface-builder.storyboard-xib.ast', code: 'HEURISTICS_IOS_INTERFACE_BUILDER_STORYBOARD_XIB_AST', message: 'AST heuristic detected Storyboard/XIB usage; programmatic SwiftUI/UIKit UI remains the preferred baseline for versionable iOS code.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { iosRules } from './ios';
|
|
4
4
|
|
|
5
5
|
test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
6
|
-
assert.equal(iosRules.length,
|
|
6
|
+
assert.equal(iosRules.length, 137);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -69,6 +69,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
69
69
|
'heuristics.ios.uikit.cell-without-reuse.ast',
|
|
70
70
|
'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
71
71
|
'heuristics.ios.security.insecure-transport.ast',
|
|
72
|
+
'heuristics.ios.security.urlsession-trust-bypass.ast',
|
|
72
73
|
'heuristics.ios.localization.localizable-strings.ast',
|
|
73
74
|
'heuristics.ios.interface-builder.storyboard-xib.ast',
|
|
74
75
|
'heuristics.ios.localization.hardcoded-ui-string.ast',
|
|
@@ -1145,6 +1145,24 @@ export const iosRules: RuleSet = [
|
|
|
1145
1145
|
code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST',
|
|
1146
1146
|
},
|
|
1147
1147
|
},
|
|
1148
|
+
{
|
|
1149
|
+
id: 'heuristics.ios.security.urlsession-trust-bypass.ast',
|
|
1150
|
+
description: 'Detects URLSessionDelegate trust challenge handlers that accept server trust without validation.',
|
|
1151
|
+
severity: 'WARN',
|
|
1152
|
+
platform: 'ios',
|
|
1153
|
+
locked: true,
|
|
1154
|
+
when: {
|
|
1155
|
+
kind: 'Heuristic',
|
|
1156
|
+
where: {
|
|
1157
|
+
ruleId: 'heuristics.ios.security.urlsession-trust-bypass.ast',
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
then: {
|
|
1161
|
+
kind: 'Finding',
|
|
1162
|
+
message: 'AST heuristic detected URLSession trust challenge bypass in iOS production code.',
|
|
1163
|
+
code: 'HEURISTICS_IOS_SECURITY_URLSESSION_TRUST_BYPASS_AST',
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1148
1166
|
{
|
|
1149
1167
|
id: 'heuristics.ios.localization.localizable-strings.ast',
|
|
1150
1168
|
description: 'Detects legacy Localizable.strings files where String Catalogs are the preferred iOS baseline.',
|
|
@@ -304,6 +304,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
304
304
|
'ios.security.insecure-transport',
|
|
305
305
|
['heuristics.ios.security.insecure-transport.ast']
|
|
306
306
|
),
|
|
307
|
+
'skills.ios.guideline.ios.ssl-pinning-para-apps-con-alta-seguridad': heuristicDetector(
|
|
308
|
+
'ios.security.urlsession-trust-bypass',
|
|
309
|
+
['heuristics.ios.security.urlsession-trust-bypass.ast']
|
|
310
|
+
),
|
|
311
|
+
'skills.ios.guideline.ios.ssl-pinning-prevenir-man-in-the-middle': heuristicDetector(
|
|
312
|
+
'ios.security.urlsession-trust-bypass',
|
|
313
|
+
['heuristics.ios.security.urlsession-trust-bypass.ast']
|
|
314
|
+
),
|
|
307
315
|
'skills.ios.guideline.ios.localizable-strings-deprecado-usar-string-catalogs':
|
|
308
316
|
heuristicDetector('ios.localization.localizable-strings', [
|
|
309
317
|
'heuristics.ios.localization.localizable-strings.ast',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.363",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|