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.
@@ -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, 49);
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.194",
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-12T21:28:07.507Z",
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": "d91f2cad6499310a4299b39593d47f40a507299a0562c21bc64aad9b5d27c66f",
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
  },