pumuki 6.3.194 → 6.3.196
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 +17 -0
- package/core/facts/detectors/text/ios.ts +17 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +18 -0
- package/docs/codex-skills/ios-enterprise-rules.md +7 -0
- package/integrations/config/skillsDetectorRegistry.ts +12 -0
- package/integrations/config/skillsMarkdownRules.ts +9 -0
- package/package.json +1 -1
- package/skills.lock.json +14 -26
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
hasSwiftSheetIsPresentedUsage,
|
|
45
45
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
46
46
|
hasSwiftSensitiveLoggingUsage,
|
|
47
|
+
hasSwiftSensitiveUserDefaultsStorageUsage,
|
|
47
48
|
hasSwiftJSONSerializationUsage,
|
|
48
49
|
hasSwiftStringFormatUsage,
|
|
49
50
|
hasSwiftTabItemUsage,
|
|
@@ -223,6 +224,22 @@ final class APIClient {
|
|
|
223
224
|
assert.equal(hasSwiftJSONSerializationUsage(ignored), false);
|
|
224
225
|
});
|
|
225
226
|
|
|
227
|
+
test('detector iOS de seguridad detecta secretos en UserDefaults y AppStorage', () => {
|
|
228
|
+
const source = `
|
|
229
|
+
UserDefaults.standard.set(accessToken, forKey: "accessToken")
|
|
230
|
+
@AppStorage("refreshToken") private var refreshToken = ""
|
|
231
|
+
`;
|
|
232
|
+
const ignored = `
|
|
233
|
+
UserDefaults.standard.set(theme, forKey: "selectedTheme")
|
|
234
|
+
@AppStorage("preferredTab") private var preferredTab = "home"
|
|
235
|
+
let text = "UserDefaults.standard.set(accessToken, forKey: \\"accessToken\\")"
|
|
236
|
+
// UserDefaults.standard.set(accessToken, forKey: "accessToken")
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
assert.equal(hasSwiftSensitiveUserDefaultsStorageUsage(source), true);
|
|
240
|
+
assert.equal(hasSwiftSensitiveUserDefaultsStorageUsage(ignored), false);
|
|
241
|
+
});
|
|
242
|
+
|
|
226
243
|
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
227
244
|
const source = `
|
|
228
245
|
final class LegacyBox: @unchecked Sendable {}
|
|
@@ -475,6 +475,23 @@ export const hasSwiftJSONSerializationUsage = (source: string): boolean => {
|
|
|
475
475
|
return collectSwiftRegexLines(source, /\bJSONSerialization\s*\./).length > 0;
|
|
476
476
|
};
|
|
477
477
|
|
|
478
|
+
export const hasSwiftSensitiveUserDefaultsStorageUsage = (source: string): boolean => {
|
|
479
|
+
return source.split(/\r?\n/).some((line) => {
|
|
480
|
+
const sanitized = stripSwiftLineForSemanticScan(line);
|
|
481
|
+
const lineWithoutComments = line.replace(/\/\/.*$/, '');
|
|
482
|
+
const hasUserDefaultsWrite = /\bUserDefaults\s*\.\s*standard\s*\.\s*set\s*\(/.test(sanitized);
|
|
483
|
+
const hasAppStorage = /@\s*AppStorage\s*\(/.test(sanitized);
|
|
484
|
+
|
|
485
|
+
if (!hasUserDefaultsWrite && !hasAppStorage) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return /\b(?:accessToken|refreshToken|authToken|token|password|secret|credential|authorization|bearer|apiKey|sessionId)\b/i.test(
|
|
490
|
+
lineWithoutComments
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
};
|
|
494
|
+
|
|
478
495
|
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
479
496
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
480
497
|
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
@@ -640,6 +640,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
640
640
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveLoggingUsage, ruleId: 'heuristics.ios.logging.sensitive-data.ast', code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data in an iOS logging call.' },
|
|
641
641
|
{ 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
642
|
{ 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
|
+
{ 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.' },
|
|
643
644
|
{ 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.' },
|
|
644
645
|
{ 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.' },
|
|
645
646
|
{ 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, 50);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -23,6 +23,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
23
23
|
'heuristics.ios.json.jsonserialization.ast',
|
|
24
24
|
'heuristics.ios.dependencies.cocoapods.ast',
|
|
25
25
|
'heuristics.ios.dependencies.carthage.ast',
|
|
26
|
+
'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
26
27
|
'heuristics.ios.unchecked-sendable.ast',
|
|
27
28
|
'heuristics.ios.preconcurrency.ast',
|
|
28
29
|
'heuristics.ios.nonisolated-unsafe.ast',
|
|
@@ -91,6 +92,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
91
92
|
byId.get('heuristics.ios.dependencies.carthage.ast')?.then.code,
|
|
92
93
|
'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST'
|
|
93
94
|
);
|
|
95
|
+
assert.equal(
|
|
96
|
+
byId.get('heuristics.ios.security.userdefaults-sensitive-data.ast')?.then.code,
|
|
97
|
+
'HEURISTICS_IOS_SECURITY_USERDEFAULTS_SENSITIVE_DATA_AST'
|
|
98
|
+
);
|
|
94
99
|
assert.equal(
|
|
95
100
|
byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
|
|
96
101
|
'HEURISTICS_IOS_PRECONCURRENCY_AST'
|
|
@@ -289,6 +289,24 @@ export const iosRules: RuleSet = [
|
|
|
289
289
|
code: 'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST',
|
|
290
290
|
},
|
|
291
291
|
},
|
|
292
|
+
{
|
|
293
|
+
id: 'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
294
|
+
description: 'Detects sensitive data stored in UserDefaults/AppStorage; Keychain is the preferred baseline for secrets.',
|
|
295
|
+
severity: 'WARN',
|
|
296
|
+
platform: 'ios',
|
|
297
|
+
locked: true,
|
|
298
|
+
when: {
|
|
299
|
+
kind: 'Heuristic',
|
|
300
|
+
where: {
|
|
301
|
+
ruleId: 'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
then: {
|
|
305
|
+
kind: 'Finding',
|
|
306
|
+
message: 'AST heuristic detected sensitive data stored in UserDefaults/AppStorage; native Keychain remains the preferred baseline for secrets.',
|
|
307
|
+
code: 'HEURISTICS_IOS_SECURITY_USERDEFAULTS_SENSITIVE_DATA_AST',
|
|
308
|
+
},
|
|
309
|
+
},
|
|
292
310
|
{
|
|
293
311
|
id: 'heuristics.ios.unchecked-sendable.ast',
|
|
294
312
|
description: 'Detects @unchecked Sendable usage in iOS production code.',
|
|
@@ -595,6 +595,13 @@ struct APIEndpoint: Sendable {
|
|
|
595
595
|
✅ **FileManager** - Archivos, imágenes, documents
|
|
596
596
|
✅ **iCloud** - Sync entre dispositivos (NSUbiquitousKeyValueStore, CloudKit)
|
|
597
597
|
|
|
598
|
+
### Enforcement AST inicial de secretos en preferencias iOS
|
|
599
|
+
|
|
600
|
+
- `skills.ios.guideline.ios.keychain-passwords-tokens-no-userdefaults` se mapea a `heuristics.ios.security.userdefaults-sensitive-data.ast`.
|
|
601
|
+
- `skills.ios.guideline.ios.keychainservices-nativo-passwords-tokens-datos-sensibles-no-wrappers-d` se mapea a `heuristics.ios.security.userdefaults-sensitive-data.ast`.
|
|
602
|
+
- `skills.ios.guideline.ios.userdefaults-settings-simples-no-datos-sensibles` se mapea a `heuristics.ios.security.userdefaults-sensitive-data.ast`.
|
|
603
|
+
- En `PROJECT MODE: brownfield`, este hallazgo es señal de baseline/adopción y debe evitar drift nuevo sin bloquear deuda histórica salvo promoción explícita de policy. Keychain nativo permanece como baseline preferente para secretos.
|
|
604
|
+
|
|
598
605
|
### Combine (Reactive):
|
|
599
606
|
✅ **Publishers** - AsyncSequence para async, Combine para streams complejos
|
|
600
607
|
✅ **@Published** - En ViewModels para binding con Views
|
|
@@ -75,6 +75,18 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
75
75
|
'ios.dependencies.carthage',
|
|
76
76
|
['heuristics.ios.dependencies.carthage.ast']
|
|
77
77
|
),
|
|
78
|
+
'skills.ios.guideline.ios.keychain-passwords-tokens-no-userdefaults': heuristicDetector(
|
|
79
|
+
'ios.security.userdefaults-sensitive-data',
|
|
80
|
+
['heuristics.ios.security.userdefaults-sensitive-data.ast']
|
|
81
|
+
),
|
|
82
|
+
'skills.ios.guideline.ios.keychainservices-nativo-passwords-tokens-datos-sensibles-no-wrappers-d': heuristicDetector(
|
|
83
|
+
'ios.security.userdefaults-sensitive-data',
|
|
84
|
+
['heuristics.ios.security.userdefaults-sensitive-data.ast']
|
|
85
|
+
),
|
|
86
|
+
'skills.ios.guideline.ios.userdefaults-settings-simples-no-datos-sensibles': heuristicDetector(
|
|
87
|
+
'ios.security.userdefaults-sensitive-data',
|
|
88
|
+
['heuristics.ios.security.userdefaults-sensitive-data.ast']
|
|
89
|
+
),
|
|
78
90
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
79
91
|
'heuristics.ios.unchecked-sendable.ast',
|
|
80
92
|
]),
|
|
@@ -403,6 +403,15 @@ const normalizeKnownRuleTarget = (
|
|
|
403
403
|
) {
|
|
404
404
|
return 'skills.ios.no-nsmanagedobject-async-boundary';
|
|
405
405
|
}
|
|
406
|
+
if (
|
|
407
|
+
includes('keep swiftdata orchestration') ||
|
|
408
|
+
includes('swiftdata contexts containers queries or persistence models') ||
|
|
409
|
+
includes('modelcontext') ||
|
|
410
|
+
includes('modelcontainer') ||
|
|
411
|
+
includes('query model')
|
|
412
|
+
) {
|
|
413
|
+
return 'skills.ios.no-swiftdata-layer-leak';
|
|
414
|
+
}
|
|
406
415
|
if (
|
|
407
416
|
includes('core data orchestration inside infrastructure') ||
|
|
408
417
|
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.196",
|
|
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:03:09.894Z",
|
|
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
|
},
|