pumuki 6.3.270 → 6.3.271

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/VERSION +1 -1
  3. package/core/facts/detectors/text/android.test.ts +538 -0
  4. package/core/facts/detectors/text/android.ts +436 -0
  5. package/core/facts/detectors/text/ios.test.ts +328 -1
  6. package/core/facts/detectors/text/ios.ts +241 -0
  7. package/core/facts/detectors/typescript/index.test.ts +393 -0
  8. package/core/facts/detectors/typescript/index.ts +316 -0
  9. package/core/facts/extractHeuristicFacts.ts +70 -1
  10. package/core/rules/presets/heuristics/android.test.ts +91 -1
  11. package/core/rules/presets/heuristics/android.ts +360 -0
  12. package/core/rules/presets/heuristics/ios.test.ts +54 -1
  13. package/core/rules/presets/heuristics/ios.ts +243 -2
  14. package/core/rules/presets/heuristics/typescript.test.ts +50 -2
  15. package/core/rules/presets/heuristics/typescript.ts +162 -0
  16. package/docs/operations/RELEASE_NOTES.md +4 -0
  17. package/integrations/config/skillsDetectorRegistry.ts +501 -0
  18. package/integrations/config/skillsRuleClassification.ts +127 -3
  19. package/integrations/git/runPlatformGate.ts +4 -1
  20. package/integrations/lifecycle/preWriteAutomation.ts +1 -0
  21. package/integrations/lifecycle/preWriteLease.ts +41 -4
  22. package/package.json +1 -1
  23. package/scripts/classify-skills-rules.ts +2 -2
  24. package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
  25. package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
  26. package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
  27. package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
  28. package/scripts/framework-menu-gate-lib.ts +86 -1
  29. package/scripts/framework-menu-layout-data.ts +3 -3
  30. package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
  31. package/scripts/framework-menu.ts +10 -6
  32. package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
  33. package/scripts/package-install-smoke-lifecycle-lib.ts +19 -0
