pumuki 6.3.361 → 6.3.363
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/android.test.ts +26 -0
- package/core/facts/detectors/text/android.ts +45 -0
- package/core/facts/detectors/text/ios.test.ts +37 -0
- package/core/facts/detectors/text/ios.ts +21 -0
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/android.test.ts +6 -1
- package/core/rules/presets/heuristics/android.ts +20 -0
- package/core/rules/presets/heuristics/ios.test.ts +2 -1
- package/core/rules/presets/heuristics/ios.ts +18 -0
- package/integrations/config/skillsDetectorRegistry.ts +12 -0
- package/package.json +1 -1
- package/skills.lock.json +1 -1
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
findKotlinPresentationSrpMatch,
|
|
9
9
|
hasAndroidAsyncTaskUsage,
|
|
10
10
|
hasAndroidCustomSingletonObjectUsage,
|
|
11
|
+
hasAndroidHiltInjectionWithoutEntryPointUsage,
|
|
11
12
|
hasAndroidLegacyFingerprintApiUsage,
|
|
12
13
|
hasKotlinCoroutineTryCatchUsage,
|
|
13
14
|
hasKotlinDispatcherMainBoundaryLeakUsage,
|
|
@@ -1306,3 +1307,28 @@ object CheckoutModule {
|
|
|
1306
1307
|
assert.equal(hasAndroidCustomSingletonObjectUsage(singletonSource), true);
|
|
1307
1308
|
assert.equal(hasAndroidCustomSingletonObjectUsage(safeSource), false);
|
|
1308
1309
|
});
|
|
1310
|
+
|
|
1311
|
+
test('hasAndroidHiltInjectionWithoutEntryPointUsage detecta Activity o Fragment con inyección Hilt sin AndroidEntryPoint', () => {
|
|
1312
|
+
const missingEntryPoint = `
|
|
1313
|
+
class CheckoutActivity : AppCompatActivity() {
|
|
1314
|
+
@Inject lateinit var analytics: CheckoutAnalytics
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
class StoresFragment : Fragment() {
|
|
1318
|
+
@Inject lateinit var repository: StoresRepository
|
|
1319
|
+
}
|
|
1320
|
+
`;
|
|
1321
|
+
const validEntryPoint = `
|
|
1322
|
+
@AndroidEntryPoint
|
|
1323
|
+
class CheckoutActivity : AppCompatActivity() {
|
|
1324
|
+
@Inject lateinit var analytics: CheckoutAnalytics
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
class CheckoutViewModel @Inject constructor(
|
|
1328
|
+
private val repository: CheckoutRepository
|
|
1329
|
+
) : ViewModel()
|
|
1330
|
+
`;
|
|
1331
|
+
|
|
1332
|
+
assert.equal(hasAndroidHiltInjectionWithoutEntryPointUsage(missingEntryPoint), true);
|
|
1333
|
+
assert.equal(hasAndroidHiltInjectionWithoutEntryPointUsage(validEntryPoint), false);
|
|
1334
|
+
});
|
|
@@ -175,6 +175,51 @@ export const collectAndroidCustomSingletonObjectLines = (source: string): readon
|
|
|
175
175
|
export const hasAndroidCustomSingletonObjectUsage = (source: string): boolean =>
|
|
176
176
|
collectAndroidCustomSingletonObjectLines(source).length > 0;
|
|
177
177
|
|
|
178
|
+
export const collectAndroidHiltInjectionWithoutEntryPointLines = (
|
|
179
|
+
source: string
|
|
180
|
+
): readonly number[] => {
|
|
181
|
+
const lines = source.split(/\r?\n/);
|
|
182
|
+
const matches: number[] = [];
|
|
183
|
+
|
|
184
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
185
|
+
const sanitized = stripKotlinLineForSemanticScan(lines[index] ?? '');
|
|
186
|
+
const classMatch = sanitized.match(
|
|
187
|
+
/^\s*(?:class|open\s+class)\s+([A-Za-z_][A-Za-z0-9_]*)\b[^{]*(?:Activity|Fragment)\s*\(/
|
|
188
|
+
);
|
|
189
|
+
if (!classMatch) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const annotationContext = lines
|
|
194
|
+
.slice(Math.max(0, index - 4), index)
|
|
195
|
+
.map((line) => stripKotlinLineForSemanticScan(line))
|
|
196
|
+
.join('\n');
|
|
197
|
+
if (/@AndroidEntryPoint\b/.test(annotationContext)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const classStartLine = index + 1;
|
|
202
|
+
let braceDepth =
|
|
203
|
+
countTokenOccurrences(sanitized, '{') - countTokenOccurrences(sanitized, '}');
|
|
204
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
205
|
+
const candidate = stripKotlinLineForSemanticScan(lines[cursor] ?? '');
|
|
206
|
+
if (/@Inject\b\s+lateinit\s+var\b/.test(candidate)) {
|
|
207
|
+
matches.push(classStartLine, cursor + 1);
|
|
208
|
+
}
|
|
209
|
+
braceDepth += countTokenOccurrences(candidate, '{');
|
|
210
|
+
braceDepth -= countTokenOccurrences(candidate, '}');
|
|
211
|
+
if (braceDepth <= 0) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return sortedUniqueLines(matches);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const hasAndroidHiltInjectionWithoutEntryPointUsage = (source: string): boolean =>
|
|
221
|
+
collectAndroidHiltInjectionWithoutEntryPointLines(source).length > 0;
|
|
222
|
+
|
|
178
223
|
const countTokenOccurrences = (line: string, token: string): number => {
|
|
179
224
|
return line.split(token).length - 1;
|
|
180
225
|
};
|
|
@@ -180,9 +180,11 @@ import {
|
|
|
180
180
|
hasSwiftSensitiveLoggingUsage,
|
|
181
181
|
hasSwiftSelfPrintChangesUsage,
|
|
182
182
|
collectSwiftInsecureTransportLines,
|
|
183
|
+
collectSwiftUrlSessionTrustBypassLines,
|
|
183
184
|
collectSwiftSensitiveUserDefaultsStorageLines,
|
|
184
185
|
hasSwiftSensitiveUserDefaultsStorageUsage,
|
|
185
186
|
hasSwiftInsecureTransportUsage,
|
|
187
|
+
hasSwiftUrlSessionTrustBypassUsage,
|
|
186
188
|
hasSwiftJSONSerializationUsage,
|
|
187
189
|
collectSwiftJSONSerializationLines,
|
|
188
190
|
hasSwiftExplicitColorStaticMemberUsage,
|
|
@@ -229,6 +231,41 @@ let value = loadUser()!
|
|
|
229
231
|
assert.deepEqual(collectSwiftForceUnwrapLines(source), [2, 3]);
|
|
230
232
|
});
|
|
231
233
|
|
|
234
|
+
test('hasSwiftUrlSessionTrustBypassUsage detecta delegate que acepta cualquier certificado', () => {
|
|
235
|
+
const source = `
|
|
236
|
+
final class InsecureDelegate: NSObject, URLSessionDelegate {
|
|
237
|
+
func urlSession(
|
|
238
|
+
_ session: URLSession,
|
|
239
|
+
didReceive challenge: URLAuthenticationChallenge,
|
|
240
|
+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
|
241
|
+
) {
|
|
242
|
+
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
assert.equal(hasSwiftUrlSessionTrustBypassUsage(source), true);
|
|
248
|
+
assert.deepEqual(collectSwiftUrlSessionTrustBypassLines(source), [8]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('hasSwiftUrlSessionTrustBypassUsage preserva cancelacion o manejo por defecto del challenge TLS', () => {
|
|
252
|
+
const source = `
|
|
253
|
+
final class SecureDelegate: NSObject, URLSessionDelegate {
|
|
254
|
+
func urlSession(
|
|
255
|
+
_ session: URLSession,
|
|
256
|
+
didReceive challenge: URLAuthenticationChallenge,
|
|
257
|
+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
|
258
|
+
) {
|
|
259
|
+
completionHandler(.performDefaultHandling, nil)
|
|
260
|
+
completionHandler(.cancelAuthenticationChallenge, nil)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
`;
|
|
264
|
+
|
|
265
|
+
assert.equal(hasSwiftUrlSessionTrustBypassUsage(source), false);
|
|
266
|
+
assert.deepEqual(collectSwiftUrlSessionTrustBypassLines(source), []);
|
|
267
|
+
});
|
|
268
|
+
|
|
232
269
|
test('hasSwiftForceUnwrap excluye type annotations, force cast y operadores', () => {
|
|
233
270
|
const source = `
|
|
234
271
|
let name: String!
|
|
@@ -1940,6 +1940,27 @@ export const collectSwiftInsecureTransportLines = (source: string): readonly num
|
|
|
1940
1940
|
return sortedUniqueLines(result);
|
|
1941
1941
|
};
|
|
1942
1942
|
|
|
1943
|
+
export const collectSwiftUrlSessionTrustBypassLines = (source: string): readonly number[] => {
|
|
1944
|
+
const result: number[] = [];
|
|
1945
|
+
|
|
1946
|
+
source.split(/\r?\n/).forEach((line, index) => {
|
|
1947
|
+
const sanitized = stripSwiftLineForSemanticScan(line);
|
|
1948
|
+
if (
|
|
1949
|
+
/completionHandler\s*\(\s*\.useCredential\s*,\s*URLCredential\s*\(\s*trust\s*:/.test(
|
|
1950
|
+
sanitized
|
|
1951
|
+
)
|
|
1952
|
+
) {
|
|
1953
|
+
result.push(index + 1);
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
return sortedUniqueLines(result);
|
|
1958
|
+
};
|
|
1959
|
+
|
|
1960
|
+
export const hasSwiftUrlSessionTrustBypassUsage = (source: string): boolean => {
|
|
1961
|
+
return collectSwiftUrlSessionTrustBypassLines(source).length > 0;
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1943
1964
|
const swiftUiLiteralTextPatterns = [
|
|
1944
1965
|
/\b(?:Text|Button|Label|TextField|SecureField)\s*\(\s*"((?:\\.|[^"\\])*)"/,
|
|
1945
1966
|
/\.navigationTitle\s*\(\s*"((?:\\.|[^"\\])*)"/,
|
|
@@ -812,6 +812,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
812
812
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, locateLines: TextIOS.collectSwiftJSONSerializationLines, primaryNode: (lines) => ({ kind: 'call', name: 'JSONSerialization call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: Codable decoder or encoder', lines }], why: 'JSONSerialization bypasses typed decoding and makes payload contracts harder to validate than Codable models.', impact: 'Runtime casts and dictionary traversal can hide API drift until production and make the gate report vague file-level serialization debt.', expected_fix: 'Replace JSONSerialization calls with Codable DTOs using JSONDecoder or JSONEncoder at the repository networking boundary.', 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.' },
|
|
813
813
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveUserDefaultsStorageUsage, locateLines: TextIOS.collectSwiftSensitiveUserDefaultsStorageLines, primaryNode: (lines) => ({ kind: 'call', name: 'sensitive UserDefaults/AppStorage storage', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: native Keychain or secure secret store', lines }], why: 'Tokens, passwords, credentials and session identifiers must not be stored in UserDefaults or AppStorage because those stores are not the approved secret boundary.', impact: 'Secrets persisted in UserDefaults/AppStorage can leak through backups, diagnostics or simple local inspection, and the gate needs the exact storage node to avoid whole-file remediation.', expected_fix: 'Move tokens, passwords, credentials and session identifiers to native Keychain or the repository-approved secure storage adapter. Keep UserDefaults/AppStorage only for non-sensitive preferences.', 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.' },
|
|
814
814
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInsecureTransportUsage, locateLines: TextIOS.collectSwiftInsecureTransportLines, primaryNode: (lines) => ({ kind: 'call', name: 'insecure HTTP URL literal', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: HTTPS URLSession endpoint with ATS enabled', lines }], why: 'Production iOS networking must use HTTPS and keep App Transport Security enabled by default.', impact: 'HTTP endpoints can expose credentials, session data or payloads in transit and create release-blocking transport exceptions.', expected_fix: 'Replace http:// endpoints with https:// endpoints and keep ATS enabled. If a temporary exception is unavoidable, isolate it to a documented debug-only configuration, not production code.', ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected insecure HTTP transport in iOS production code.' },
|
|
815
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUrlSessionTrustBypassUsage, locateLines: TextIOS.collectSwiftUrlSessionTrustBypassLines, primaryNode: (lines) => ({ kind: 'call', name: 'URLSession trust challenge accepted without validation', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: validate server trust or use default URLSession trust handling', lines }], why: 'Production iOS networking must not accept arbitrary TLS server trust in URLSessionDelegate callbacks.', impact: 'Accepting URLCredential(trust:) directly for the server trust challenge disables effective certificate validation and can expose sessions to man-in-the-middle attacks.', expected_fix: 'Remove unconditional .useCredential handling. Use default trust handling, cancel invalid challenges, or implement explicit certificate/public-key pinning with a documented trust evaluator.', ruleId: 'heuristics.ios.security.urlsession-trust-bypass.ast', code: 'HEURISTICS_IOS_SECURITY_URLSESSION_TRUST_BYPASS_AST', message: 'AST heuristic detected URLSession trust challenge bypass in iOS production code.' },
|
|
815
816
|
{ platform: 'ios', pathCheck: isIOSInfoPlistPath, excludePaths: [], detect: TextIOS.hasSwiftInsecureTransportUsage, locateLines: TextIOS.collectSwiftInsecureTransportLines, primaryNode: (lines) => ({ kind: 'property', name: 'ATS NSAllowsArbitraryLoads enabled', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: ATS enabled with explicit HTTPS-only exceptions', lines }], why: 'NSAllowsArbitraryLoads disables the default iOS App Transport Security protection and should not be enabled for production builds.', impact: 'Permissive ATS configuration allows insecure transport globally, making networking violations harder to audit per endpoint.', expected_fix: 'Remove NSAllowsArbitraryLoads=true. Keep ATS enabled by default and add the narrowest documented domain exception only when the production requirement is approved.', ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected permissive App Transport Security configuration.' },
|
|
816
817
|
{ platform: 'ios', pathCheck: isIOSLocalizableStringsPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.localization.localizable-strings.ast', code: 'HEURISTICS_IOS_LOCALIZATION_LOCALIZABLE_STRINGS_AST', message: 'AST heuristic detected Localizable.strings usage; String Catalogs (.xcstrings) remain the preferred baseline for new localization work.' },
|
|
817
818
|
{ platform: 'ios', pathCheck: isIOSInterfaceBuilderPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.interface-builder.storyboard-xib.ast', code: 'HEURISTICS_IOS_INTERFACE_BUILDER_STORYBOARD_XIB_AST', message: 'AST heuristic detected Storyboard/XIB usage; programmatic SwiftUI/UIKit UI remains the preferred baseline for versionable iOS code.' },
|
|
@@ -895,6 +896,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
895
896
|
{ platform: 'android', pathCheck: isAndroidSourcePath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasAndroidAsyncTaskUsage, ruleId: 'heuristics.android.concurrency.asynctask.ast', code: 'HEURISTICS_ANDROID_CONCURRENCY_ASYNCTASK_AST', message: 'AST heuristic detected deprecated AsyncTask usage in Android production code; use coroutines.' },
|
|
896
897
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasAndroidLegacyFingerprintApiUsage, locateLines: TextAndroid.collectAndroidLegacyFingerprintApiLines, ruleId: 'heuristics.android.security.legacy-fingerprint-api.ast', code: 'HEURISTICS_ANDROID_SECURITY_LEGACY_FINGERPRINT_API_AST', message: 'AST heuristic detected legacy FingerprintManager API usage; use androidx.biometric.BiometricPrompt.' },
|
|
897
898
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasAndroidCustomSingletonObjectUsage, locateLines: TextAndroid.collectAndroidCustomSingletonObjectLines, primaryNode: (lines) => ({ kind: 'class', name: 'Kotlin object singleton', lines }), relatedNodes: (lines) => [{ kind: 'member', name: 'replacement: Hilt/Dagger dependency injection boundary', lines }], why: 'Kotlin object singletons create global mutable architecture boundaries that bypass the Android DI graph.', impact: 'Global repositories, services or managers make feature slices harder to test, override and isolate in brownfield remediation.', expected_fix: 'Replace custom Kotlin object singletons with constructor-injected classes provided by Hilt/Dagger. Keep object declarations only for constants, routes, UI metadata or DI modules.', ruleId: 'heuristics.android.architecture.custom-singleton-object.ast', code: 'HEURISTICS_ANDROID_ARCHITECTURE_CUSTOM_SINGLETON_OBJECT_AST', message: 'AST heuristic detected a custom Kotlin object singleton in Android production code; use Hilt/Dagger dependency injection.' },
|
|
899
|
+
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasAndroidHiltInjectionWithoutEntryPointUsage, locateLines: TextAndroid.collectAndroidHiltInjectionWithoutEntryPointLines, primaryNode: (lines) => ({ kind: 'class', name: 'Activity/Fragment using Hilt injection without @AndroidEntryPoint', lines }), relatedNodes: (lines) => [{ kind: 'member', name: 'replacement: annotate Activity/Fragment with @AndroidEntryPoint', lines }], why: 'Hilt field injection in Activity or Fragment requires the Android entry point annotation on the Android framework type.', impact: 'The app can compile but crash or fail injection at runtime because the generated Hilt component is not installed for that entry point.', expected_fix: 'Add @AndroidEntryPoint to the Activity or Fragment that declares @Inject fields, or remove field injection and use constructor/ViewModel boundaries where appropriate.', ruleId: 'heuristics.android.di.hilt-injection-without-entrypoint.ast', code: 'HEURISTICS_ANDROID_DI_HILT_INJECTION_WITHOUT_ENTRYPOINT_AST', message: 'AST heuristic detected Hilt field injection in Activity/Fragment without @AndroidEntryPoint.' },
|
|
898
900
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinGodActivityUsage, ruleId: 'heuristics.android.architecture.god-activity.ast', code: 'HEURISTICS_ANDROID_ARCHITECTURE_GOD_ACTIVITY_AST', message: 'AST heuristic detected an Android Activity mixing UI entrypoint with product responsibilities; keep Activity thin and move features to composables/ViewModels/use cases.' },
|
|
899
901
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinApplicationOnCreateHeavyInitializationUsage, ruleId: 'heuristics.android.startup.application-oncreate-heavy-init.ast', code: 'HEURISTICS_ANDROID_STARTUP_APPLICATION_ONCREATE_HEAVY_INIT_AST', message: 'AST heuristic detected heavy library initialization in Application.onCreate; move lazy startup work to AndroidX Startup Initializer or defer it behind the feature boundary.' },
|
|
900
902
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinNonLazyScrollableCollectionUsage, ruleId: 'heuristics.android.compose.non-lazy-scrollable-collection.ast', code: 'HEURISTICS_ANDROID_COMPOSE_NON_LAZY_SCROLLABLE_COLLECTION_AST', message: 'AST heuristic detected a scrollable Column/Row rendering a collection; use LazyColumn/LazyRow for virtualized lists.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { androidRules } from './android';
|
|
4
4
|
|
|
5
5
|
test('androidRules define reglas heurísticas locked para plataforma android', () => {
|
|
6
|
-
assert.equal(androidRules.length,
|
|
6
|
+
assert.equal(androidRules.length, 42);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -15,6 +15,7 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
15
15
|
'heuristics.android.flow.sharedflow-used-as-state.ast',
|
|
16
16
|
'heuristics.android.security.legacy-fingerprint-api.ast',
|
|
17
17
|
'heuristics.android.architecture.custom-singleton-object.ast',
|
|
18
|
+
'heuristics.android.di.hilt-injection-without-entrypoint.ast',
|
|
18
19
|
'heuristics.android.concurrency.asynctask.ast',
|
|
19
20
|
'heuristics.android.architecture.god-activity.ast',
|
|
20
21
|
'heuristics.android.startup.application-oncreate-heavy-init.ast',
|
|
@@ -93,6 +94,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
93
94
|
byId.get('heuristics.android.architecture.custom-singleton-object.ast')?.then.code,
|
|
94
95
|
'HEURISTICS_ANDROID_ARCHITECTURE_CUSTOM_SINGLETON_OBJECT_AST'
|
|
95
96
|
);
|
|
97
|
+
assert.equal(
|
|
98
|
+
byId.get('heuristics.android.di.hilt-injection-without-entrypoint.ast')?.then.code,
|
|
99
|
+
'HEURISTICS_ANDROID_DI_HILT_INJECTION_WITHOUT_ENTRYPOINT_AST'
|
|
100
|
+
);
|
|
96
101
|
assert.equal(
|
|
97
102
|
byId.get('heuristics.android.concurrency.asynctask.ast')?.then.code,
|
|
98
103
|
'HEURISTICS_ANDROID_CONCURRENCY_ASYNCTASK_AST'
|
|
@@ -155,6 +155,26 @@ export const androidRules: RuleSet = [
|
|
|
155
155
|
code: 'HEURISTICS_ANDROID_ARCHITECTURE_CUSTOM_SINGLETON_OBJECT_AST',
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
id: 'heuristics.android.di.hilt-injection-without-entrypoint.ast',
|
|
160
|
+
description:
|
|
161
|
+
'Detects Activity or Fragment Hilt field injection without @AndroidEntryPoint.',
|
|
162
|
+
severity: 'WARN',
|
|
163
|
+
platform: 'android',
|
|
164
|
+
locked: true,
|
|
165
|
+
when: {
|
|
166
|
+
kind: 'Heuristic',
|
|
167
|
+
where: {
|
|
168
|
+
ruleId: 'heuristics.android.di.hilt-injection-without-entrypoint.ast',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
then: {
|
|
172
|
+
kind: 'Finding',
|
|
173
|
+
message:
|
|
174
|
+
'AST heuristic detected Hilt field injection in Activity/Fragment without @AndroidEntryPoint.',
|
|
175
|
+
code: 'HEURISTICS_ANDROID_DI_HILT_INJECTION_WITHOUT_ENTRYPOINT_AST',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
158
178
|
{
|
|
159
179
|
id: 'heuristics.android.concurrency.asynctask.ast',
|
|
160
180
|
description:
|
|
@@ -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, 137);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -69,6 +69,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
69
69
|
'heuristics.ios.uikit.cell-without-reuse.ast',
|
|
70
70
|
'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
71
71
|
'heuristics.ios.security.insecure-transport.ast',
|
|
72
|
+
'heuristics.ios.security.urlsession-trust-bypass.ast',
|
|
72
73
|
'heuristics.ios.localization.localizable-strings.ast',
|
|
73
74
|
'heuristics.ios.interface-builder.storyboard-xib.ast',
|
|
74
75
|
'heuristics.ios.localization.hardcoded-ui-string.ast',
|
|
@@ -1145,6 +1145,24 @@ export const iosRules: RuleSet = [
|
|
|
1145
1145
|
code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST',
|
|
1146
1146
|
},
|
|
1147
1147
|
},
|
|
1148
|
+
{
|
|
1149
|
+
id: 'heuristics.ios.security.urlsession-trust-bypass.ast',
|
|
1150
|
+
description: 'Detects URLSessionDelegate trust challenge handlers that accept server trust without validation.',
|
|
1151
|
+
severity: 'WARN',
|
|
1152
|
+
platform: 'ios',
|
|
1153
|
+
locked: true,
|
|
1154
|
+
when: {
|
|
1155
|
+
kind: 'Heuristic',
|
|
1156
|
+
where: {
|
|
1157
|
+
ruleId: 'heuristics.ios.security.urlsession-trust-bypass.ast',
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
then: {
|
|
1161
|
+
kind: 'Finding',
|
|
1162
|
+
message: 'AST heuristic detected URLSession trust challenge bypass in iOS production code.',
|
|
1163
|
+
code: 'HEURISTICS_IOS_SECURITY_URLSESSION_TRUST_BYPASS_AST',
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1148
1166
|
{
|
|
1149
1167
|
id: 'heuristics.ios.localization.localizable-strings.ast',
|
|
1150
1168
|
description: 'Detects legacy Localizable.strings files where String Catalogs are the preferred iOS baseline.',
|
|
@@ -304,6 +304,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
304
304
|
'ios.security.insecure-transport',
|
|
305
305
|
['heuristics.ios.security.insecure-transport.ast']
|
|
306
306
|
),
|
|
307
|
+
'skills.ios.guideline.ios.ssl-pinning-para-apps-con-alta-seguridad': heuristicDetector(
|
|
308
|
+
'ios.security.urlsession-trust-bypass',
|
|
309
|
+
['heuristics.ios.security.urlsession-trust-bypass.ast']
|
|
310
|
+
),
|
|
311
|
+
'skills.ios.guideline.ios.ssl-pinning-prevenir-man-in-the-middle': heuristicDetector(
|
|
312
|
+
'ios.security.urlsession-trust-bypass',
|
|
313
|
+
['heuristics.ios.security.urlsession-trust-bypass.ast']
|
|
314
|
+
),
|
|
307
315
|
'skills.ios.guideline.ios.localizable-strings-deprecado-usar-string-catalogs':
|
|
308
316
|
heuristicDetector('ios.localization.localizable-strings', [
|
|
309
317
|
'heuristics.ios.localization.localizable-strings.ast',
|
|
@@ -1443,6 +1451,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
1443
1451
|
'android.architecture.custom-singleton-object',
|
|
1444
1452
|
['heuristics.android.architecture.custom-singleton-object.ast']
|
|
1445
1453
|
),
|
|
1454
|
+
'skills.android.guideline.android.androidentrypoint-activity-fragment-viewmodel':
|
|
1455
|
+
heuristicDetector('android.di.hilt-injection-without-entrypoint', [
|
|
1456
|
+
'heuristics.android.di.hilt-injection-without-entrypoint.ast',
|
|
1457
|
+
]),
|
|
1446
1458
|
'skills.android.guideline.android.statein-convertir-cold-flow-a-hot-stateflow': heuristicDetector(
|
|
1447
1459
|
'android.flow.viewmodel-flow-without-statein',
|
|
1448
1460
|
['heuristics.android.flow.viewmodel-flow-without-statein.ast']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.363",
|
|
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": {
|