pumuki 6.3.193 → 6.3.195

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)) {
@@ -76,6 +76,22 @@ const isIOSSwiftPath = (path: string): boolean => {
76
76
  return path.endsWith('.swift') && path.startsWith('apps/ios/');
77
77
  };
78
78
 
79
+ const isIOSPodfilePath = (path: string): boolean => {
80
+ const normalized = path.replace(/\\/g, '/');
81
+ return (
82
+ normalized.startsWith('apps/ios/') &&
83
+ (normalized.endsWith('/Podfile') || normalized.endsWith('/Podfile.lock'))
84
+ );
85
+ };
86
+
87
+ const isIOSCartfilePath = (path: string): boolean => {
88
+ const normalized = path.replace(/\\/g, '/');
89
+ return (
90
+ normalized.startsWith('apps/ios/') &&
91
+ (normalized.endsWith('/Cartfile') || normalized.endsWith('/Cartfile.resolved'))
92
+ );
93
+ };
94
+
79
95
  const isIOSApplicationOrPresentationPath = (path: string): boolean => {
80
96
  return (
81
97
  isIOSSwiftPath(path) &&
@@ -608,6 +624,8 @@ type TextDetectorRegistryEntry = {
608
624
 
609
625
  const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
610
626
  // iOS
627
+ { platform: 'ios', pathCheck: isIOSPodfilePath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.dependencies.cocoapods.ast', code: 'HEURISTICS_IOS_DEPENDENCIES_COCOAPODS_AST', message: 'AST heuristic detected CocoaPods dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.' },
628
+ { platform: 'ios', pathCheck: isIOSCartfilePath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.dependencies.carthage.ast', code: 'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST', message: 'AST heuristic detected Carthage dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.' },
611
629
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForceUnwrap, ruleId: 'heuristics.ios.force-unwrap.ast', code: 'HEURISTICS_IOS_FORCE_UNWRAP_AST', message: 'AST heuristic detected force unwrap usage.' },
612
630
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAnyViewUsage, ruleId: 'heuristics.ios.anyview.ast', code: 'HEURISTICS_IOS_ANYVIEW_AST', message: 'AST heuristic detected AnyView usage.' },
613
631
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForceTryUsage, ruleId: 'heuristics.ios.force-try.ast', code: 'HEURISTICS_IOS_FORCE_TRY_AST', message: 'AST heuristic detected force try usage.' },
@@ -622,6 +640,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
622
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.' },
623
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.' },
624
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.' },
625
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.' },
626
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.' },
627
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, 47);
6
+ assert.equal(iosRules.length, 50);
7
7
 
8
8
  const ids = iosRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -21,6 +21,9 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
21
21
  'heuristics.ios.logging.sensitive-data.ast',
22
22
  'heuristics.ios.networking.alamofire.ast',
23
23
  'heuristics.ios.json.jsonserialization.ast',
24
+ 'heuristics.ios.dependencies.cocoapods.ast',
25
+ 'heuristics.ios.dependencies.carthage.ast',
26
+ 'heuristics.ios.security.userdefaults-sensitive-data.ast',
24
27
  'heuristics.ios.unchecked-sendable.ast',
25
28
  'heuristics.ios.preconcurrency.ast',
26
29
  'heuristics.ios.nonisolated-unsafe.ast',
@@ -81,6 +84,18 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
81
84
  byId.get('heuristics.ios.json.jsonserialization.ast')?.then.code,
82
85
  'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST'
83
86
  );
87
+ assert.equal(
88
+ byId.get('heuristics.ios.dependencies.cocoapods.ast')?.then.code,
89
+ 'HEURISTICS_IOS_DEPENDENCIES_COCOAPODS_AST'
90
+ );
91
+ assert.equal(
92
+ byId.get('heuristics.ios.dependencies.carthage.ast')?.then.code,
93
+ 'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST'
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
+ );
84
99
  assert.equal(
85
100
  byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
86
101
  'HEURISTICS_IOS_PRECONCURRENCY_AST'
@@ -253,6 +253,60 @@ export const iosRules: RuleSet = [
253
253
  code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST',
254
254
  },
255
255
  },
256
+ {
257
+ id: 'heuristics.ios.dependencies.cocoapods.ast',
258
+ description: 'Detects CocoaPods dependency files in iOS projects; Swift Package Manager is the preferred baseline for new code.',
259
+ severity: 'WARN',
260
+ platform: 'ios',
261
+ locked: true,
262
+ when: {
263
+ kind: 'Heuristic',
264
+ where: {
265
+ ruleId: 'heuristics.ios.dependencies.cocoapods.ast',
266
+ },
267
+ },
268
+ then: {
269
+ kind: 'Finding',
270
+ message: 'AST heuristic detected CocoaPods dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.',
271
+ code: 'HEURISTICS_IOS_DEPENDENCIES_COCOAPODS_AST',
272
+ },
273
+ },
274
+ {
275
+ id: 'heuristics.ios.dependencies.carthage.ast',
276
+ description: 'Detects Carthage dependency files in iOS projects; Swift Package Manager is the preferred baseline for new code.',
277
+ severity: 'WARN',
278
+ platform: 'ios',
279
+ locked: true,
280
+ when: {
281
+ kind: 'Heuristic',
282
+ where: {
283
+ ruleId: 'heuristics.ios.dependencies.carthage.ast',
284
+ },
285
+ },
286
+ then: {
287
+ kind: 'Finding',
288
+ message: 'AST heuristic detected Carthage dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.',
289
+ code: 'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST',
290
+ },
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
+ },
256
310
  {
257
311
  id: 'heuristics.ios.unchecked-sendable.ast',
258
312
  description: 'Detects @unchecked Sendable usage in iOS production code.',
@@ -509,6 +509,12 @@ struct UseCaseFactory {
509
509
  - `skills.ios.guideline.ios.codable-para-serializacio-n-json-nunca-jsonserialization` se mapea a `heuristics.ios.json.jsonserialization.ast`.
510
510
  - En `PROJECT MODE: brownfield`, estos hallazgos son señal de baseline/adopción y deben evitar drift nuevo sin bloquear deuda histórica salvo promoción explícita de policy.
511
511
 
512
+ ### Enforcement AST inicial de dependencias iOS
513
+
514
+ - `skills.ios.guideline.ios.cocoapods-prohibido` se mapea a `heuristics.ios.dependencies.cocoapods.ast`.
515
+ - `skills.ios.guideline.ios.carthage-prohibido` se mapea a `heuristics.ios.dependencies.carthage.ast`.
516
+ - En `PROJECT MODE: brownfield`, estos hallazgos son señal de baseline/adopción y deben evitar drift nuevo sin bloquear deuda histórica salvo promoción explícita de policy. Swift Package Manager permanece como baseline preferente para código nuevo.
517
+
512
518
  ```swift
513
519
  // ✅ Ejemplo: APIClient con URLSession y async/await
514
520
  protocol APIClientProtocol: Sendable {
@@ -589,6 +595,13 @@ struct APIEndpoint: Sendable {
589
595
  ✅ **FileManager** - Archivos, imágenes, documents
590
596
  ✅ **iCloud** - Sync entre dispositivos (NSUbiquitousKeyValueStore, CloudKit)
591
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
+
592
605
  ### Combine (Reactive):
593
606
  ✅ **Publishers** - AsyncSequence para async, Combine para streams complejos
594
607
  ✅ **@Published** - En ViewModels para binding con Views
@@ -67,6 +67,26 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
67
67
  'ios.json.jsonserialization',
68
68
  ['heuristics.ios.json.jsonserialization.ast']
69
69
  ),
70
+ 'skills.ios.guideline.ios.cocoapods-prohibido': heuristicDetector(
71
+ 'ios.dependencies.cocoapods',
72
+ ['heuristics.ios.dependencies.cocoapods.ast']
73
+ ),
74
+ 'skills.ios.guideline.ios.carthage-prohibido': heuristicDetector(
75
+ 'ios.dependencies.carthage',
76
+ ['heuristics.ios.dependencies.carthage.ast']
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
+ ),
70
90
  'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
71
91
  'heuristics.ios.unchecked-sendable.ast',
72
92
  ]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.193",
3
+ "version": "6.3.195",
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": {