Skip to content

Commit 7c38c48

Browse files
authored
Merge pull request #19769 from trailofbits/VF/Nest-improvements
Improve NestJS sources and dependency injection
2 parents 3247bab + e2eca5b commit 7c38c48

File tree

8 files changed

+186
-19
lines changed

8 files changed

+186
-19
lines changed

javascript/ql/lib/semmle/javascript/frameworks/Nest.qll

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,61 @@ module NestJS {
447447
}
448448
}
449449

450+
/**
451+
* A NestJS Middleware Class
452+
*/
453+
private class NestMiddlewareClass extends DataFlow::ClassNode {
454+
NestMiddlewareClass() {
455+
exists(ClassDefinition cls |
456+
this = cls.flow() and
457+
cls.getASuperInterface().hasUnderlyingType("@nestjs/common", "NestMiddleware")
458+
)
459+
}
460+
461+
DataFlow::FunctionNode getUseFunction() { result = this.getInstanceMethod("use") }
462+
}
463+
464+
/**
465+
* A NestJS Middleware Class route handler (the `use` method)
466+
*/
467+
private class MiddlewareRouteHandler extends Http::RouteHandler, DataFlow::FunctionNode {
468+
MiddlewareRouteHandler() { this = any(NestMiddlewareClass m).getUseFunction() }
469+
470+
override Http::HeaderDefinition getAResponseHeader(string name) { none() }
471+
472+
/**
473+
* Gets the request object used by this route
474+
*/
475+
DataFlow::ParameterNode getRequest() { result = this.getParameter(0) }
476+
477+
/**
478+
* Gets the response object used by this route
479+
*/
480+
DataFlow::ParameterNode getResponse() { result = this.getParameter(1) }
481+
}
482+
483+
/**
484+
* A source of `express` request objects for NestJS middlewares
485+
*/
486+
private class MiddlewareRequestSource extends Express::RequestSource {
487+
MiddlewareRouteHandler middlewareRouteHandler;
488+
489+
MiddlewareRequestSource() { this = middlewareRouteHandler.getRequest() }
490+
491+
override Http::RouteHandler getRouteHandler() { result = middlewareRouteHandler }
492+
}
493+
494+
/**
495+
* A source of `express` response objects for NestJS middlewares
496+
*/
497+
private class MiddlewareResponseSource extends Express::ResponseSource {
498+
MiddlewareRouteHandler middlewareRouteHandler;
499+
500+
MiddlewareResponseSource() { this = middlewareRouteHandler.getResponse() }
501+
502+
override Http::RouteHandler getRouteHandler() { result = middlewareRouteHandler }
503+
}
504+
450505
/**
451506
* A value passed in the `providers` array in:
452507
* ```js
@@ -455,21 +510,53 @@ module NestJS {
455510
* ```
456511
*/
457512
private DataFlow::Node providerTuple() {
458-
result =
459-
DataFlow::moduleImport("@nestjs/common")
460-
.getAPropertyRead("Module")
461-
.getACall()
462-
.getOptionArgument(0, "providers")
463-
.getALocalSource()
464-
.(DataFlow::ArrayCreationNode)
465-
.getAnElement()
513+
exists(DataFlow::CallNode moduleCall |
514+
moduleCall = DataFlow::moduleImport("@nestjs/common").getAPropertyRead("Module").getACall() and
515+
result = providerTupleAux(moduleCall.getArgument(0).getALocalSource())
516+
)
517+
}
518+
519+
private DataFlow::Node providerTupleAux(DataFlow::ObjectLiteralNode o) {
520+
(
521+
result =
522+
o.getAPropertyWrite("providers")
523+
.getRhs()
524+
.getALocalSource()
525+
.(DataFlow::ArrayCreationNode)
526+
.getAnElement()
527+
or
528+
result =
529+
providerTupleAux(o.getAPropertyWrite("imports")
530+
.getRhs()
531+
.getALocalSource()
532+
.(DataFlow::ArrayCreationNode)
533+
.getAnElement()
534+
.(DataFlow::CallNode)
535+
.getCalleeNode()
536+
.getAFunctionValue()
537+
.getFunction()
538+
.getAReturnedExpr()
539+
.flow())
540+
)
541+
}
542+
543+
private DataFlow::Node getConcreteClassFromProviderTuple(DataFlow::SourceNode tuple) {
544+
result = tuple.getAPropertyWrite("useClass").getRhs()
545+
or
546+
exists(DataFlow::FunctionNode f |
547+
f = tuple.getAPropertyWrite("useFactory").getRhs().getAFunctionValue() and
548+
result.getAstNode() = f.getFunction().getAReturnedExpr().getType().(ClassType).getClass()
549+
)
550+
or
551+
result.getAstNode() =
552+
tuple.getAPropertyWrite("useValue").getRhs().asExpr().getType().(ClassType).getClass()
466553
}
467554

