pumuki 6.3.293 → 6.3.295
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/CHANGELOG.md +4 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/ios.test.ts +65 -0
- package/core/facts/detectors/text/ios.ts +66 -6
- package/core/facts/detectors/typescript/index.test.ts +1115 -94
- package/core/facts/detectors/typescript/index.ts +653 -1
- package/core/facts/extractHeuristicFacts.ts +33 -2
- package/core/rules/presets/heuristics/ios.test.ts +11 -1
- package/core/rules/presets/heuristics/ios.ts +36 -0
- package/core/rules/presets/heuristics/typescript.test.ts +73 -1
- package/core/rules/presets/heuristics/typescript.ts +264 -0
- package/integrations/config/skillsDetectorRegistry.ts +90 -0
- package/integrations/git/runPlatformGate.ts +41 -3
- package/package.json +1 -1
|
@@ -6,14 +6,27 @@ import {
|
|
|
6
6
|
findDtoPropertyWithoutValidationMatch,
|
|
7
7
|
findDtoNestedPropertyWithoutNestedValidationMatch,
|
|
8
8
|
findBackendControllerRouteWithoutGuardLines,
|
|
9
|
+
findBackendControllerRouteWithoutRolesLines,
|
|
10
|
+
findBackendEventHandlerWithoutOnEventLines,
|
|
11
|
+
findBackendAuthRouteWithoutThrottleLines,
|
|
9
12
|
findBackendPermissiveCorsConfigurationLines,
|
|
10
13
|
findBackendStringLiteralUnionEnumCandidateLines,
|
|
14
|
+
findBackendSensitiveCacheWriteLines,
|
|
11
15
|
findBackendHardDeleteWithoutSoftDeleteLines,
|
|
12
16
|
findBackendLogWithoutContextLines,
|
|
17
|
+
findBackendAuthResponseWithoutRefreshTokenLines,
|
|
18
|
+
findBackendProcessEnvDefaultFallbackLines,
|
|
19
|
+
findBackendDirectProcessEnvReadLines,
|
|
20
|
+
findBackendConfigModuleWithoutValidationLines,
|
|
13
21
|
findBackendInconsistentErrorResponseLines,
|
|
14
22
|
findBackendErrorPayloadWithSuccessStatusLines,
|
|
15
23
|
findBackendControllerEntityResponseLines,
|
|
24
|
+
findBackendControllerBusinessLogicMatch,
|
|
25
|
+
findBackendRepositoryBusinessLogicMatch,
|
|
16
26
|
findBackendRawThrowExpressionLines,
|
|
27
|
+
findBackendMissingGlobalValidationPipeLines,
|
|
28
|
+
findBackendMissingHelmetSecurityHeadersLines,
|
|
29
|
+
findBackendMissingCompressionMiddlewareLines,
|
|
17
30
|
findFrameworkDependencyImportMatch,
|
|
18
31
|
findMixedCommandQueryClassMatch,
|
|
19
32
|
findMixedCommandQueryInterfaceMatch,
|
|
@@ -22,6 +35,7 @@ import {
|
|
|
22
35
|
findNetworkCallWithoutErrorHandlingLines,
|
|
23
36
|
findNestJsConstructorDependencyWithoutDecoratorMatch,
|
|
24
37
|
findPersistenceMutationWithoutAuditEventMatch,
|
|
38
|
+
findDtoPropertyWithoutApiPropertyLines,
|
|
25
39
|
findRecordStringUnknownTypeLines,
|
|
26
40
|
findUndefinedInBaseTypeUnionLines,
|
|
27
41
|
findUnknownWithoutGuardLines,
|
|
@@ -35,15 +49,25 @@ import {
|
|
|
35
49
|
hasDefaultExportedApiRouteHandler,
|
|
36
50
|
hasDirectNetworkCall,
|
|
37
51
|
hasDtoPropertyWithoutValidation,
|
|
52
|
+
hasDtoPropertyWithoutApiProperty,
|
|
38
53
|
hasDtoNestedPropertyWithoutNestedValidation,
|
|
39
54
|
hasBackendControllerRouteWithoutGuard,
|
|
55
|
+
hasBackendControllerRouteWithoutRoles,
|
|
56
|
+
hasBackendEventHandlerWithoutOnEvent,
|
|
57
|
+
hasBackendAuthRouteWithoutThrottle,
|
|
40
58
|
hasBackendPermissiveCorsConfiguration,
|
|
41
59
|
hasBackendStringLiteralUnionEnumCandidate,
|
|
60
|
+
hasBackendSensitiveCacheWrite,
|
|
42
61
|
hasBackendHardDeleteWithoutSoftDelete,
|
|
43
62
|
hasBackendInconsistentErrorResponse,
|
|
44
63
|
hasBackendErrorPayloadWithSuccessStatus,
|
|
45
64
|
hasBackendControllerEntityResponse,
|
|
65
|
+
hasBackendControllerBusinessLogic,
|
|
66
|
+
hasBackendRepositoryBusinessLogic,
|
|
46
67
|
hasBackendRawThrowExpression,
|
|
68
|
+
hasBackendMissingGlobalValidationPipe,
|
|
69
|
+
hasBackendMissingHelmetSecurityHeaders,
|
|
70
|
+
hasBackendMissingCompressionMiddleware,
|
|
47
71
|
hasEmptyCatchClause,
|
|
48
72
|
hasEvalCall,
|
|
49
73
|
hasExplicitAnyType,
|
|
@@ -54,6 +78,10 @@ import {
|
|
|
54
78
|
hasNestedIfElseStatement,
|
|
55
79
|
hasNestJsConstructorDependencyWithoutDecorator,
|
|
56
80
|
hasBackendLogWithoutContext,
|
|
81
|
+
hasBackendAuthResponseWithoutRefreshToken,
|
|
82
|
+
hasBackendProcessEnvDefaultFallback,
|
|
83
|
+
hasBackendDirectProcessEnvRead,
|
|
84
|
+
hasBackendConfigModuleWithoutValidation,
|
|
57
85
|
hasBackendMagicNumberLiteral,
|
|
58
86
|
hasBackendGenericErrorThrow,
|
|
59
87
|
hasBackendProductionMockOrSpy,
|
|
@@ -307,6 +335,237 @@ test('hasBackendLogWithoutContext detecta logs backend sin request user o trace
|
|
|
307
335
|
assert.equal(hasBackendLogWithoutContext(contextualLoggerAst), false);
|
|
308
336
|
});
|
|
309
337
|
|
|
338
|
+
test('hasBackendAuthResponseWithoutRefreshToken detecta respuestas auth solo con access token', () => {
|
|
339
|
+
const accessOnlyResponseAst = {
|
|
340
|
+
type: 'Program',
|
|
341
|
+
body: [
|
|
342
|
+
{
|
|
343
|
+
type: 'ReturnStatement',
|
|
344
|
+
argument: {
|
|
345
|
+
type: 'ObjectExpression',
|
|
346
|
+
loc: { start: { line: 16 }, end: { line: 19 } },
|
|
347
|
+
properties: [
|
|
348
|
+
{
|
|
349
|
+
type: 'ObjectProperty',
|
|
350
|
+
key: { type: 'Identifier', name: 'accessToken' },
|
|
351
|
+
value: { type: 'Identifier', name: 'accessToken' },
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
const refreshTokenResponseAst = {
|
|
359
|
+
type: 'Program',
|
|
360
|
+
body: [
|
|
361
|
+
{
|
|
362
|
+
type: 'ReturnStatement',
|
|
363
|
+
argument: {
|
|
364
|
+
type: 'ObjectExpression',
|
|
365
|
+
loc: { start: { line: 27 }, end: { line: 31 } },
|
|
366
|
+
properties: [
|
|
367
|
+
{
|
|
368
|
+
type: 'ObjectProperty',
|
|
369
|
+
key: { type: 'Identifier', name: 'accessToken' },
|
|
370
|
+
value: { type: 'Identifier', name: 'accessToken' },
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
type: 'ObjectProperty',
|
|
374
|
+
key: { type: 'Identifier', name: 'refreshToken' },
|
|
375
|
+
value: { type: 'Identifier', name: 'refreshToken' },
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
assert.equal(hasBackendAuthResponseWithoutRefreshToken(accessOnlyResponseAst), true);
|
|
384
|
+
assert.deepEqual(findBackendAuthResponseWithoutRefreshTokenLines(accessOnlyResponseAst), [16]);
|
|
385
|
+
assert.equal(hasBackendAuthResponseWithoutRefreshToken(refreshTokenResponseAst), false);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('hasBackendProcessEnvDefaultFallback detecta defaults literales en process.env', () => {
|
|
389
|
+
const envFallbackAst = {
|
|
390
|
+
type: 'Program',
|
|
391
|
+
body: [
|
|
392
|
+
{
|
|
393
|
+
type: 'VariableDeclaration',
|
|
394
|
+
declarations: [
|
|
395
|
+
{
|
|
396
|
+
type: 'VariableDeclarator',
|
|
397
|
+
id: { type: 'Identifier', name: 'jwtSecret' },
|
|
398
|
+
init: {
|
|
399
|
+
type: 'LogicalExpression',
|
|
400
|
+
operator: '||',
|
|
401
|
+
loc: { start: { line: 6 }, end: { line: 6 } },
|
|
402
|
+
left: {
|
|
403
|
+
type: 'MemberExpression',
|
|
404
|
+
object: {
|
|
405
|
+
type: 'MemberExpression',
|
|
406
|
+
object: { type: 'Identifier', name: 'process' },
|
|
407
|
+
property: { type: 'Identifier', name: 'env' },
|
|
408
|
+
},
|
|
409
|
+
property: { type: 'Identifier', name: 'JWT_SECRET' },
|
|
410
|
+
},
|
|
411
|
+
right: { type: 'StringLiteral', value: 'dev-secret' },
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
type: 'VariableDeclarator',
|
|
416
|
+
id: { type: 'Identifier', name: 'port' },
|
|
417
|
+
init: {
|
|
418
|
+
type: 'LogicalExpression',
|
|
419
|
+
operator: '??',
|
|
420
|
+
loc: { start: { line: 9 }, end: { line: 9 } },
|
|
421
|
+
left: {
|
|
422
|
+
type: 'MemberExpression',
|
|
423
|
+
object: {
|
|
424
|
+
type: 'MemberExpression',
|
|
425
|
+
object: { type: 'Identifier', name: 'process' },
|
|
426
|
+
property: { type: 'Identifier', name: 'env' },
|
|
427
|
+
},
|
|
428
|
+
property: { type: 'Identifier', name: 'PORT' },
|
|
429
|
+
},
|
|
430
|
+
right: { type: 'NumericLiteral', value: 3000 },
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
const strictEnvAst = {
|
|
438
|
+
type: 'Program',
|
|
439
|
+
body: [
|
|
440
|
+
{
|
|
441
|
+
type: 'VariableDeclaration',
|
|
442
|
+
declarations: [
|
|
443
|
+
{
|
|
444
|
+
type: 'VariableDeclarator',
|
|
445
|
+
id: { type: 'Identifier', name: 'jwtSecret' },
|
|
446
|
+
init: {
|
|
447
|
+
type: 'CallExpression',
|
|
448
|
+
callee: { type: 'Identifier', name: 'requiredEnv' },
|
|
449
|
+
arguments: [
|
|
450
|
+
{
|
|
451
|
+
type: 'MemberExpression',
|
|
452
|
+
object: {
|
|
453
|
+
type: 'MemberExpression',
|
|
454
|
+
object: { type: 'Identifier', name: 'process' },
|
|
455
|
+
property: { type: 'Identifier', name: 'env' },
|
|
456
|
+
},
|
|
457
|
+
property: { type: 'Identifier', name: 'JWT_SECRET' },
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
assert.equal(hasBackendProcessEnvDefaultFallback(envFallbackAst), true);
|
|
468
|
+
assert.deepEqual(findBackendProcessEnvDefaultFallbackLines(envFallbackAst), [6, 9]);
|
|
469
|
+
assert.equal(hasBackendProcessEnvDefaultFallback(strictEnvAst), false);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test('hasBackendDirectProcessEnvRead detecta lecturas directas de process.env', () => {
|
|
473
|
+
const directEnvAst = {
|
|
474
|
+
type: 'Program',
|
|
475
|
+
body: [
|
|
476
|
+
{
|
|
477
|
+
type: 'VariableDeclaration',
|
|
478
|
+
declarations: [
|
|
479
|
+
{
|
|
480
|
+
type: 'VariableDeclarator',
|
|
481
|
+
id: { type: 'Identifier', name: 'databaseUrl' },
|
|
482
|
+
init: {
|
|
483
|
+
type: 'MemberExpression',
|
|
484
|
+
loc: { start: { line: 6 }, end: { line: 6 } },
|
|
485
|
+
object: {
|
|
486
|
+
type: 'MemberExpression',
|
|
487
|
+
object: { type: 'Identifier', name: 'process' },
|
|
488
|
+
property: { type: 'Identifier', name: 'env' },
|
|
489
|
+
},
|
|
490
|
+
property: { type: 'Identifier', name: 'DATABASE_URL' },
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
};
|
|
497
|
+
const configServiceAst = {
|
|
498
|
+
type: 'CallExpression',
|
|
499
|
+
callee: {
|
|
500
|
+
type: 'MemberExpression',
|
|
501
|
+
object: { type: 'Identifier', name: 'configService' },
|
|
502
|
+
property: { type: 'Identifier', name: 'getOrThrow' },
|
|
503
|
+
},
|
|
504
|
+
arguments: [{ type: 'StringLiteral', value: 'DATABASE_URL' }],
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
assert.equal(hasBackendDirectProcessEnvRead(directEnvAst), true);
|
|
508
|
+
assert.deepEqual(findBackendDirectProcessEnvReadLines(directEnvAst), [6]);
|
|
509
|
+
assert.equal(hasBackendDirectProcessEnvRead(configServiceAst), false);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test('hasBackendConfigModuleWithoutValidation detecta ConfigModule sin validacion de env', () => {
|
|
513
|
+
const missingValidationAst = {
|
|
514
|
+
type: 'Program',
|
|
515
|
+
body: [
|
|
516
|
+
{
|
|
517
|
+
type: 'ExpressionStatement',
|
|
518
|
+
expression: {
|
|
519
|
+
type: 'CallExpression',
|
|
520
|
+
loc: { start: { line: 8 }, end: { line: 11 } },
|
|
521
|
+
callee: {
|
|
522
|
+
type: 'MemberExpression',
|
|
523
|
+
object: { type: 'Identifier', name: 'ConfigModule' },
|
|
524
|
+
property: { type: 'Identifier', name: 'forRoot' },
|
|
525
|
+
},
|
|
526
|
+
arguments: [
|
|
527
|
+
{
|
|
528
|
+
type: 'ObjectExpression',
|
|
529
|
+
properties: [
|
|
530
|
+
{
|
|
531
|
+
type: 'ObjectProperty',
|
|
532
|
+
key: { type: 'Identifier', name: 'isGlobal' },
|
|
533
|
+
value: { type: 'BooleanLiteral', value: true },
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
const validatedAst = {
|
|
543
|
+
type: 'CallExpression',
|
|
544
|
+
loc: { start: { line: 18 }, end: { line: 21 } },
|
|
545
|
+
callee: {
|
|
546
|
+
type: 'MemberExpression',
|
|
547
|
+
object: { type: 'Identifier', name: 'ConfigModule' },
|
|
548
|
+
property: { type: 'Identifier', name: 'forRoot' },
|
|
549
|
+
},
|
|
550
|
+
arguments: [
|
|
551
|
+
{
|
|
552
|
+
type: 'ObjectExpression',
|
|
553
|
+
properties: [
|
|
554
|
+
{
|
|
555
|
+
type: 'ObjectProperty',
|
|
556
|
+
key: { type: 'Identifier', name: 'validationSchema' },
|
|
557
|
+
value: { type: 'Identifier', name: 'envSchema' },
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
assert.equal(hasBackendConfigModuleWithoutValidation(missingValidationAst), true);
|
|
565
|
+
assert.deepEqual(findBackendConfigModuleWithoutValidationLines(missingValidationAst), [8]);
|
|
566
|
+
assert.equal(hasBackendConfigModuleWithoutValidation(validatedAst), false);
|
|
567
|
+
});
|
|
568
|
+
|
|
310
569
|
test('hasBackendMagicNumberLiteral detecta numeros inline y permite constantes nombradas', () => {
|
|
311
570
|
const inlineMagicNumberAst = {
|
|
312
571
|
type: 'ReturnStatement',
|
|
@@ -715,87 +974,280 @@ test('hasBackendControllerEntityResponse detecta entidades expuestas por control
|
|
|
715
974
|
assert.equal(hasBackendControllerEntityResponse(dtoReturnTypeAst), false);
|
|
716
975
|
});
|
|
717
976
|
|
|
718
|
-
test('
|
|
719
|
-
const
|
|
720
|
-
type: 'ClassDeclaration',
|
|
721
|
-
id: { type: 'Identifier', name: 'OrderEntity' },
|
|
722
|
-
body: {
|
|
723
|
-
type: 'ClassBody',
|
|
724
|
-
body: [
|
|
725
|
-
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'id' } },
|
|
726
|
-
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'status' } },
|
|
727
|
-
{
|
|
728
|
-
type: 'ClassMethod',
|
|
729
|
-
kind: 'method',
|
|
730
|
-
key: { type: 'Identifier', name: 'getStatus' },
|
|
731
|
-
body: { type: 'BlockStatement', body: [] },
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
type: 'ClassMethod',
|
|
735
|
-
kind: 'method',
|
|
736
|
-
key: { type: 'Identifier', name: 'setStatus' },
|
|
737
|
-
body: { type: 'BlockStatement', body: [] },
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
},
|
|
741
|
-
};
|
|
742
|
-
const richEntityAst = {
|
|
743
|
-
type: 'ClassDeclaration',
|
|
744
|
-
id: { type: 'Identifier', name: 'OrderEntity' },
|
|
745
|
-
body: {
|
|
746
|
-
type: 'ClassBody',
|
|
747
|
-
body: [
|
|
748
|
-
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'id' } },
|
|
749
|
-
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'status' } },
|
|
750
|
-
{
|
|
751
|
-
type: 'ClassMethod',
|
|
752
|
-
kind: 'method',
|
|
753
|
-
key: { type: 'Identifier', name: 'markAsPaid' },
|
|
754
|
-
body: { type: 'BlockStatement', body: [] },
|
|
755
|
-
},
|
|
756
|
-
],
|
|
757
|
-
},
|
|
758
|
-
};
|
|
759
|
-
|
|
760
|
-
assert.equal(hasBackendAnemicDomainModel(anemicEntityAst), true);
|
|
761
|
-
assert.equal(hasBackendAnemicDomainModel(richEntityAst), false);
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
test('hasBackendPermissiveCorsConfiguration detecta CORS permisivo y permite origenes acotados', () => {
|
|
765
|
-
const permissiveAst = {
|
|
977
|
+
test('hasBackendControllerBusinessLogic detecta logica dentro de rutas controller NestJS', () => {
|
|
978
|
+
const controllerWithLogicAst = {
|
|
766
979
|
type: 'Program',
|
|
767
980
|
body: [
|
|
768
981
|
{
|
|
769
|
-
type: '
|
|
770
|
-
|
|
771
|
-
callee: {
|
|
772
|
-
type: 'MemberExpression',
|
|
773
|
-
computed: false,
|
|
774
|
-
object: { type: 'Identifier', name: 'app' },
|
|
775
|
-
property: { type: 'Identifier', name: 'enableCors' },
|
|
776
|
-
},
|
|
777
|
-
arguments: [],
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
type: 'CallExpression',
|
|
781
|
-
loc: { start: { line: 7 }, end: { line: 7 } },
|
|
782
|
-
callee: {
|
|
783
|
-
type: 'MemberExpression',
|
|
784
|
-
computed: false,
|
|
785
|
-
object: { type: 'Identifier', name: 'NestFactory' },
|
|
786
|
-
property: { type: 'Identifier', name: 'create' },
|
|
787
|
-
},
|
|
788
|
-
arguments: [
|
|
789
|
-
{ type: 'Identifier', name: 'AppModule' },
|
|
982
|
+
type: 'ClassDeclaration',
|
|
983
|
+
decorators: [
|
|
790
984
|
{
|
|
791
|
-
type: '
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
985
|
+
type: 'Decorator',
|
|
986
|
+
expression: {
|
|
987
|
+
type: 'CallExpression',
|
|
988
|
+
callee: { type: 'Identifier', name: 'Controller' },
|
|
989
|
+
arguments: [],
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
body: {
|
|
994
|
+
type: 'ClassBody',
|
|
995
|
+
body: [
|
|
996
|
+
{
|
|
997
|
+
type: 'ClassMethod',
|
|
998
|
+
key: { type: 'Identifier', name: 'createOrder' },
|
|
999
|
+
loc: { start: { line: 18 }, end: { line: 30 } },
|
|
1000
|
+
decorators: [
|
|
1001
|
+
{
|
|
1002
|
+
type: 'Decorator',
|
|
1003
|
+
expression: {
|
|
1004
|
+
type: 'CallExpression',
|
|
1005
|
+
callee: { type: 'Identifier', name: 'Post' },
|
|
1006
|
+
arguments: [],
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
],
|
|
1010
|
+
body: {
|
|
1011
|
+
type: 'BlockStatement',
|
|
1012
|
+
body: [
|
|
1013
|
+
{
|
|
1014
|
+
type: 'IfStatement',
|
|
1015
|
+
test: { type: 'Identifier', name: 'invalid' },
|
|
1016
|
+
consequent: { type: 'BlockStatement', body: [] },
|
|
1017
|
+
},
|
|
1018
|
+
],
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
],
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
],
|
|
1025
|
+
};
|
|
1026
|
+
const thinControllerAst = {
|
|
1027
|
+
type: 'Program',
|
|
1028
|
+
body: [
|
|
1029
|
+
{
|
|
1030
|
+
type: 'ClassDeclaration',
|
|
1031
|
+
decorators: [
|
|
1032
|
+
{
|
|
1033
|
+
type: 'Decorator',
|
|
1034
|
+
expression: {
|
|
1035
|
+
type: 'CallExpression',
|
|
1036
|
+
callee: { type: 'Identifier', name: 'Controller' },
|
|
1037
|
+
arguments: [],
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
],
|
|
1041
|
+
body: {
|
|
1042
|
+
type: 'ClassBody',
|
|
1043
|
+
body: [
|
|
1044
|
+
{
|
|
1045
|
+
type: 'ClassMethod',
|
|
1046
|
+
key: { type: 'Identifier', name: 'createOrder' },
|
|
1047
|
+
decorators: [
|
|
1048
|
+
{
|
|
1049
|
+
type: 'Decorator',
|
|
1050
|
+
expression: {
|
|
1051
|
+
type: 'CallExpression',
|
|
1052
|
+
callee: { type: 'Identifier', name: 'Post' },
|
|
1053
|
+
arguments: [],
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1056
|
+
],
|
|
1057
|
+
body: {
|
|
1058
|
+
type: 'BlockStatement',
|
|
1059
|
+
body: [
|
|
1060
|
+
{
|
|
1061
|
+
type: 'ReturnStatement',
|
|
1062
|
+
argument: {
|
|
1063
|
+
type: 'CallExpression',
|
|
1064
|
+
callee: {
|
|
1065
|
+
type: 'MemberExpression',
|
|
1066
|
+
object: {
|
|
1067
|
+
type: 'MemberExpression',
|
|
1068
|
+
object: { type: 'ThisExpression' },
|
|
1069
|
+
property: { type: 'Identifier', name: 'ordersService' },
|
|
1070
|
+
},
|
|
1071
|
+
property: { type: 'Identifier', name: 'create' },
|
|
1072
|
+
},
|
|
1073
|
+
arguments: [],
|
|
1074
|
+
},
|
|
1075
|
+
},
|
|
1076
|
+
],
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
],
|
|
1080
|
+
},
|
|
1081
|
+
},
|
|
1082
|
+
],
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
assert.equal(hasBackendControllerBusinessLogic(controllerWithLogicAst), true);
|
|
1086
|
+
assert.equal(hasBackendControllerBusinessLogic(thinControllerAst), false);
|
|
1087
|
+
|
|
1088
|
+
const match = findBackendControllerBusinessLogicMatch(controllerWithLogicAst);
|
|
1089
|
+
assert.ok(match);
|
|
1090
|
+
assert.deepEqual(match.lines, [18]);
|
|
1091
|
+
assert.equal(match.primary_node, 'createOrder');
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test('hasBackendRepositoryBusinessLogic detecta decisiones de negocio dentro de repositories', () => {
|
|
1095
|
+
const repositoryWithLogicAst = {
|
|
1096
|
+
type: 'Program',
|
|
1097
|
+
body: [
|
|
1098
|
+
{
|
|
1099
|
+
type: 'ClassDeclaration',
|
|
1100
|
+
id: { type: 'Identifier', name: 'OrdersRepository' },
|
|
1101
|
+
body: {
|
|
1102
|
+
type: 'ClassBody',
|
|
1103
|
+
body: [
|
|
1104
|
+
{
|
|
1105
|
+
type: 'ClassMethod',
|
|
1106
|
+
key: { type: 'Identifier', name: 'saveOrder' },
|
|
1107
|
+
loc: { start: { line: 27 }, end: { line: 35 } },
|
|
1108
|
+
body: {
|
|
1109
|
+
type: 'BlockStatement',
|
|
1110
|
+
body: [
|
|
1111
|
+
{
|
|
1112
|
+
type: 'IfStatement',
|
|
1113
|
+
test: { type: 'Identifier', name: 'isPremium' },
|
|
1114
|
+
consequent: { type: 'BlockStatement', body: [] },
|
|
1115
|
+
},
|
|
1116
|
+
],
|
|
1117
|
+
},
|
|
1118
|
+
},
|
|
1119
|
+
],
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
],
|
|
1123
|
+
};
|
|
1124
|
+
const thinRepositoryAst = {
|
|
1125
|
+
type: 'Program',
|
|
1126
|
+
body: [
|
|
1127
|
+
{
|
|
1128
|
+
type: 'ClassDeclaration',
|
|
1129
|
+
id: { type: 'Identifier', name: 'OrdersRepository' },
|
|
1130
|
+
body: {
|
|
1131
|
+
type: 'ClassBody',
|
|
1132
|
+
body: [
|
|
1133
|
+
{
|
|
1134
|
+
type: 'ClassMethod',
|
|
1135
|
+
key: { type: 'Identifier', name: 'findActiveOrdersByUserId' },
|
|
1136
|
+
body: {
|
|
1137
|
+
type: 'BlockStatement',
|
|
1138
|
+
body: [
|
|
1139
|
+
{
|
|
1140
|
+
type: 'ReturnStatement',
|
|
1141
|
+
argument: {
|
|
1142
|
+
type: 'CallExpression',
|
|
1143
|
+
callee: {
|
|
1144
|
+
type: 'MemberExpression',
|
|
1145
|
+
object: { type: 'Identifier', name: 'queryBuilder' },
|
|
1146
|
+
property: { type: 'Identifier', name: 'where' },
|
|
1147
|
+
},
|
|
1148
|
+
arguments: [],
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
],
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
],
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
],
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
assert.equal(hasBackendRepositoryBusinessLogic(repositoryWithLogicAst), true);
|
|
1161
|
+
assert.equal(hasBackendRepositoryBusinessLogic(thinRepositoryAst), false);
|
|
1162
|
+
|
|
1163
|
+
const match = findBackendRepositoryBusinessLogicMatch(repositoryWithLogicAst);
|
|
1164
|
+
assert.ok(match);
|
|
1165
|
+
assert.deepEqual(match.lines, [27]);
|
|
1166
|
+
assert.equal(match.primary_node, 'OrdersRepository');
|
|
1167
|
+
assert.deepEqual(match.related_nodes, ['saveOrder']);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
test('hasBackendAnemicDomainModel detecta entidades de dominio anemicas', () => {
|
|
1171
|
+
const anemicEntityAst = {
|
|
1172
|
+
type: 'ClassDeclaration',
|
|
1173
|
+
id: { type: 'Identifier', name: 'OrderEntity' },
|
|
1174
|
+
body: {
|
|
1175
|
+
type: 'ClassBody',
|
|
1176
|
+
body: [
|
|
1177
|
+
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'id' } },
|
|
1178
|
+
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'status' } },
|
|
1179
|
+
{
|
|
1180
|
+
type: 'ClassMethod',
|
|
1181
|
+
kind: 'method',
|
|
1182
|
+
key: { type: 'Identifier', name: 'getStatus' },
|
|
1183
|
+
body: { type: 'BlockStatement', body: [] },
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
type: 'ClassMethod',
|
|
1187
|
+
kind: 'method',
|
|
1188
|
+
key: { type: 'Identifier', name: 'setStatus' },
|
|
1189
|
+
body: { type: 'BlockStatement', body: [] },
|
|
1190
|
+
},
|
|
1191
|
+
],
|
|
1192
|
+
},
|
|
1193
|
+
};
|
|
1194
|
+
const richEntityAst = {
|
|
1195
|
+
type: 'ClassDeclaration',
|
|
1196
|
+
id: { type: 'Identifier', name: 'OrderEntity' },
|
|
1197
|
+
body: {
|
|
1198
|
+
type: 'ClassBody',
|
|
1199
|
+
body: [
|
|
1200
|
+
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'id' } },
|
|
1201
|
+
{ type: 'ClassProperty', key: { type: 'Identifier', name: 'status' } },
|
|
1202
|
+
{
|
|
1203
|
+
type: 'ClassMethod',
|
|
1204
|
+
kind: 'method',
|
|
1205
|
+
key: { type: 'Identifier', name: 'markAsPaid' },
|
|
1206
|
+
body: { type: 'BlockStatement', body: [] },
|
|
1207
|
+
},
|
|
1208
|
+
],
|
|
1209
|
+
},
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
assert.equal(hasBackendAnemicDomainModel(anemicEntityAst), true);
|
|
1213
|
+
assert.equal(hasBackendAnemicDomainModel(richEntityAst), false);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
test('hasBackendPermissiveCorsConfiguration detecta CORS permisivo y permite origenes acotados', () => {
|
|
1217
|
+
const permissiveAst = {
|
|
1218
|
+
type: 'Program',
|
|
1219
|
+
body: [
|
|
1220
|
+
{
|
|
1221
|
+
type: 'CallExpression',
|
|
1222
|
+
loc: { start: { line: 3 }, end: { line: 3 } },
|
|
1223
|
+
callee: {
|
|
1224
|
+
type: 'MemberExpression',
|
|
1225
|
+
computed: false,
|
|
1226
|
+
object: { type: 'Identifier', name: 'app' },
|
|
1227
|
+
property: { type: 'Identifier', name: 'enableCors' },
|
|
1228
|
+
},
|
|
1229
|
+
arguments: [],
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
type: 'CallExpression',
|
|
1233
|
+
loc: { start: { line: 7 }, end: { line: 7 } },
|
|
1234
|
+
callee: {
|
|
1235
|
+
type: 'MemberExpression',
|
|
1236
|
+
computed: false,
|
|
1237
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
1238
|
+
property: { type: 'Identifier', name: 'create' },
|
|
1239
|
+
},
|
|
1240
|
+
arguments: [
|
|
1241
|
+
{ type: 'Identifier', name: 'AppModule' },
|
|
1242
|
+
{
|
|
1243
|
+
type: 'ObjectExpression',
|
|
1244
|
+
properties: [
|
|
1245
|
+
{
|
|
1246
|
+
type: 'ObjectProperty',
|
|
1247
|
+
key: { type: 'Identifier', name: 'cors' },
|
|
1248
|
+
value: { type: 'BooleanLiteral', value: true },
|
|
1249
|
+
},
|
|
1250
|
+
],
|
|
799
1251
|
},
|
|
800
1252
|
],
|
|
801
1253
|
},
|
|
@@ -891,6 +1343,55 @@ test('hasBackendStringLiteralUnionEnumCandidate detecta uniones de literales str
|
|
|
891
1343
|
assert.equal(hasBackendStringLiteralUnionEnumCandidate(openUnionAst), false);
|
|
892
1344
|
});
|
|
893
1345
|
|
|
1346
|
+
test('hasBackendSensitiveCacheWrite detecta datos sensibles escritos en cache', () => {
|
|
1347
|
+
const sensitiveCacheAst = {
|
|
1348
|
+
type: 'Program',
|
|
1349
|
+
body: [
|
|
1350
|
+
{
|
|
1351
|
+
type: 'ExpressionStatement',
|
|
1352
|
+
expression: {
|
|
1353
|
+
type: 'CallExpression',
|
|
1354
|
+
loc: { start: { line: 18 }, end: { line: 18 } },
|
|
1355
|
+
callee: {
|
|
1356
|
+
type: 'MemberExpression',
|
|
1357
|
+
object: { type: 'Identifier', name: 'cacheManager' },
|
|
1358
|
+
property: { type: 'Identifier', name: 'set' },
|
|
1359
|
+
},
|
|
1360
|
+
arguments: [
|
|
1361
|
+
{ type: 'StringLiteral', value: 'session:token' },
|
|
1362
|
+
{ type: 'Identifier', name: 'accessToken' },
|
|
1363
|
+
],
|
|
1364
|
+
},
|
|
1365
|
+
},
|
|
1366
|
+
],
|
|
1367
|
+
};
|
|
1368
|
+
const safeCacheAst = {
|
|
1369
|
+
type: 'Program',
|
|
1370
|
+
body: [
|
|
1371
|
+
{
|
|
1372
|
+
type: 'ExpressionStatement',
|
|
1373
|
+
expression: {
|
|
1374
|
+
type: 'CallExpression',
|
|
1375
|
+
loc: { start: { line: 27 }, end: { line: 27 } },
|
|
1376
|
+
callee: {
|
|
1377
|
+
type: 'MemberExpression',
|
|
1378
|
+
object: { type: 'Identifier', name: 'cacheManager' },
|
|
1379
|
+
property: { type: 'Identifier', name: 'set' },
|
|
1380
|
+
},
|
|
1381
|
+
arguments: [
|
|
1382
|
+
{ type: 'StringLiteral', value: 'products:list' },
|
|
1383
|
+
{ type: 'Identifier', name: 'products' },
|
|
1384
|
+
],
|
|
1385
|
+
},
|
|
1386
|
+
},
|
|
1387
|
+
],
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
assert.equal(hasBackendSensitiveCacheWrite(sensitiveCacheAst), true);
|
|
1391
|
+
assert.deepEqual(findBackendSensitiveCacheWriteLines(sensitiveCacheAst), [18]);
|
|
1392
|
+
assert.equal(hasBackendSensitiveCacheWrite(safeCacheAst), false);
|
|
1393
|
+
});
|
|
1394
|
+
|
|
894
1395
|
test('hasBackendHardDeleteWithoutSoftDelete detecta borrados fisicos en persistencia backend', () => {
|
|
895
1396
|
const hardDeleteAst = {
|
|
896
1397
|
type: 'Program',
|
|
@@ -2677,8 +3178,8 @@ test('hasDtoPropertyWithoutValidation detecta DTOs sin validacion class-validato
|
|
|
2677
3178
|
assert.deepEqual(match.related_nodes, ['email']);
|
|
2678
3179
|
});
|
|
2679
3180
|
|
|
2680
|
-
test('
|
|
2681
|
-
const
|
|
3181
|
+
test('hasDtoPropertyWithoutApiProperty detecta DTOs sin decoradores Swagger', () => {
|
|
3182
|
+
const missingSwaggerAst = {
|
|
2682
3183
|
type: 'Program',
|
|
2683
3184
|
body: [
|
|
2684
3185
|
{
|
|
@@ -2689,22 +3190,25 @@ test('hasDtoNestedPropertyWithoutNestedValidation detecta DTOs anidados sin Vali
|
|
|
2689
3190
|
body: [
|
|
2690
3191
|
{
|
|
2691
3192
|
type: 'ClassProperty',
|
|
2692
|
-
key: { type: 'Identifier', name: '
|
|
2693
|
-
loc: { start: { line:
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
3193
|
+
key: { type: 'Identifier', name: 'email' },
|
|
3194
|
+
loc: { start: { line: 7 }, end: { line: 7 } },
|
|
3195
|
+
decorators: [
|
|
3196
|
+
{
|
|
3197
|
+
type: 'Decorator',
|
|
3198
|
+
expression: {
|
|
3199
|
+
type: 'CallExpression',
|
|
3200
|
+
callee: { type: 'Identifier', name: 'IsEmail' },
|
|
3201
|
+
arguments: [],
|
|
3202
|
+
},
|
|
2699
3203
|
},
|
|
2700
|
-
|
|
3204
|
+
],
|
|
2701
3205
|
},
|
|
2702
3206
|
],
|
|
2703
3207
|
},
|
|
2704
3208
|
},
|
|
2705
3209
|
],
|
|
2706
3210
|
};
|
|
2707
|
-
const
|
|
3211
|
+
const swaggerDtoAst = {
|
|
2708
3212
|
type: 'Program',
|
|
2709
3213
|
body: [
|
|
2710
3214
|
{
|
|
@@ -2715,20 +3219,82 @@ test('hasDtoNestedPropertyWithoutNestedValidation detecta DTOs anidados sin Vali
|
|
|
2715
3219
|
body: [
|
|
2716
3220
|
{
|
|
2717
3221
|
type: 'ClassProperty',
|
|
2718
|
-
key: { type: 'Identifier', name: '
|
|
3222
|
+
key: { type: 'Identifier', name: 'email' },
|
|
3223
|
+
loc: { start: { line: 14 }, end: { line: 14 } },
|
|
2719
3224
|
decorators: [
|
|
2720
3225
|
{
|
|
2721
3226
|
type: 'Decorator',
|
|
2722
3227
|
expression: {
|
|
2723
3228
|
type: 'CallExpression',
|
|
2724
|
-
callee: { type: 'Identifier', name: '
|
|
3229
|
+
callee: { type: 'Identifier', name: 'ApiProperty' },
|
|
2725
3230
|
arguments: [],
|
|
2726
3231
|
},
|
|
2727
3232
|
},
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
3233
|
+
],
|
|
3234
|
+
},
|
|
3235
|
+
],
|
|
3236
|
+
},
|
|
3237
|
+
},
|
|
3238
|
+
],
|
|
3239
|
+
};
|
|
3240
|
+
|
|
3241
|
+
assert.equal(hasDtoPropertyWithoutApiProperty(missingSwaggerAst), true);
|
|
3242
|
+
assert.deepEqual(findDtoPropertyWithoutApiPropertyLines(missingSwaggerAst), [7]);
|
|
3243
|
+
assert.equal(hasDtoPropertyWithoutApiProperty(swaggerDtoAst), false);
|
|
3244
|
+
});
|
|
3245
|
+
|
|
3246
|
+
test('hasDtoNestedPropertyWithoutNestedValidation detecta DTOs anidados sin ValidateNested y Type', () => {
|
|
3247
|
+
const missingNestedValidationAst = {
|
|
3248
|
+
type: 'Program',
|
|
3249
|
+
body: [
|
|
3250
|
+
{
|
|
3251
|
+
type: 'ClassDeclaration',
|
|
3252
|
+
id: { type: 'Identifier', name: 'CreateOrderDto' },
|
|
3253
|
+
body: {
|
|
3254
|
+
type: 'ClassBody',
|
|
3255
|
+
body: [
|
|
3256
|
+
{
|
|
3257
|
+
type: 'ClassProperty',
|
|
3258
|
+
key: { type: 'Identifier', name: 'address' },
|
|
3259
|
+
loc: { start: { line: 9 }, end: { line: 9 } },
|
|
3260
|
+
typeAnnotation: {
|
|
3261
|
+
type: 'TSTypeAnnotation',
|
|
3262
|
+
typeAnnotation: {
|
|
3263
|
+
type: 'TSTypeReference',
|
|
3264
|
+
typeName: { type: 'Identifier', name: 'AddressDto' },
|
|
3265
|
+
},
|
|
3266
|
+
},
|
|
3267
|
+
},
|
|
3268
|
+
],
|
|
3269
|
+
},
|
|
3270
|
+
},
|
|
3271
|
+
],
|
|
3272
|
+
};
|
|
3273
|
+
const validNestedDtoAst = {
|
|
3274
|
+
type: 'Program',
|
|
3275
|
+
body: [
|
|
3276
|
+
{
|
|
3277
|
+
type: 'ClassDeclaration',
|
|
3278
|
+
id: { type: 'Identifier', name: 'CreateOrderDto' },
|
|
3279
|
+
body: {
|
|
3280
|
+
type: 'ClassBody',
|
|
3281
|
+
body: [
|
|
3282
|
+
{
|
|
3283
|
+
type: 'ClassProperty',
|
|
3284
|
+
key: { type: 'Identifier', name: 'address' },
|
|
3285
|
+
decorators: [
|
|
3286
|
+
{
|
|
3287
|
+
type: 'Decorator',
|
|
3288
|
+
expression: {
|
|
3289
|
+
type: 'CallExpression',
|
|
3290
|
+
callee: { type: 'Identifier', name: 'ValidateNested' },
|
|
3291
|
+
arguments: [],
|
|
3292
|
+
},
|
|
3293
|
+
},
|
|
3294
|
+
{
|
|
3295
|
+
type: 'Decorator',
|
|
3296
|
+
expression: {
|
|
3297
|
+
type: 'CallExpression',
|
|
2732
3298
|
callee: { type: 'Identifier', name: 'Type' },
|
|
2733
3299
|
arguments: [],
|
|
2734
3300
|
},
|
|
@@ -2758,6 +3324,225 @@ test('hasDtoNestedPropertyWithoutNestedValidation detecta DTOs anidados sin Vali
|
|
|
2758
3324
|
assert.deepEqual(match.related_nodes, ['address']);
|
|
2759
3325
|
});
|
|
2760
3326
|
|
|
3327
|
+
test('hasBackendMissingGlobalValidationPipe detecta bootstrap NestJS sin ValidationPipe estricto', () => {
|
|
3328
|
+
const missingPipeAst = {
|
|
3329
|
+
type: 'Program',
|
|
3330
|
+
body: [
|
|
3331
|
+
{
|
|
3332
|
+
type: 'ExpressionStatement',
|
|
3333
|
+
expression: {
|
|
3334
|
+
type: 'CallExpression',
|
|
3335
|
+
loc: { start: { line: 7 }, end: { line: 7 } },
|
|
3336
|
+
callee: {
|
|
3337
|
+
type: 'MemberExpression',
|
|
3338
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
3339
|
+
property: { type: 'Identifier', name: 'create' },
|
|
3340
|
+
},
|
|
3341
|
+
arguments: [],
|
|
3342
|
+
},
|
|
3343
|
+
},
|
|
3344
|
+
],
|
|
3345
|
+
};
|
|
3346
|
+
const unsafePipeAst = {
|
|
3347
|
+
type: 'Program',
|
|
3348
|
+
body: [
|
|
3349
|
+
{
|
|
3350
|
+
type: 'ExpressionStatement',
|
|
3351
|
+
expression: {
|
|
3352
|
+
type: 'CallExpression',
|
|
3353
|
+
loc: { start: { line: 11 }, end: { line: 11 } },
|
|
3354
|
+
callee: {
|
|
3355
|
+
type: 'MemberExpression',
|
|
3356
|
+
object: { type: 'Identifier', name: 'app' },
|
|
3357
|
+
property: { type: 'Identifier', name: 'useGlobalPipes' },
|
|
3358
|
+
},
|
|
3359
|
+
arguments: [
|
|
3360
|
+
{
|
|
3361
|
+
type: 'NewExpression',
|
|
3362
|
+
callee: { type: 'Identifier', name: 'ValidationPipe' },
|
|
3363
|
+
arguments: [],
|
|
3364
|
+
},
|
|
3365
|
+
],
|
|
3366
|
+
},
|
|
3367
|
+
},
|
|
3368
|
+
],
|
|
3369
|
+
};
|
|
3370
|
+
const strictPipeAst = {
|
|
3371
|
+
type: 'Program',
|
|
3372
|
+
body: [
|
|
3373
|
+
{
|
|
3374
|
+
type: 'ExpressionStatement',
|
|
3375
|
+
expression: {
|
|
3376
|
+
type: 'CallExpression',
|
|
3377
|
+
callee: {
|
|
3378
|
+
type: 'MemberExpression',
|
|
3379
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
3380
|
+
property: { type: 'Identifier', name: 'create' },
|
|
3381
|
+
},
|
|
3382
|
+
arguments: [],
|
|
3383
|
+
},
|
|
3384
|
+
},
|
|
3385
|
+
{
|
|
3386
|
+
type: 'ExpressionStatement',
|
|
3387
|
+
expression: {
|
|
3388
|
+
type: 'CallExpression',
|
|
3389
|
+
callee: {
|
|
3390
|
+
type: 'MemberExpression',
|
|
3391
|
+
object: { type: 'Identifier', name: 'app' },
|
|
3392
|
+
property: { type: 'Identifier', name: 'useGlobalPipes' },
|
|
3393
|
+
},
|
|
3394
|
+
arguments: [
|
|
3395
|
+
{
|
|
3396
|
+
type: 'NewExpression',
|
|
3397
|
+
callee: { type: 'Identifier', name: 'ValidationPipe' },
|
|
3398
|
+
arguments: [
|
|
3399
|
+
{
|
|
3400
|
+
type: 'ObjectExpression',
|
|
3401
|
+
properties: [
|
|
3402
|
+
{
|
|
3403
|
+
type: 'ObjectProperty',
|
|
3404
|
+
key: { type: 'Identifier', name: 'whitelist' },
|
|
3405
|
+
value: { type: 'BooleanLiteral', value: true },
|
|
3406
|
+
},
|
|
3407
|
+
],
|
|
3408
|
+
},
|
|
3409
|
+
],
|
|
3410
|
+
},
|
|
3411
|
+
],
|
|
3412
|
+
},
|
|
3413
|
+
},
|
|
3414
|
+
],
|
|
3415
|
+
};
|
|
3416
|
+
|
|
3417
|
+
assert.equal(hasBackendMissingGlobalValidationPipe(missingPipeAst), true);
|
|
3418
|
+
assert.deepEqual(findBackendMissingGlobalValidationPipeLines(missingPipeAst), [7]);
|
|
3419
|
+
assert.equal(hasBackendMissingGlobalValidationPipe(unsafePipeAst), true);
|
|
3420
|
+
assert.deepEqual(findBackendMissingGlobalValidationPipeLines(unsafePipeAst), [11]);
|
|
3421
|
+
assert.equal(hasBackendMissingGlobalValidationPipe(strictPipeAst), false);
|
|
3422
|
+
});
|
|
3423
|
+
|
|
3424
|
+
test('hasBackendMissingHelmetSecurityHeaders detecta bootstrap NestJS sin Helmet', () => {
|
|
3425
|
+
const missingHelmetAst = {
|
|
3426
|
+
type: 'Program',
|
|
3427
|
+
body: [
|
|
3428
|
+
{
|
|
3429
|
+
type: 'ExpressionStatement',
|
|
3430
|
+
expression: {
|
|
3431
|
+
type: 'CallExpression',
|
|
3432
|
+
loc: { start: { line: 9 }, end: { line: 9 } },
|
|
3433
|
+
callee: {
|
|
3434
|
+
type: 'MemberExpression',
|
|
3435
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
3436
|
+
property: { type: 'Identifier', name: 'create' },
|
|
3437
|
+
},
|
|
3438
|
+
arguments: [],
|
|
3439
|
+
},
|
|
3440
|
+
},
|
|
3441
|
+
],
|
|
3442
|
+
};
|
|
3443
|
+
const helmetAst = {
|
|
3444
|
+
type: 'Program',
|
|
3445
|
+
body: [
|
|
3446
|
+
{
|
|
3447
|
+
type: 'ExpressionStatement',
|
|
3448
|
+
expression: {
|
|
3449
|
+
type: 'CallExpression',
|
|
3450
|
+
loc: { start: { line: 11 }, end: { line: 11 } },
|
|
3451
|
+
callee: {
|
|
3452
|
+
type: 'MemberExpression',
|
|
3453
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
3454
|
+
property: { type: 'Identifier', name: 'create' },
|
|
3455
|
+
},
|
|
3456
|
+
arguments: [],
|
|
3457
|
+
},
|
|
3458
|
+
},
|
|
3459
|
+
{
|
|
3460
|
+
type: 'ExpressionStatement',
|
|
3461
|
+
expression: {
|
|
3462
|
+
type: 'CallExpression',
|
|
3463
|
+
callee: {
|
|
3464
|
+
type: 'MemberExpression',
|
|
3465
|
+
object: { type: 'Identifier', name: 'app' },
|
|
3466
|
+
property: { type: 'Identifier', name: 'use' },
|
|
3467
|
+
},
|
|
3468
|
+
arguments: [
|
|
3469
|
+
{
|
|
3470
|
+
type: 'CallExpression',
|
|
3471
|
+
callee: { type: 'Identifier', name: 'helmet' },
|
|
3472
|
+
arguments: [],
|
|
3473
|
+
},
|
|
3474
|
+
],
|
|
3475
|
+
},
|
|
3476
|
+
},
|
|
3477
|
+
],
|
|
3478
|
+
};
|
|
3479
|
+
|
|
3480
|
+
assert.equal(hasBackendMissingHelmetSecurityHeaders(missingHelmetAst), true);
|
|
3481
|
+
assert.deepEqual(findBackendMissingHelmetSecurityHeadersLines(missingHelmetAst), [9]);
|
|
3482
|
+
assert.equal(hasBackendMissingHelmetSecurityHeaders(helmetAst), false);
|
|
3483
|
+
});
|
|
3484
|
+
|
|
3485
|
+
test('hasBackendMissingCompressionMiddleware detecta bootstrap NestJS sin compression', () => {
|
|
3486
|
+
const missingCompressionAst = {
|
|
3487
|
+
type: 'Program',
|
|
3488
|
+
body: [
|
|
3489
|
+
{
|
|
3490
|
+
type: 'ExpressionStatement',
|
|
3491
|
+
expression: {
|
|
3492
|
+
type: 'CallExpression',
|
|
3493
|
+
loc: { start: { line: 9 }, end: { line: 9 } },
|
|
3494
|
+
callee: {
|
|
3495
|
+
type: 'MemberExpression',
|
|
3496
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
3497
|
+
property: { type: 'Identifier', name: 'create' },
|
|
3498
|
+
},
|
|
3499
|
+
arguments: [],
|
|
3500
|
+
},
|
|
3501
|
+
},
|
|
3502
|
+
],
|
|
3503
|
+
};
|
|
3504
|
+
const compressionAst = {
|
|
3505
|
+
type: 'Program',
|
|
3506
|
+
body: [
|
|
3507
|
+
{
|
|
3508
|
+
type: 'ExpressionStatement',
|
|
3509
|
+
expression: {
|
|
3510
|
+
type: 'CallExpression',
|
|
3511
|
+
loc: { start: { line: 7 }, end: { line: 7 } },
|
|
3512
|
+
callee: {
|
|
3513
|
+
type: 'MemberExpression',
|
|
3514
|
+
object: { type: 'Identifier', name: 'NestFactory' },
|
|
3515
|
+
property: { type: 'Identifier', name: 'create' },
|
|
3516
|
+
},
|
|
3517
|
+
arguments: [],
|
|
3518
|
+
},
|
|
3519
|
+
},
|
|
3520
|
+
{
|
|
3521
|
+
type: 'ExpressionStatement',
|
|
3522
|
+
expression: {
|
|
3523
|
+
type: 'CallExpression',
|
|
3524
|
+
callee: {
|
|
3525
|
+
type: 'MemberExpression',
|
|
3526
|
+
object: { type: 'Identifier', name: 'app' },
|
|
3527
|
+
property: { type: 'Identifier', name: 'use' },
|
|
3528
|
+
},
|
|
3529
|
+
arguments: [
|
|
3530
|
+
{
|
|
3531
|
+
type: 'CallExpression',
|
|
3532
|
+
callee: { type: 'Identifier', name: 'compression' },
|
|
3533
|
+
arguments: [],
|
|
3534
|
+
},
|
|
3535
|
+
],
|
|
3536
|
+
},
|
|
3537
|
+
},
|
|
3538
|
+
],
|
|
3539
|
+
};
|
|
3540
|
+
|
|
3541
|
+
assert.equal(hasBackendMissingCompressionMiddleware(missingCompressionAst), true);
|
|
3542
|
+
assert.deepEqual(findBackendMissingCompressionMiddlewareLines(missingCompressionAst), [9]);
|
|
3543
|
+
assert.equal(hasBackendMissingCompressionMiddleware(compressionAst), false);
|
|
3544
|
+
});
|
|
3545
|
+
|
|
2761
3546
|
test('hasBackendControllerRouteWithoutGuard detecta rutas NestJS sin UseGuards', () => {
|
|
2762
3547
|
const unguardedRouteAst = {
|
|
2763
3548
|
type: 'Program',
|
|
@@ -2828,3 +3613,239 @@ test('hasBackendControllerRouteWithoutGuard detecta rutas NestJS sin UseGuards',
|
|
|
2828
3613
|
assert.equal(hasBackendControllerRouteWithoutGuard(classGuardedRouteAst), false);
|
|
2829
3614
|
assert.deepEqual(findBackendControllerRouteWithoutGuardLines(unguardedRouteAst), [12]);
|
|
2830
3615
|
});
|
|
3616
|
+
|
|
3617
|
+
test('hasBackendAuthRouteWithoutThrottle detecta endpoints auth sin rate limiting', () => {
|
|
3618
|
+
const loginWithoutThrottleAst = {
|
|
3619
|
+
type: 'Program',
|
|
3620
|
+
body: [
|
|
3621
|
+
{
|
|
3622
|
+
type: 'ClassDeclaration',
|
|
3623
|
+
decorators: [
|
|
3624
|
+
{
|
|
3625
|
+
type: 'Decorator',
|
|
3626
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
|
|
3627
|
+
},
|
|
3628
|
+
],
|
|
3629
|
+
body: {
|
|
3630
|
+
type: 'ClassBody',
|
|
3631
|
+
body: [
|
|
3632
|
+
{
|
|
3633
|
+
type: 'ClassMethod',
|
|
3634
|
+
key: { type: 'Identifier', name: 'login' },
|
|
3635
|
+
loc: { start: { line: 21 }, end: { line: 24 } },
|
|
3636
|
+
decorators: [
|
|
3637
|
+
{
|
|
3638
|
+
type: 'Decorator',
|
|
3639
|
+
expression: {
|
|
3640
|
+
type: 'CallExpression',
|
|
3641
|
+
callee: { type: 'Identifier', name: 'Post' },
|
|
3642
|
+
arguments: [{ type: 'StringLiteral', value: 'login' }],
|
|
3643
|
+
},
|
|
3644
|
+
},
|
|
3645
|
+
],
|
|
3646
|
+
},
|
|
3647
|
+
],
|
|
3648
|
+
},
|
|
3649
|
+
},
|
|
3650
|
+
],
|
|
3651
|
+
};
|
|
3652
|
+
const classThrottledLoginAst = {
|
|
3653
|
+
type: 'Program',
|
|
3654
|
+
body: [
|
|
3655
|
+
{
|
|
3656
|
+
type: 'ClassDeclaration',
|
|
3657
|
+
decorators: [
|
|
3658
|
+
{
|
|
3659
|
+
type: 'Decorator',
|
|
3660
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
|
|
3661
|
+
},
|
|
3662
|
+
{
|
|
3663
|
+
type: 'Decorator',
|
|
3664
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Throttle' }, arguments: [] },
|
|
3665
|
+
},
|
|
3666
|
+
],
|
|
3667
|
+
body: {
|
|
3668
|
+
type: 'ClassBody',
|
|
3669
|
+
body: [
|
|
3670
|
+
{
|
|
3671
|
+
type: 'ClassMethod',
|
|
3672
|
+
key: { type: 'Identifier', name: 'login' },
|
|
3673
|
+
loc: { start: { line: 34 }, end: { line: 37 } },
|
|
3674
|
+
decorators: [
|
|
3675
|
+
{
|
|
3676
|
+
type: 'Decorator',
|
|
3677
|
+
expression: {
|
|
3678
|
+
type: 'CallExpression',
|
|
3679
|
+
callee: { type: 'Identifier', name: 'Post' },
|
|
3680
|
+
arguments: [{ type: 'StringLiteral', value: 'login' }],
|
|
3681
|
+
},
|
|
3682
|
+
},
|
|
3683
|
+
],
|
|
3684
|
+
},
|
|
3685
|
+
],
|
|
3686
|
+
},
|
|
3687
|
+
},
|
|
3688
|
+
],
|
|
3689
|
+
};
|
|
3690
|
+
|
|
3691
|
+
assert.equal(hasBackendAuthRouteWithoutThrottle(loginWithoutThrottleAst), true);
|
|
3692
|
+
assert.deepEqual(findBackendAuthRouteWithoutThrottleLines(loginWithoutThrottleAst), [21]);
|
|
3693
|
+
assert.equal(hasBackendAuthRouteWithoutThrottle(classThrottledLoginAst), false);
|
|
3694
|
+
});
|
|
3695
|
+
|
|
3696
|
+
test('hasBackendControllerRouteWithoutRoles detecta rutas NestJS protegidas sin RBAC explicito', () => {
|
|
3697
|
+
const guardedWithoutRolesAst = {
|
|
3698
|
+
type: 'Program',
|
|
3699
|
+
body: [
|
|
3700
|
+
{
|
|
3701
|
+
type: 'ClassDeclaration',
|
|
3702
|
+
decorators: [
|
|
3703
|
+
{
|
|
3704
|
+
type: 'Decorator',
|
|
3705
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
|
|
3706
|
+
},
|
|
3707
|
+
{
|
|
3708
|
+
type: 'Decorator',
|
|
3709
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'UseGuards' }, arguments: [] },
|
|
3710
|
+
},
|
|
3711
|
+
],
|
|
3712
|
+
body: {
|
|
3713
|
+
type: 'ClassBody',
|
|
3714
|
+
body: [
|
|
3715
|
+
{
|
|
3716
|
+
type: 'ClassMethod',
|
|
3717
|
+
key: { type: 'Identifier', name: 'deleteOrder' },
|
|
3718
|
+
loc: { start: { line: 31 }, end: { line: 33 } },
|
|
3719
|
+
decorators: [
|
|
3720
|
+
{
|
|
3721
|
+
type: 'Decorator',
|
|
3722
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Delete' }, arguments: [] },
|
|
3723
|
+
},
|
|
3724
|
+
],
|
|
3725
|
+
},
|
|
3726
|
+
],
|
|
3727
|
+
},
|
|
3728
|
+
},
|
|
3729
|
+
],
|
|
3730
|
+
};
|
|
3731
|
+
const methodRolesAst = {
|
|
3732
|
+
type: 'Program',
|
|
3733
|
+
body: [
|
|
3734
|
+
{
|
|
3735
|
+
type: 'ClassDeclaration',
|
|
3736
|
+
decorators: [
|
|
3737
|
+
{
|
|
3738
|
+
type: 'Decorator',
|
|
3739
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
|
|
3740
|
+
},
|
|
3741
|
+
{
|
|
3742
|
+
type: 'Decorator',
|
|
3743
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'UseGuards' }, arguments: [] },
|
|
3744
|
+
},
|
|
3745
|
+
],
|
|
3746
|
+
body: {
|
|
3747
|
+
type: 'ClassBody',
|
|
3748
|
+
body: [
|
|
3749
|
+
{
|
|
3750
|
+
type: 'ClassMethod',
|
|
3751
|
+
key: { type: 'Identifier', name: 'deleteOrder' },
|
|
3752
|
+
loc: { start: { line: 42 }, end: { line: 44 } },
|
|
3753
|
+
decorators: [
|
|
3754
|
+
{
|
|
3755
|
+
type: 'Decorator',
|
|
3756
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Roles' }, arguments: [] },
|
|
3757
|
+
},
|
|
3758
|
+
{
|
|
3759
|
+
type: 'Decorator',
|
|
3760
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Delete' }, arguments: [] },
|
|
3761
|
+
},
|
|
3762
|
+
],
|
|
3763
|
+
},
|
|
3764
|
+
],
|
|
3765
|
+
},
|
|
3766
|
+
},
|
|
3767
|
+
],
|
|
3768
|
+
};
|
|
3769
|
+
const unguardedRouteAst = {
|
|
3770
|
+
type: 'ClassMethod',
|
|
3771
|
+
key: { type: 'Identifier', name: 'publicHealth' },
|
|
3772
|
+
loc: { start: { line: 51 }, end: { line: 53 } },
|
|
3773
|
+
decorators: [
|
|
3774
|
+
{
|
|
3775
|
+
type: 'Decorator',
|
|
3776
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Get' }, arguments: [] },
|
|
3777
|
+
},
|
|
3778
|
+
],
|
|
3779
|
+
};
|
|
3780
|
+
|
|
3781
|
+
assert.equal(hasBackendControllerRouteWithoutRoles(guardedWithoutRolesAst), true);
|
|
3782
|
+
assert.deepEqual(findBackendControllerRouteWithoutRolesLines(guardedWithoutRolesAst), [31]);
|
|
3783
|
+
assert.equal(hasBackendControllerRouteWithoutRoles(methodRolesAst), false);
|
|
3784
|
+
assert.equal(hasBackendControllerRouteWithoutRoles(unguardedRouteAst), false);
|
|
3785
|
+
});
|
|
3786
|
+
|
|
3787
|
+
test('hasBackendEventHandlerWithoutOnEvent detecta handlers backend sin decorador OnEvent', () => {
|
|
3788
|
+
const missingOnEventAst = {
|
|
3789
|
+
type: 'Program',
|
|
3790
|
+
body: [
|
|
3791
|
+
{
|
|
3792
|
+
type: 'ClassDeclaration',
|
|
3793
|
+
id: { type: 'Identifier', name: 'OrderCreatedEventHandler' },
|
|
3794
|
+
loc: { start: { line: 9 }, end: { line: 20 } },
|
|
3795
|
+
decorators: [
|
|
3796
|
+
{
|
|
3797
|
+
type: 'Decorator',
|
|
3798
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Injectable' }, arguments: [] },
|
|
3799
|
+
},
|
|
3800
|
+
],
|
|
3801
|
+
body: {
|
|
3802
|
+
type: 'ClassBody',
|
|
3803
|
+
body: [
|
|
3804
|
+
{
|
|
3805
|
+
type: 'ClassMethod',
|
|
3806
|
+
key: { type: 'Identifier', name: 'handleOrderCreatedEvent' },
|
|
3807
|
+
loc: { start: { line: 14 }, end: { line: 18 } },
|
|
3808
|
+
decorators: [],
|
|
3809
|
+
},
|
|
3810
|
+
],
|
|
3811
|
+
},
|
|
3812
|
+
},
|
|
3813
|
+
],
|
|
3814
|
+
};
|
|
3815
|
+
const decoratedMethodAst = {
|
|
3816
|
+
type: 'Program',
|
|
3817
|
+
body: [
|
|
3818
|
+
{
|
|
3819
|
+
type: 'ClassDeclaration',
|
|
3820
|
+
id: { type: 'Identifier', name: 'OrderCreatedEventHandler' },
|
|
3821
|
+
loc: { start: { line: 29 }, end: { line: 40 } },
|
|
3822
|
+
decorators: [
|
|
3823
|
+
{
|
|
3824
|
+
type: 'Decorator',
|
|
3825
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Injectable' }, arguments: [] },
|
|
3826
|
+
},
|
|
3827
|
+
],
|
|
3828
|
+
body: {
|
|
3829
|
+
type: 'ClassBody',
|
|
3830
|
+
body: [
|
|
3831
|
+
{
|
|
3832
|
+
type: 'ClassMethod',
|
|
3833
|
+
key: { type: 'Identifier', name: 'handleOrderCreatedEvent' },
|
|
3834
|
+
loc: { start: { line: 34 }, end: { line: 38 } },
|
|
3835
|
+
decorators: [
|
|
3836
|
+
{
|
|
3837
|
+
type: 'Decorator',
|
|
3838
|
+
expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'OnEvent' }, arguments: [] },
|
|
3839
|
+
},
|
|
3840
|
+
],
|
|
3841
|
+
},
|
|
3842
|
+
],
|
|
3843
|
+
},
|
|
3844
|
+
},
|
|
3845
|
+
],
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3848
|
+
assert.equal(hasBackendEventHandlerWithoutOnEvent(missingOnEventAst), true);
|
|
3849
|
+
assert.deepEqual(findBackendEventHandlerWithoutOnEventLines(missingOnEventAst), [9, 14]);
|
|
3850
|
+
assert.equal(hasBackendEventHandlerWithoutOnEvent(decoratedMethodAst), false);
|
|
3851
|
+
});
|