pumuki 6.3.194 → 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)) {
@@ -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
  ]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.194",
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": {