pumuki 6.3.215 → 6.3.217
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 +20 -0
- package/core/facts/detectors/text/ios.ts +7 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.ts +19 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/config/skillsMarkdownRules.ts +8 -0
- package/integrations/gate/evaluateAiGate.ts +110 -1
- package/package.json +1 -1
- package/skills.lock.json +3 -3
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
hasSwiftForceUnwrap,
|
|
24
24
|
hasSwiftGeometryReaderUsage,
|
|
25
25
|
hasSwiftHardcodedUiStringUsage,
|
|
26
|
+
hasSwiftHardcodedSensitiveStringUsage,
|
|
26
27
|
hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage,
|
|
27
28
|
hasSwiftLooseAssetResourceUsage,
|
|
28
29
|
hasSwiftLegacyOnChangeUsage,
|
|
@@ -426,6 +427,25 @@ logger.error("Refresh failed \\(refreshToken)")
|
|
|
426
427
|
assert.equal(hasSwiftSensitiveLoggingUsage(structuredSafe), false);
|
|
427
428
|
});
|
|
428
429
|
|
|
430
|
+
test('hasSwiftHardcodedSensitiveStringUsage detecta secretos hardcodeados en Swift productivo', () => {
|
|
431
|
+
const source = `
|
|
432
|
+
final class Credentials {
|
|
433
|
+
let apiKey = "sk_live_123456789"
|
|
434
|
+
private var refreshToken: String = "refresh-token-123456"
|
|
435
|
+
}
|
|
436
|
+
`;
|
|
437
|
+
const safe = `
|
|
438
|
+
final class Credentials {
|
|
439
|
+
let apiKey = keychain.read("api_key")
|
|
440
|
+
let label = "public title"
|
|
441
|
+
// let apiKey = "sk_live_123456789"
|
|
442
|
+
}
|
|
443
|
+
`;
|
|
444
|
+
|
|
445
|
+
assert.equal(hasSwiftHardcodedSensitiveStringUsage(source), true);
|
|
446
|
+
assert.equal(hasSwiftHardcodedSensitiveStringUsage(safe), false);
|
|
447
|
+
});
|
|
448
|
+
|
|
429
449
|
test('detectores iOS de networking y JSON detectan Alamofire y JSONSerialization sin leer comentarios ni strings', () => {
|
|
430
450
|
const source = `
|
|
431
451
|
import Alamofire
|
|
@@ -600,6 +600,13 @@ export const hasSwiftSensitiveLoggingUsage = (source: string): boolean => {
|
|
|
600
600
|
});
|
|
601
601
|
};
|
|
602
602
|
|
|
603
|
+
export const hasSwiftHardcodedSensitiveStringUsage = (source: string): boolean => {
|
|
604
|
+
return collectSwiftRegexLines(
|
|
605
|
+
source,
|
|
606
|
+
/\b(?:(?:private|fileprivate|internal|public|open|static|class|final|lazy)\s+)*(?:let|var)\s+(?=[A-Za-z_])[A-Za-z0-9_]*(?:token|secret|password|apikey|clientsecret|privatekey|sessionid)[A-Za-z0-9_]*\s*(?::\s*String\s*)?=\s*""/i
|
|
607
|
+
).length > 0;
|
|
608
|
+
};
|
|
609
|
+
|
|
603
610
|
export const hasSwiftAlamofireUsage = (source: string): boolean => {
|
|
604
611
|
return (
|
|
605
612
|
collectSwiftRegexLines(source, /^\s*import\s+Alamofire\b/).length > 0 ||
|
|
@@ -655,6 +655,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
655
655
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMagicNumberLayoutUsage, ruleId: 'heuristics.ios.maintainability.magic-number-layout.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_MAGIC_NUMBER_LAYOUT_AST', message: 'AST heuristic detected SwiftUI layout magic numbers; named constants or design tokens remain the preferred baseline.' },
|
|
656
656
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAdHocLoggingUsage, ruleId: 'heuristics.ios.logging.adhoc-print.ast', code: 'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST', message: 'AST heuristic detected print/debugPrint/dump/NSLog/os_log usage in iOS production code.' },
|
|
657
657
|
{ 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.' },
|
|
658
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedSensitiveStringUsage, ruleId: 'heuristics.ios.security.hardcoded-sensitive-string.ast', code: 'HEURISTICS_IOS_SECURITY_HARDCODED_SENSITIVE_STRING_AST', message: 'AST heuristic detected hardcoded sensitive Swift string; Keychain, secure config or environment-specific secrets remain the preferred baseline.' },
|
|
658
659
|
{ 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.' },
|
|
659
660
|
{ 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.' },
|
|
660
661
|
{ 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.' },
|
|
@@ -350,6 +350,25 @@ export const iosRules: RuleSet = [
|
|
|
350
350
|
code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST',
|
|
351
351
|
},
|
|
352
352
|
},
|
|
353
|
+
{
|
|
354
|
+
id: 'heuristics.ios.security.hardcoded-sensitive-string.ast',
|
|
355
|
+
description: 'Detects hardcoded sensitive Swift string values in iOS production code.',
|
|
356
|
+
severity: 'WARN',
|
|
357
|
+
platform: 'ios',
|
|
358
|
+
locked: true,
|
|
359
|
+
when: {
|
|
360
|
+
kind: 'Heuristic',
|
|
361
|
+
where: {
|
|
362
|
+
ruleId: 'heuristics.ios.security.hardcoded-sensitive-string.ast',
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
then: {
|
|
366
|
+
kind: 'Finding',
|
|
367
|
+
message:
|
|
368
|
+
'AST heuristic detected hardcoded sensitive Swift string; Keychain, secure config or environment-specific secrets remain the preferred baseline.',
|
|
369
|
+
code: 'HEURISTICS_IOS_SECURITY_HARDCODED_SENSITIVE_STRING_AST',
|
|
370
|
+
},
|
|
371
|
+
},
|
|
353
372
|
{
|
|
354
373
|
id: 'heuristics.ios.networking.alamofire.ast',
|
|
355
374
|
description: 'Detects Alamofire usage in iOS production code; URLSession is the preferred baseline for new code.',
|
|
@@ -98,6 +98,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
98
98
|
'ios.logging.sensitive-data',
|
|
99
99
|
['heuristics.ios.logging.sensitive-data.ast']
|
|
100
100
|
),
|
|
101
|
+
'skills.ios.guideline.ios.obfuscation-strings-sensibles-en-co-digo': heuristicDetector(
|
|
102
|
+
'ios.security.hardcoded-sensitive-string',
|
|
103
|
+
['heuristics.ios.security.hardcoded-sensitive-string.ast']
|
|
104
|
+
),
|
|
101
105
|
'skills.ios.guideline.ios.alamofire-prohibido-usar-urlsession-nativo': heuristicDetector(
|
|
102
106
|
'ios.networking.alamofire',
|
|
103
107
|
['heuristics.ios.networking.alamofire.ast']
|
|
@@ -483,6 +483,14 @@ const normalizeKnownRuleTarget = (
|
|
|
483
483
|
if (includes('swinject')) {
|
|
484
484
|
return 'skills.ios.guideline.ios.swinject-prohibido-di-manual-o-environment';
|
|
485
485
|
}
|
|
486
|
+
if (
|
|
487
|
+
includes('obfuscation') ||
|
|
488
|
+
includes('strings sensibles en codigo') ||
|
|
489
|
+
includes('strings sensibles en co digo') ||
|
|
490
|
+
includes('sensitive strings')
|
|
491
|
+
) {
|
|
492
|
+
return 'skills.ios.guideline.ios.obfuscation-strings-sensibles-en-co-digo';
|
|
493
|
+
}
|
|
486
494
|
if (
|
|
487
495
|
includes('mixing legacy xctest style') ||
|
|
488
496
|
includes('mixed xctest and swift testing') ||
|
|
@@ -1144,7 +1144,15 @@ const collectEvidenceViolations = (
|
|
|
1144
1144
|
}
|
|
1145
1145
|
|
|
1146
1146
|
if (result.evidence.ai_gate.status === 'BLOCKED') {
|
|
1147
|
-
|
|
1147
|
+
const gateBlockedMessage = 'Evidence AI gate status is BLOCKED.';
|
|
1148
|
+
violations.push(
|
|
1149
|
+
isAdvisoryDocumentationRenderSlice(repoRoot, ageSeconds, maxAgeSeconds)
|
|
1150
|
+
? toWarnViolation(
|
|
1151
|
+
'EVIDENCE_GATE_BLOCKED',
|
|
1152
|
+
`${gateBlockedMessage} Advisory because the current slice only contains documentation/render/tooling artifacts and evidence is fresh.`
|
|
1153
|
+
)
|
|
1154
|
+
: toErrorViolation('EVIDENCE_GATE_BLOCKED', gateBlockedMessage)
|
|
1155
|
+
);
|
|
1148
1156
|
}
|
|
1149
1157
|
|
|
1150
1158
|
if (stage === 'PRE_WRITE') {
|
|
@@ -1164,6 +1172,107 @@ const collectEvidenceViolations = (
|
|
|
1164
1172
|
return { violations, ageSeconds };
|
|
1165
1173
|
};
|
|
1166
1174
|
|
|
1175
|
+
const SUPPORTED_FUNCTIONAL_EXTENSIONS = new Set([
|
|
1176
|
+
'.swift',
|
|
1177
|
+
'.ts',
|
|
1178
|
+
'.tsx',
|
|
1179
|
+
'.js',
|
|
1180
|
+
'.jsx',
|
|
1181
|
+
'.kt',
|
|
1182
|
+
'.kts',
|
|
1183
|
+
]);
|
|
1184
|
+
|
|
1185
|
+
const DOCUMENTATION_RENDER_TOOLING_EXTENSIONS = new Set([
|
|
1186
|
+
'.css',
|
|
1187
|
+
'.html',
|
|
1188
|
+
'.json',
|
|
1189
|
+
'.md',
|
|
1190
|
+
'.mdx',
|
|
1191
|
+
'.txt',
|
|
1192
|
+
]);
|
|
1193
|
+
|
|
1194
|
+
const normalizeGitPath = (value: string): string => value.replace(/\\/g, '/').replace(/^"|"$/g, '');
|
|
1195
|
+
|
|
1196
|
+
const extractGitStatusPath = (line: string): string | null => {
|
|
1197
|
+
const payload = line.slice(3).trim();
|
|
1198
|
+
if (payload.length === 0) {
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
const renameSeparator = ' -> ';
|
|
1202
|
+
if (payload.includes(renameSeparator)) {
|
|
1203
|
+
return normalizeGitPath(payload.slice(payload.lastIndexOf(renameSeparator) + renameSeparator.length));
|
|
1204
|
+
}
|
|
1205
|
+
return normalizeGitPath(payload);
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
const collectGitStatusPaths = (repoRoot: string): readonly string[] => {
|
|
1209
|
+
if (!existsSync(resolve(repoRoot, '.git'))) {
|
|
1210
|
+
return [];
|
|
1211
|
+
}
|
|
1212
|
+
try {
|
|
1213
|
+
return execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], {
|
|
1214
|
+
cwd: repoRoot,
|
|
1215
|
+
encoding: 'utf8',
|
|
1216
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1217
|
+
})
|
|
1218
|
+
.split(/\r?\n/)
|
|
1219
|
+
.map(extractGitStatusPath)
|
|
1220
|
+
.filter((path): path is string => path !== null);
|
|
1221
|
+
} catch {
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
const pathExtension = (path: string): string => {
|
|
1227
|
+
const basename = path.split('/').pop() ?? '';
|
|
1228
|
+
const dotIndex = basename.lastIndexOf('.');
|
|
1229
|
+
return dotIndex >= 0 ? basename.slice(dotIndex).toLowerCase() : '';
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const isSupportedFunctionalPath = (path: string): boolean =>
|
|
1233
|
+
SUPPORTED_FUNCTIONAL_EXTENSIONS.has(pathExtension(path));
|
|
1234
|
+
|
|
1235
|
+
const isDocumentationRenderToolingPath = (path: string): boolean => {
|
|
1236
|
+
const normalized = path.toLowerCase();
|
|
1237
|
+
if (normalized.startsWith('docs/') && DOCUMENTATION_RENDER_TOOLING_EXTENSIONS.has(pathExtension(normalized))) {
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
if (normalized.endsWith('.md') || normalized.endsWith('.mdx')) {
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
if (
|
|
1244
|
+
normalized.startsWith('stack-my-architecture-pumuki/dist/') ||
|
|
1245
|
+
normalized.startsWith('stack-my-architecture-hub/pumuki/')
|
|
1246
|
+
) {
|
|
1247
|
+
return DOCUMENTATION_RENDER_TOOLING_EXTENSIONS.has(pathExtension(normalized));
|
|
1248
|
+
}
|
|
1249
|
+
if (normalized === 'stack-my-architecture-pumuki/scripts/build-html.py') {
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1252
|
+
if (normalized === 'package.json' || normalized === 'package-lock.json') {
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
return false;
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
const isAdvisoryDocumentationRenderSlice = (
|
|
1259
|
+
repoRoot: string,
|
|
1260
|
+
ageSeconds: number,
|
|
1261
|
+
maxAgeSeconds: number
|
|
1262
|
+
): boolean => {
|
|
1263
|
+
if (ageSeconds > maxAgeSeconds) {
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
const paths = collectGitStatusPaths(repoRoot);
|
|
1267
|
+
if (paths.length === 0) {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
return (
|
|
1271
|
+
paths.every(isDocumentationRenderToolingPath) &&
|
|
1272
|
+
!paths.some(isSupportedFunctionalPath)
|
|
1273
|
+
);
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1167
1276
|
const toEvidenceSourceDescriptor = (
|
|
1168
1277
|
result: EvidenceReadResult,
|
|
1169
1278
|
repoRoot: string
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.217",
|
|
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-13T13:
|
|
4
|
+
"generatedAt": "2026-05-13T13:30:09.135Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -5764,7 +5764,7 @@
|
|
|
5764
5764
|
"name": "ios-guidelines",
|
|
5765
5765
|
"version": "1.0.0",
|
|
5766
5766
|
"source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5767
|
-
"hash": "
|
|
5767
|
+
"hash": "c7b96d97cd02175dacf17b64cb8bdd1be7b5978dd2f74c7feb3bf3d462311467",
|
|
5768
5768
|
"rules": [
|
|
5769
5769
|
{
|
|
5770
5770
|
"id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
|
|
@@ -7192,7 +7192,7 @@
|
|
|
7192
7192
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
7193
7193
|
"confidence": "MEDIUM",
|
|
7194
7194
|
"locked": true,
|
|
7195
|
-
"evaluationMode": "
|
|
7195
|
+
"evaluationMode": "AUTO",
|
|
7196
7196
|
"origin": "core"
|
|
7197
7197
|
},
|
|
7198
7198
|
{
|