pumuki 6.3.195 → 6.3.197
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 +28 -0
- package/core/facts/detectors/text/ios.ts +19 -0
- package/core/facts/extractHeuristicFacts.ts +7 -0
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +18 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/config/skillsMarkdownRules.ts +16 -0
- package/package.json +1 -1
- package/skills.lock.json +16 -28
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
46
46
|
hasSwiftSensitiveLoggingUsage,
|
|
47
47
|
hasSwiftSensitiveUserDefaultsStorageUsage,
|
|
48
|
+
hasSwiftInsecureTransportUsage,
|
|
48
49
|
hasSwiftJSONSerializationUsage,
|
|
49
50
|
hasSwiftStringFormatUsage,
|
|
50
51
|
hasSwiftTabItemUsage,
|
|
@@ -240,6 +241,33 @@ let text = "UserDefaults.standard.set(accessToken, forKey: \\"accessToken\\")"
|
|
|
240
241
|
assert.equal(hasSwiftSensitiveUserDefaultsStorageUsage(ignored), false);
|
|
241
242
|
});
|
|
242
243
|
|
|
244
|
+
test('detector iOS de seguridad detecta transporte inseguro HTTP y ATS permisivo', () => {
|
|
245
|
+
const source = `
|
|
246
|
+
final class CatalogClient {
|
|
247
|
+
func load() {
|
|
248
|
+
_ = URL(string: "http://example.com/catalog.json")
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
const plist = `
|
|
253
|
+
<dict>
|
|
254
|
+
<key>NSAppTransportSecurity</key>
|
|
255
|
+
<dict>
|
|
256
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
257
|
+
<true/>
|
|
258
|
+
</dict>
|
|
259
|
+
</dict>
|
|
260
|
+
`;
|
|
261
|
+
const ignored = `
|
|
262
|
+
// _ = URL(string: "http://example.com/catalog.json")
|
|
263
|
+
let text = "https://example.com/catalog.json"
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
assert.equal(hasSwiftInsecureTransportUsage(source), true);
|
|
267
|
+
assert.equal(hasSwiftInsecureTransportUsage(plist), true);
|
|
268
|
+
assert.equal(hasSwiftInsecureTransportUsage(ignored), false);
|
|
269
|
+
});
|
|
270
|
+
|
|
243
271
|
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
244
272
|
const source = `
|
|
245
273
|
final class LegacyBox: @unchecked Sendable {}
|
|
@@ -492,6 +492,25 @@ export const hasSwiftSensitiveUserDefaultsStorageUsage = (source: string): boole
|
|
|
492
492
|
});
|
|
493
493
|
};
|
|
494
494
|
|
|
495
|
+
export const hasSwiftInsecureTransportUsage = (source: string): boolean => {
|
|
496
|
+
const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
|
|
497
|
+
const hasHttpUrlLiteral = withoutBlockComments.split(/\r?\n/).some((line) => {
|
|
498
|
+
if (/^\s*\/\//.test(line)) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
return /["']http:\/\/[^"']*["']/.test(line);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
if (hasHttpUrlLiteral) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return (
|
|
509
|
+
/<key>\s*NSAllowsArbitraryLoads\s*<\/key>\s*<true\s*\/>/i.test(source) ||
|
|
510
|
+
/\bNSAllowsArbitraryLoads\b\s*=\s*(?:true|YES|1)\b/i.test(source)
|
|
511
|
+
);
|
|
512
|
+
};
|
|
513
|
+
|
|
495
514
|
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
496
515
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
497
516
|
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
@@ -92,6 +92,11 @@ const isIOSCartfilePath = (path: string): boolean => {
|
|
|
92
92
|
);
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
+
const isIOSInfoPlistPath = (path: string): boolean => {
|
|
96
|
+
const normalized = path.replace(/\\/g, '/');
|
|
97
|
+
return normalized.startsWith('apps/ios/') && normalized.endsWith('/Info.plist');
|
|
98
|
+
};
|
|
99
|
+
|
|
95
100
|
const isIOSApplicationOrPresentationPath = (path: string): boolean => {
|
|
96
101
|
return (
|
|
97
102
|
isIOSSwiftPath(path) &&
|
|
@@ -641,6 +646,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
641
646
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAlamofireUsage, ruleId: 'heuristics.ios.networking.alamofire.ast', code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST', message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.' },
|
|
642
647
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, 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.' },
|
|
643
648
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveUserDefaultsStorageUsage, 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.' },
|
|
649
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInsecureTransportUsage, 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; HTTPS and ATS remain the preferred baseline.' },
|
|
650
|
+
{ platform: 'ios', pathCheck: isIOSInfoPlistPath, excludePaths: [], detect: TextIOS.hasSwiftInsecureTransportUsage, ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected permissive App Transport Security configuration; HTTPS and ATS remain the preferred baseline.' },
|
|
644
651
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUncheckedSendableUsage, ruleId: 'heuristics.ios.unchecked-sendable.ast', code: 'HEURISTICS_IOS_UNCHECKED_SENDABLE_AST', message: 'AST heuristic detected @unchecked Sendable usage.' },
|
|
645
652
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPreconcurrencyUsage, ruleId: 'heuristics.ios.preconcurrency.ast', code: 'HEURISTICS_IOS_PRECONCURRENCY_AST', message: 'AST heuristic detected @preconcurrency usage.' },
|
|
646
653
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonisolatedUnsafeUsage, ruleId: 'heuristics.ios.nonisolated-unsafe.ast', code: 'HEURISTICS_IOS_NONISOLATED_UNSAFE_AST', message: 'AST heuristic detected nonisolated(unsafe) usage.' },
|
|
@@ -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, 51);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -24,6 +24,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
24
24
|
'heuristics.ios.dependencies.cocoapods.ast',
|
|
25
25
|
'heuristics.ios.dependencies.carthage.ast',
|
|
26
26
|
'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
27
|
+
'heuristics.ios.security.insecure-transport.ast',
|
|
27
28
|
'heuristics.ios.unchecked-sendable.ast',
|
|
28
29
|
'heuristics.ios.preconcurrency.ast',
|
|
29
30
|
'heuristics.ios.nonisolated-unsafe.ast',
|
|
@@ -96,6 +97,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
96
97
|
byId.get('heuristics.ios.security.userdefaults-sensitive-data.ast')?.then.code,
|
|
97
98
|
'HEURISTICS_IOS_SECURITY_USERDEFAULTS_SENSITIVE_DATA_AST'
|
|
98
99
|
);
|
|
100
|
+
assert.equal(
|
|
101
|
+
byId.get('heuristics.ios.security.insecure-transport.ast')?.then.code,
|
|
102
|
+
'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST'
|
|
103
|
+
);
|
|
99
104
|
assert.equal(
|
|
100
105
|
byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
|
|
101
106
|
'HEURISTICS_IOS_PRECONCURRENCY_AST'
|
|
@@ -307,6 +307,24 @@ export const iosRules: RuleSet = [
|
|
|
307
307
|
code: 'HEURISTICS_IOS_SECURITY_USERDEFAULTS_SENSITIVE_DATA_AST',
|
|
308
308
|
},
|
|
309
309
|
},
|
|
310
|
+
{
|
|
311
|
+
id: 'heuristics.ios.security.insecure-transport.ast',
|
|
312
|
+
description: 'Detects insecure HTTP transport or permissive ATS configuration in iOS projects.',
|
|
313
|
+
severity: 'WARN',
|
|
314
|
+
platform: 'ios',
|
|
315
|
+
locked: true,
|
|
316
|
+
when: {
|
|
317
|
+
kind: 'Heuristic',
|
|
318
|
+
where: {
|
|
319
|
+
ruleId: 'heuristics.ios.security.insecure-transport.ast',
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
then: {
|
|
323
|
+
kind: 'Finding',
|
|
324
|
+
message: 'AST heuristic detected insecure iOS transport configuration; HTTPS and ATS remain the preferred baseline.',
|
|
325
|
+
code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
310
328
|
{
|
|
311
329
|
id: 'heuristics.ios.unchecked-sendable.ast',
|
|
312
330
|
description: 'Detects @unchecked Sendable usage in iOS production code.',
|
|
@@ -87,6 +87,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
87
87
|
'ios.security.userdefaults-sensitive-data',
|
|
88
88
|
['heuristics.ios.security.userdefaults-sensitive-data.ast']
|
|
89
89
|
),
|
|
90
|
+
'skills.ios.guideline.ios.app-transport-security-ats-https-por-defecto': heuristicDetector(
|
|
91
|
+
'ios.security.insecure-transport',
|
|
92
|
+
['heuristics.ios.security.insecure-transport.ast']
|
|
93
|
+
),
|
|
90
94
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
91
95
|
'heuristics.ios.unchecked-sendable.ast',
|
|
92
96
|
]),
|
|
@@ -381,6 +381,13 @@ const normalizeKnownRuleTarget = (
|
|
|
381
381
|
) {
|
|
382
382
|
return 'skills.ios.no-legacy-expectation-description';
|
|
383
383
|
}
|
|
384
|
+
if (
|
|
385
|
+
includes('app transport security') ||
|
|
386
|
+
includes('ats https') ||
|
|
387
|
+
includes('https por defecto')
|
|
388
|
+
) {
|
|
389
|
+
return 'skills.ios.guideline.ios.app-transport-security-ats-https-por-defecto';
|
|
390
|
+
}
|
|
384
391
|
if (
|
|
385
392
|
includes('mixing legacy xctest style') ||
|
|
386
393
|
includes('mixed xctest and swift testing') ||
|
|
@@ -403,6 +410,15 @@ const normalizeKnownRuleTarget = (
|
|
|
403
410
|
) {
|
|
404
411
|
return 'skills.ios.no-nsmanagedobject-async-boundary';
|
|
405
412
|
}
|
|
413
|
+
if (
|
|
414
|
+
includes('keep swiftdata orchestration') ||
|
|
415
|
+
includes('swiftdata contexts containers queries or persistence models') ||
|
|
416
|
+
includes('modelcontext') ||
|
|
417
|
+
includes('modelcontainer') ||
|
|
418
|
+
includes('query model')
|
|
419
|
+
) {
|
|
420
|
+
return 'skills.ios.no-swiftdata-layer-leak';
|
|
421
|
+
}
|
|
406
422
|
if (
|
|
407
423
|
includes('core data orchestration inside infrastructure') ||
|
|
408
424
|
includes('instead of presentation code') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.197",
|
|
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": {
|
package/skills.lock.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"compilerVersion": "1.0.0",
|
|
4
|
-
"generatedAt": "2026-05-
|
|
4
|
+
"generatedAt": "2026-05-13T11:13:04.662Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -5644,20 +5644,8 @@
|
|
|
5644
5644
|
"name": "ios-core-data-guidelines",
|
|
5645
5645
|
"version": "1.0.0",
|
|
5646
5646
|
"source": "file:vendor/skills/core-data-expert/SKILL.md",
|
|
5647
|
-
"hash": "
|
|
5647
|
+
"hash": "be63caab019ec200e0248cc670f431b27d4fa8023c3267d0577785e50ba14db4",
|
|
5648
5648
|
"rules": [
|
|
5649
|
-
{
|
|
5650
|
-
"id": "skills.ios.guideline.ios-core-data.keep-swiftdata-orchestration-modelcontext-modelcontainer-query-model-i",
|
|
5651
|
-
"description": "Keep SwiftData orchestration (ModelContext, ModelContainer, @Query, @Model) inside infrastructure or repository layers instead of application or presentation code in enterprise Clean Architecture.",
|
|
5652
|
-
"severity": "WARN",
|
|
5653
|
-
"platform": "ios",
|
|
5654
|
-
"sourceSkill": "ios-core-data-guidelines",
|
|
5655
|
-
"sourcePath": "vendor/skills/core-data-expert/SKILL.md",
|
|
5656
|
-
"confidence": "MEDIUM",
|
|
5657
|
-
"locked": true,
|
|
5658
|
-
"evaluationMode": "DECLARATIVE",
|
|
5659
|
-
"origin": "core"
|
|
5660
|
-
},
|
|
5661
5649
|
{
|
|
5662
5650
|
"id": "skills.ios.guideline.ios-core-data.make-context-ownership-explicit-and-keep-merge-boundaries-controlled",
|
|
5663
5651
|
"description": "Make context ownership explicit and keep merge boundaries controlled.",
|
|
@@ -5706,18 +5694,6 @@
|
|
|
5706
5694
|
"evaluationMode": "DECLARATIVE",
|
|
5707
5695
|
"origin": "core"
|
|
5708
5696
|
},
|
|
5709
|
-
{
|
|
5710
|
-
"id": "skills.ios.guideline.ios-core-data.using-swiftdata-contexts-containers-queries-or-persistence-models-dire",
|
|
5711
|
-
"description": "Using SwiftData contexts, containers, queries, or persistence models directly in application or presentation code when the repo requires Clean Architecture boundaries.",
|
|
5712
|
-
"severity": "ERROR",
|
|
5713
|
-
"platform": "ios",
|
|
5714
|
-
"sourceSkill": "ios-core-data-guidelines",
|
|
5715
|
-
"sourcePath": "vendor/skills/core-data-expert/SKILL.md",
|
|
5716
|
-
"confidence": "HIGH",
|
|
5717
|
-
"locked": true,
|
|
5718
|
-
"evaluationMode": "DECLARATIVE",
|
|
5719
|
-
"origin": "core"
|
|
5720
|
-
},
|
|
5721
5697
|
{
|
|
5722
5698
|
"id": "skills.ios.no-core-data-layer-leak",
|
|
5723
5699
|
"description": "Keep Core Data orchestration inside infrastructure or repository layers; avoid Core Data APIs in application or presentation code.",
|
|
@@ -5769,6 +5745,18 @@
|
|
|
5769
5745
|
"sourcePath": "vendor/skills/core-data-expert/SKILL.md",
|
|
5770
5746
|
"evaluationMode": "AUTO",
|
|
5771
5747
|
"origin": "core"
|
|
5748
|
+
},
|
|
5749
|
+
{
|
|
5750
|
+
"id": "skills.ios.no-swiftdata-layer-leak",
|
|
5751
|
+
"description": "Keep SwiftData orchestration (ModelContext, ModelContainer, @Query, @Model) inside infrastructure or repository layers instead of application or presentation code in enterprise Clean Architecture.",
|
|
5752
|
+
"severity": "WARN",
|
|
5753
|
+
"platform": "ios",
|
|
5754
|
+
"sourceSkill": "ios-core-data-guidelines",
|
|
5755
|
+
"sourcePath": "vendor/skills/core-data-expert/SKILL.md",
|
|
5756
|
+
"confidence": "MEDIUM",
|
|
5757
|
+
"locked": true,
|
|
5758
|
+
"evaluationMode": "AUTO",
|
|
5759
|
+
"origin": "core"
|
|
5772
5760
|
}
|
|
5773
5761
|
]
|
|
5774
5762
|
},
|
|
@@ -5776,7 +5764,7 @@
|
|
|
5776
5764
|
"name": "ios-guidelines",
|
|
5777
5765
|
"version": "1.0.0",
|
|
5778
5766
|
"source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5779
|
-
"hash": "
|
|
5767
|
+
"hash": "d4504cef8996fd4877a6c89183ee891e33be0164628ec64d0e1d564db60f9a7a",
|
|
5780
5768
|
"rules": [
|
|
5781
5769
|
{
|
|
5782
5770
|
"id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
|
|
@@ -5883,7 +5871,7 @@
|
|
|
5883
5871
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5884
5872
|
"confidence": "MEDIUM",
|
|
5885
5873
|
"locked": true,
|
|
5886
|
-
"evaluationMode": "
|
|
5874
|
+
"evaluationMode": "AUTO",
|
|
5887
5875
|
"origin": "core"
|
|
5888
5876
|
},
|
|
5889
5877
|
{
|