468555
private predicate providerPair(DataFlow::Node interface, DataFlow::Node concreteClass) {
469556
exists(DataFlow::SourceNode tuple |
470557
tuple = providerTuple().getALocalSource() and
471558
interface = tuple.getAPropertyWrite("provide").getRhs() and
472-
concreteClass = tuple.getAPropertyWrite("useClass").getRhs()
559+
concreteClass = getConcreteClassFromProviderTuple(tuple)
473560
)
474561
}
475562

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
import { Module } from '@nestjs/common';
22
import { Controller } from './validation';
3-
import { Foo } from './foo.interface';
4-
import { FooImpl } from './foo.impl';
3+
import { Imports } from './imports';
4+
import { Foo, Foo2, Foo3 } from './foo.interface';
5+
import { FooImpl, Foo2Impl, Foo3Impl } from './foo.impl';
6+
7+
const foo3 = new Foo3Impl()
58

69
@Module({
7-
controllers: [Controller],
8-
providers: [{
9-
provide: Foo, useClass: FooImpl
10-
}],
10+
controllers: [Controller],
11+
imports: [Imports.forRoot()],
12+
providers: [
13+
{
14+
provide: Foo,
15+
useClass: FooImpl
16+
},
17+
{
18+
provide: Foo2,
19+
useFactory: () => new Foo2Impl()
20+
},
21+
{
22+
provide: Foo3,
23+
useValue: foo3
24+
}
25+
],
1126
})
1227
export class AppModule { }
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
1-
import { Foo } from "./foo.interface";
1+
import { Foo, Foo2, Foo3, Foo4 } from "./foo.interface";
22