@@ -19,16 +19,25 @@ import {
19
19
  hasConsoleLogCall,
20
20
  hasDebuggerStatement,
21
21
  hasDeleteOperator,
22
+ hasDefaultExportedApiRouteHandler,
23
+ hasDirectNetworkCall,
22
24
  hasEmptyCatchClause,
23
25
  hasEvalCall,
24
26
  hasExplicitAnyType,
25
27
  hasFrameworkDependencyImport,
26
28
  hasFunctionConstructorUsage,
29
+ hasInterpolatedUnsafeSqlCall,
30
+ hasNestedCallbackUsage,
31
+ hasNestedIfElseStatement,
32
+ hasNonSemanticClickableJsx,
33
+ hasSensitiveTokenInUrl,
34
+ hasWeakBcryptSaltRounds,
27
35
  hasNetworkCallWithoutErrorHandling,
28
36
  hasMixedCommandQueryClass,
29
37
  hasMixedCommandQueryInterface,
30
38
  hasUnknownWithoutGuard,
31
39
  hasRecordStringUnknownType,
40
+ hasRedundantAriaRoleOnSemanticJsx,
32
41
  hasOverrideMethodThrowingNotImplemented,
33
42
  hasLargeClassDeclaration,
34
43
  hasSetIntervalStringCallback,
@@ -239,6 +248,390 @@ test('hasAsyncPromiseExecutor detecta ejecutor async en new Promise', () => {
239
248
  assert.equal(hasAsyncPromiseExecutor(syncExecutorAst), false);
240
249
  });
241
250
 
251
+ test('hasWeakBcryptSaltRounds detecta bcrypt con salt rounds inferiores a 10', () => {
252
+ const weakHashAst = {
253
+ type: 'CallExpression',
254
+ callee: {
255
+ type: 'MemberExpression',
256
+ object: { type: 'Identifier', name: 'bcrypt' },
257
+ property: { type: 'Identifier', name: 'hash' },
258
+ },
259
+ arguments: [
260
+ { type: 'Identifier', name: 'password' },
261
+ { type: 'NumericLiteral', value: 8 },
262
+ ],
263
+ };
264
+ const strongHashAst = {
265
+ type: 'CallExpression',
266
+ callee: {
267
+ type: 'MemberExpression',
268
+ object: { type: 'Identifier', name: 'bcrypt' },
269
+ property: { type: 'Identifier', name: 'hashSync' },
270
+ },
271
+ arguments: [
272
+ { type: 'Identifier', name: 'password' },
273
+ { type: 'NumericLiteral', value: 10 },
274
+ ],
275
+ };
276
+ const configuredRoundsAst = {
277
+ type: 'CallExpression',
278
+ callee: {
279
+ type: 'MemberExpression',
280
+ object: { type: 'Identifier', name: 'bcrypt' },
281
+ property: { type: 'Identifier', name: 'hash' },
282
+ },
283
+ arguments: [
284
+ { type: 'Identifier', name: 'password' },
285
+ { type: 'Identifier', name: 'saltRounds' },
286
+ ],
287
+ };
288
+
289
+ assert.equal(hasWeakBcryptSaltRounds(weakHashAst), true);
290
+ assert.equal(hasWeakBcryptSaltRounds(strongHashAst), false);
291
+ assert.equal(hasWeakBcryptSaltRounds(configuredRoundsAst), false);
292
+ });
293
+
294
+ test('hasInterpolatedUnsafeSqlCall detecta SQL interpolado en llamadas no parametrizadas', () => {
295
+ const unsafeQueryAst = {
296
+ type: 'CallExpression',
297
+ callee: {
298
+ type: 'MemberExpression',
299
+ object: { type: 'Identifier', name: 'db' },
300
+ property: { type: 'Identifier', name: 'query' },
301
+ },
302
+ arguments: [
303
+ {
304
+ type: 'TemplateLiteral',
305
+ expressions: [{ type: 'Identifier', name: 'userId' }],
306
+ quasis: [],
307
+ },
308
+ ],
309
+ };
310
+ const unsafePrismaAst = {
311
+ type: 'CallExpression',
312
+ callee: {
313
+ type: 'MemberExpression',
314
+ object: { type: 'Identifier', name: 'prisma' },
315
+ property: { type: 'Identifier', name: '$queryRawUnsafe' },
316
+ },
317
+ arguments: [
318
+ {
319
+ type: 'TemplateLiteral',
320
+ expressions: [{ type: 'Identifier', name: 'email' }],
321
+ quasis: [],
322
+ },
323
+ ],
324
+ };
325
+ const staticQueryAst = {
326
+ type: 'CallExpression',
327
+ callee: {
328
+ type: 'MemberExpression',
329
+ object: { type: 'Identifier', name: 'db' },
330
+ property: { type: 'Identifier', name: 'query' },
331
+ },
332
+ arguments: [
333
+ {
334
+ type: 'TemplateLiteral',
335
+ expressions: [],
336
+ quasis: [],
337
+ },
338
+ ],
339
+ };
340
+ const taggedTemplateAst = {
341
+ type: 'TaggedTemplateExpression',
342
+ tag: {
343
+ type: 'MemberExpression',
344
+ object: { type: 'Identifier', name: 'prisma' },
345
+ property: { type: 'Identifier', name: '$queryRaw' },
346
+ },
347
+ quasi: {
348
+ type: 'TemplateLiteral',
349
+ expressions: [{ type: 'Identifier', name: 'userId' }],
350
+ quasis: [],
351
+ },
352
+ };
353
+
354
+ assert.equal(hasInterpolatedUnsafeSqlCall(unsafeQueryAst), true);
355
+ assert.equal(hasInterpolatedUnsafeSqlCall(unsafePrismaAst), true);
356
+ assert.equal(hasInterpolatedUnsafeSqlCall(staticQueryAst), false);
357
+ assert.equal(hasInterpolatedUnsafeSqlCall(taggedTemplateAst), false);
358
+ });
359
+
360
+ test('hasSensitiveTokenInUrl detecta tokens sensibles en URL de llamadas de red', () => {
361
+ const fetchTokenAst = {
362
+ type: 'CallExpression',
363
+ callee: { type: 'Identifier', name: 'fetch' },
364
+ arguments: [{ type: 'StringLiteral', value: '/api/me?access_token=abc' }],
365
+ };
366
+ const axiosTemplateTokenAst = {
367
+ type: 'CallExpression',
368
+ callee: {
369
+ type: 'MemberExpression',
370
+ object: { type: 'Identifier', name: 'axios' },
371
+ property: { type: 'Identifier', name: 'get' },
372
+ },
373
+ arguments: [
374
+ {
375
+ type: 'TemplateLiteral',
376
+ expressions: [{ type: 'Identifier', name: 'token' }],
377
+ quasis: [{ type: 'TemplateElement', value: { cooked: '/api/me?token=' } }],
378
+ },
379
+ ],
380
+ };
381
+ const headerTokenAst = {
382
+ type: 'CallExpression',
383
+ callee: { type: 'Identifier', name: 'fetch' },
384
+ arguments: [
385
+ { type: 'StringLiteral', value: '/api/me' },
386
+ {
387
+ type: 'ObjectExpression',
388
+ properties: [
389
+ {
390
+ type: 'ObjectProperty',
391
+ key: { type: 'Identifier', name: 'headers' },
392
+ value: { type: 'ObjectExpression', properties: [] },
393
+ },
394
+ ],
395
+ },
396
+ ],
397
+ };
398
+
399
+ assert.equal(hasSensitiveTokenInUrl(fetchTokenAst), true);
400
+ assert.equal(hasSensitiveTokenInUrl(axiosTemplateTokenAst), true);
401
+ assert.equal(hasSensitiveTokenInUrl(headerTokenAst), false);
402
+ });
403
+
404
+ test('hasDirectNetworkCall detecta fetch y axios directos', () => {
405
+ const fetchAst = {
406
+ type: 'CallExpression',
407
+ callee: { type: 'Identifier', name: 'fetch' },
408
+ arguments: [{ type: 'StringLiteral', value: '/api/stores' }],
409
+ };
410
+ const axiosAst = {
411
+ type: 'CallExpression',
412
+ callee: {
413
+ type: 'MemberExpression',
414
+ object: { type: 'Identifier', name: 'axios' },
415
+ property: { type: 'Identifier', name: 'get' },
416
+ },
417
+ arguments: [{ type: 'StringLiteral', value: '/api/stores' }],
418
+ };
419
+ const mswAst = {
420
+ type: 'CallExpression',
421
+ callee: {
422
+ type: 'MemberExpression',
423
+ object: { type: 'Identifier', name: 'server' },
424
+ property: { type: 'Identifier', name: 'use' },
425
+ },
426
+ arguments: [],
427
+ };
428
+
429
+ assert.equal(hasDirectNetworkCall(fetchAst), true);
430
+ assert.equal(hasDirectNetworkCall(axiosAst), true);
431
+ assert.equal(hasDirectNetworkCall(mswAst), false);
432
+ });
433
+
434
+ test('hasNonSemanticClickableJsx detecta elementos no semanticos clicables sin teclado', () => {
435
+ const clickableDivAst = {
436
+ type: 'JSXOpeningElement',
437
+ name: { type: 'JSXIdentifier', name: 'div' },
438
+ attributes: [
439
+ { type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onClick' } },
440
+ ],
441
+ };
442
+ const keyboardAccessibleDivAst = {
443
+ type: 'JSXOpeningElement',
444
+ name: { type: 'JSXIdentifier', name: 'div' },
445
+ attributes: [
446
+ { type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onClick' } },
447
+ { type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onKeyDown' } },
448
+ { type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'tabIndex' } },
449
+ ],
450
+ };
451
+ const buttonAst = {
452
+ type: 'JSXOpeningElement',
453
+ name: { type: 'JSXIdentifier', name: 'button' },
454
+ attributes: [
455
+ { type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onClick' } },
456
+ ],
457
+ };
458
+
459
+ assert.equal(hasNonSemanticClickableJsx(clickableDivAst), true);
460
+ assert.equal(hasNonSemanticClickableJsx(keyboardAccessibleDivAst), false);
461
+ assert.equal(hasNonSemanticClickableJsx(buttonAst), false);
462
+ });
463
+
464
+ test('hasRedundantAriaRoleOnSemanticJsx detecta roles ARIA redundantes en HTML semantico', () => {
465
+ const redundantButtonRoleAst = {
466
+ type: 'JSXOpeningElement',
467
+ name: { type: 'JSXIdentifier', name: 'button' },
468
+ attributes: [
469
+ {
470
+ type: 'JSXAttribute',
471
+ name: { type: 'JSXIdentifier', name: 'role' },
472
+ value: { type: 'StringLiteral', value: 'button' },
473
+ },
474
+ ],
475
+ };
476
+ const expressionRoleAst = {
477
+ type: 'JSXOpeningElement',
478
+ name: { type: 'JSXIdentifier', name: 'nav' },
479
+ attributes: [
480
+ {
481
+ type: 'JSXAttribute',
482
+ name: { type: 'JSXIdentifier', name: 'role' },
483
+ value: {
484
+ type: 'JSXExpressionContainer',
485
+ expression: { type: 'StringLiteral', value: 'navigation' },
486
+ },
487
+ },
488
+ ],
489
+ };
490
+ const necessaryRoleAst = {
491
+ type: 'JSXOpeningElement',
492
+ name: { type: 'JSXIdentifier', name: 'div' },
493
+ attributes: [
494
+ {
495
+ type: 'JSXAttribute',
496
+ name: { type: 'JSXIdentifier', name: 'role' },
497
+ value: { type: 'StringLiteral', value: 'dialog' },
498
+ },
499
+ ],
500
+ };
501
+
502
+ assert.equal(hasRedundantAriaRoleOnSemanticJsx(redundantButtonRoleAst), true);
503
+ assert.equal(hasRedundantAriaRoleOnSemanticJsx(expressionRoleAst), true);
504
+ assert.equal(hasRedundantAriaRoleOnSemanticJsx(necessaryRoleAst), false);
505
+ });
506
+
507
+ test('hasNestedCallbackUsage detecta callback anidado en llamadas', () => {
508
+ const ast = {
509
+ type: 'Program',
510
+ body: [
511
+ {
512
+ type: 'CallExpression',
513
+ callee: { type: 'Identifier', name: 'loadUser' },
514
+ arguments: [
515
+ {
516
+ type: 'ArrowFunctionExpression',
517
+ body: {
518
+ type: 'BlockStatement',
519
+ body: [
520
+ {
521
+ type: 'CallExpression',
522
+ callee: { type: 'Identifier', name: 'loadOrders' },
523
+ arguments: [
524
+ {
525
+ type: 'FunctionExpression',
526
+ body: { type: 'BlockStatement', body: [] },
527
+ },
528
+ ],
529
+ },
530
+ ],
531
+ },
532
+ },
533
+ ],
534
+ },
535
+ ],
536
+ };
537
+
538
+ assert.equal(hasNestedCallbackUsage(ast), true);
539
+ });
540
+
541
+ test('hasNestedCallbackUsage permite callbacks planos y funciones internas no pasadas como callback', () => {
542
+ const flatCallbackAst = {
543
+ type: 'CallExpression',
544
+ callee: { type: 'Identifier', name: 'loadUser' },
545
+ arguments: [
546
+ {
547
+ type: 'ArrowFunctionExpression',
548
+ body: { type: 'BlockStatement', body: [] },
549
+ },
550
+ ],
551
+ };
552
+ const innerFunctionAst = {
553
+ type: 'ArrowFunctionExpression',
554
+ body: {
555
+ type: 'BlockStatement',
556
+ body: [
557
+ {
558
+ type: 'FunctionDeclaration',
559
+ id: { type: 'Identifier', name: 'helper' },
560
+ body: { type: 'BlockStatement', body: [] },
561
+ },
562
+ ],
563
+ },
564
+ };
565
+
566
+ assert.equal(hasNestedCallbackUsage(flatCallbackAst), false);
567
+ assert.equal(hasNestedCallbackUsage(innerFunctionAst), false);
568
+ });
569
+
570
+ test('hasNestedIfElseStatement detecta if else anidado y preserva early return', () => {
571
+ const nestedIfElseAst = {
572
+ type: 'IfStatement',
573
+ test: { type: 'Identifier', name: 'isReady' },
574
+ consequent: {
575
+ type: 'BlockStatement',
576
+ body: [
577
+ {
578
+ type: 'IfStatement',
579
+ test: { type: 'Identifier', name: 'hasPermission' },
580
+ consequent: { type: 'BlockStatement', body: [] },
581
+ alternate: null,
582
+ },
583
+ ],
584
+ },
585
+ alternate: { type: 'BlockStatement', body: [] },
586
+ };
587
+ const elseIfAst = {
588
+ type: 'IfStatement',
589
+ test: { type: 'Identifier', name: 'isReady' },
590
+ consequent: { type: 'BlockStatement', body: [] },
591
+ alternate: {
592
+ type: 'IfStatement',
593
+ test: { type: 'Identifier', name: 'isFallback' },
594
+ consequent: { type: 'BlockStatement', body: [] },
595
+ alternate: null,
596
+ },
597
+ };
598
+ const earlyReturnAst = {
599
+ type: 'IfStatement',
600
+ test: { type: 'Identifier', name: 'missing' },
601
+ consequent: {
602
+ type: 'BlockStatement',
603
+ body: [{ type: 'ReturnStatement' }],
604
+ },
605
+ alternate: null,
606
+ };
607
+
608
+ assert.equal(hasNestedIfElseStatement(nestedIfElseAst), true);
609
+ assert.equal(hasNestedIfElseStatement(elseIfAst), true);
610
+ assert.equal(hasNestedIfElseStatement(earlyReturnAst), false);
611
+ });
612
+
613
+ test('hasDefaultExportedApiRouteHandler detecta handlers default de pages api', () => {
614
+ const defaultFunctionAst = {
615
+ type: 'ExportDefaultDeclaration',
616
+ declaration: { type: 'FunctionDeclaration', id: { type: 'Identifier', name: 'handler' } },
617
+ };
618
+ const defaultIdentifierAst = {
619
+ type: 'ExportDefaultDeclaration',
620
+ declaration: { type: 'Identifier', name: 'handler' },
621
+ };
622
+ const namedRouteHandlerAst = {
623
+ type: 'ExportNamedDeclaration',
624
+ declaration: {
625
+ type: 'FunctionDeclaration',
626
+ id: { type: 'Identifier', name: 'GET' },
627
+ },
628
+ };
629
+
630
+ assert.equal(hasDefaultExportedApiRouteHandler(defaultFunctionAst), true);
631
+ assert.equal(hasDefaultExportedApiRouteHandler(defaultIdentifierAst), true);
632
+ assert.equal(hasDefaultExportedApiRouteHandler(namedRouteHandlerAst), false);
633
+ });
634
+
242
635
  test('hasWithStatement detecta with y hasDeleteOperator detecta operador delete', () => {
243
636
  const withAst = {
244
637
  type: 'Program',