33
export class FooImpl extends Foo {
44
fooMethod(x: string) {
55
sink(x); // $ hasValueFlow=x
66
}
77
}
8+
9+
export class Foo2Impl extends Foo2 {
10+
fooMethod(x: string) {
11+
sink(x); // $ hasValueFlow=x
12+
}
13+
}
14+
15+
export class Foo3Impl extends Foo3 {
16+
fooMethod(x: string) {
17+
sink(x); // $ hasValueFlow=x
18+
}
19+
}
20+
21+
export class Foo4Impl extends Foo4 {
22+
fooMethod(x: string) {
23+
sink(x); // $ hasValueFlow=x
24+
}
25+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
export abstract class Foo {
22
abstract fooMethod(x: string): void;
33
}
4+
5+
export abstract class Foo2 {
6+
abstract fooMethod(x: string): void;
7+
}
8+
9+
export abstract class Foo3 {
10+
abstract fooMethod(x: string): void;
11+
}
12+
13+
export abstract class Foo4 {
14+
abstract fooMethod(x: string): void;
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { DynamicModule } from '@nestjs/common';
2+
import { Foo4Impl } from './foo.impl';
3+
import { Foo4 } from './foo.interface';
4+
5+
export class Imports {
6+
static forRoot(): DynamicModule {
7+
return {
8+
providers: [
9+
{
10+
provide: Foo4,
11+
useClass: Foo4Impl,
12+
},
13+
],
14+
};
15+
}
16+
}

javascript/ql/test/library-tests/frameworks/Nest/global/validation.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Get, Query } from '@nestjs/common';
22
import { IsIn } from 'class-validator';
3-
import { Foo } from './foo.interface';
3+
import { Foo, Foo2, Foo3, Foo4 } from './foo.interface';
44

55
export class Controller {
66
constructor(
7-
private readonly foo: Foo
7+
private readonly foo: Foo, private readonly foo2: Foo2, private readonly foo3: Foo3, private readonly foo4: Foo4
88
) { }
99

1010
@Get()
@@ -16,6 +16,9 @@ export class Controller {
1616
@Get()
1717
route2(@Query('x') x: string) {
1818
this.foo.fooMethod(x);
19+
this.foo2.fooMethod(x);
20+
this.foo3.fooMethod(x);
21+
this.foo4.fooMethod(x);
1922
}
2023
}
2124

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Injectable, NestMiddleware } from '@nestjs/common';
2+
import { Response, NextFunction } from 'express';
3+
import { CustomRequest } from '@randomPackage/request';
4+
5+
@Injectable()
6+
export class LoggerMiddleware implements NestMiddleware {
7+
// The request can be a custom type that extends the express Request
8+
use(req: CustomRequest, res: Response, next: NextFunction) {
9+
console.log(req.query.abc);
10+
next();
11+
}
12+
}

javascript/ql/test/library-tests/frameworks/Nest/test.expected

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
testFailures
22
routeHandler
33
| global/validation.ts:11:3:14:3 | route1( ... OK\\n } |
4-
| global/validation.ts:17:3:19:3 | route2( ... x);\\n } |
4+
| global/validation.ts:17:3:22:3 | route2( ... x);\\n } |
55
| local/customDecorator.ts:18:3:20:3 | sneaky( ... OK\\n } |
66
| local/customDecorator.ts:23:3:25:3 | safe(@S ... OK\\n } |
77
| local/customPipe.ts:20:5:22:5 | sanitiz ... K\\n } |
@@ -10,6 +10,7 @@ routeHandler
1010
| local/customPipe.ts:36:5:38:5 | propaga ... K\\n } |
1111
| local/customPipe.ts:41:5:43:5 | propaga ... K\\n } |
1212
| local/customPipe.ts:47:5:49:5 | propaga ... K\\n } |
13+
| local/middleware.ts:8:3:11:3 | use(req ... ();\\n } |
1314
| local/routes.ts:6:3:8:3 | getFoo( ... o';\\n } |
1415
| local/routes.ts:11:3:13:3 | postFoo ... o';\\n } |
1516
| local/routes.ts:16:3:18:3 | getRoot ... o';\\n } |
@@ -29,9 +30,11 @@ routeHandler
2930
| local/validation.ts:42:3:45:3 | route6( ... OK\\n } |
3031
requestSource
3132
| local/customDecorator.ts:5:21:5:51 | ctx.swi ... quest() |
33+
| local/middleware.ts:8:7:8:9 | req |
3234
| local/routes.ts:30:12:30:14 | req |
3335
| local/routes.ts:61:23:61:25 | req |
3436
responseSource
37+
| local/middleware.ts:8:27:8:29 | res |
3538
| local/routes.ts:61:35:61:37 | res |
3639
| local/routes.ts:62:5:62:25 | res.sen ... uery.x) |
3740
requestInputAccess
@@ -44,6 +47,7 @@ requestInputAccess
4447
| parameter | local/customDecorator.ts:6:12:6:41 | request ... ryParam |
4548
| parameter | local/customPipe.ts:5:15:5:19 | value |
4649
| parameter | local/customPipe.ts:13:15:13:19 | value |
50+
| parameter | local/middleware.ts:9:17:9:29 | req.query.abc |
4751
| parameter | local/routes.ts:27:17:27:17 | x |
4852
| parameter | local/routes.ts:28:14:28:21 | queryObj |
4953
| parameter | local/routes.ts:29:20:29:23 | name |

0 commit comments

Comments
 (0)