Skip to content

Commit b48604e

Browse files
Merge pull request #222 from advanced-security/jeongsoolee09/debug-remoteflowsources-properties
Address FN involving CAP remote flow sources
2 parents 6be8846 + 80c9bd1 commit b48604e

File tree

15 files changed

+196
-30
lines changed

15 files changed

+196
-30
lines changed

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,27 @@ class ServiceInstanceFromConstructor extends ServiceInstance {
177177
}
178178

179179
/**
180-
* A read to `this` variable which represents the service whose definition encloses this variable access.
180+
* A read to `this` variable which represents the service whose definition encloses
181+
* this variable access.
182+
* e.g.1. Given this code:
183+
* ``` javascript
184+
* const cds = require("@sap/cds");
185+
* module.exports = class SomeService extends cds.ApplicationService {
186+
* init() {
187+
* this.on("SomeEvent", (req) => { ... } )
188+
* }
189+
* }
190+
* ```
191+
* This class captures the access to the `this` variable as in `this.on(...)`.
192+
*
193+
* e.g.2. Given this code:
194+
* ``` javascript
195+
* const cds = require('@sap/cds');
196+
* module.exports = cds.service.impl (function() {
197+
* this.on("SomeEvent", (req) => { ... })
198+
* })
199+
* ```
200+
* This class captures the access to the `this` variable as in `this.on(...)`.
181201
*/
182202
class ServiceInstanceFromThisNode extends ServiceInstance, ThisNode {
183203
UserDefinedApplicationService userDefinedApplicationService;
@@ -294,12 +314,56 @@ class DbServiceInstanceFromCdsConnectTo extends ServiceInstanceFromCdsConnectTo,
294314
override UserDefinedApplicationService getDefinition() { none() }
295315
}
296316

317+
/**
318+
* The 0-th parameter of an exported closure that represents the service being implemented. e.g.
319+
* ``` javascript
320+
* const cds = require('@sap/cds')
321+
* module.exports = (srv) => {
322+
* srv.on("SomeEvent1", (req) => { ... })
323+
* }
324+
* ```
325+
* This class captures the `srv` parameter of the exported arrow function. Also see
326+
* `ServiceInstanceFromImplMethodCallClosureParameter` which is similar to this.
327+
*/
328+
class ServiceInstanceFromExportedClosureParameter extends ServiceInstance, ParameterNode {
329+
ExportedClosureApplicationServiceDefinition exportedClosure;
330+
331+
ServiceInstanceFromExportedClosureParameter() { this = exportedClosure.getParameter(0) }
332+
333+
override UserDefinedApplicationService getDefinition() { result = exportedClosure }
334+
}
335+
336+
/**
337+
* The 0-th parameter of a callback (usually an arrow function) passed to `cds.service.impl` that
338+
* represents the service being implemented. e.g.
339+
* ``` javascript
340+
* const cds = require('@sap/cds')
341+
* module.exports = cds.service.impl((srv) => {
342+
* srv.on("SomeEvent1", (req) => { ... })
343+
* })
344+
* ```
345+
* This class captures the `srv` parameter of the exported arrow function. Also see
346+
* `ServiceInstanceFromExportedClosureParameter` which is similar to this.
347+
*/
348+
class ServiceInstanceFromImplMethodCallClosureParameter extends ServiceInstance, ParameterNode {
349+
ImplMethodCallApplicationServiceDefinition implMethodCallApplicationServiceDefinition;
350+
351+
ServiceInstanceFromImplMethodCallClosureParameter() {
352+
this = implMethodCallApplicationServiceDefinition.getInitFunction().getParameter(0)
353+
}
354+
355+
override UserDefinedApplicationService getDefinition() {
356+
result = implMethodCallApplicationServiceDefinition
357+
}
358+
}
359+
297360
/**
298361
* A call to `before`, `on`, or `after` on an `cds.ApplicationService`.
299362
* It registers an handler to be executed when an event is fired,
300363
* to do something with the incoming request or event as its parameter.
301364
*/
302365
class HandlerRegistration extends MethodCallNode {
366+
/** The instance of the service a handler is registered on. */
303367
ServiceInstance srv;
304368
string methodName;
305369

@@ -308,6 +372,9 @@ class HandlerRegistration extends MethodCallNode {
308372
methodName = ["before", "on", "after"]
309373
}
310374

375+
/**
376+
* Gets the instance of the service a handler is registered on.
377+
*/
311378
ServiceInstance getService() { result = srv }
312379

313380
/**
@@ -347,7 +414,7 @@ class HandlerRegistration extends MethodCallNode {
347414

348415
/**
349416
* The first parameter of a handler, representing the request object received either directly
350-
* from a user, or from another service that may be internal (defined in the same application)
417+
* from a user, or from another service that may be internal (defined in the same application)
351418
* or external (defined in another application, or even served from a different server).
352419
* e.g.
353420
* ``` javascript
@@ -356,7 +423,7 @@ class HandlerRegistration extends MethodCallNode {
356423
* this.before("SomeEvent", "SomeEntity", (req, next) => { ... });
357424
* this.after("SomeEvent", "SomeEntity", (req, next) => { ... });
358425
* }
359-
* ```
426+
* ```
360427
* All parameters named `req` above are captured. Also see `HandlerParameterOfExposedService`
361428
* for a subset of this class that is only about handlers exposed to some protocol.
362429
*/
@@ -506,7 +573,7 @@ abstract class UserDefinedApplicationService extends UserDefinedService {
506573
/**
507574
* Holds if this service supports access from the outside through any kind of protocol.
508575
*/
509-
predicate isExposed() { not this.isInternal() }
576+
predicate isExposed() { exists(this.getCdsDeclaration()) and not this.isInternal() }
510577

511578
/**
512579
* Holds if this service does not support access from the outside through any kind of protocol, thus being internal only.
@@ -539,9 +606,21 @@ class ES6ApplicationServiceDefinition extends ClassNode, UserDefinedApplicationS
539606

540607
/**
541608
* Subclassing `cds.ApplicationService` via a call to `cds.service.impl`.
542-
* ```js
609+
* e.g.1. Given this code:
610+
* ``` javascript
611+
* const cds = require('@sap/cds')
612+
* module.exports = cds.service.impl (function() {
613+
* this.on("SomeEvent1", (req) => { ... })
614+
* })
615+
* ```
616+
* This class captures the call `cds.service.impl (function() { ... })`.
617+
*
618+
* e.g.2. Given this code:
619+
* ``` javascript
543620
* const cds = require('@sap/cds')
544-
* module.exports = cds.service.impl (function() { ... })
621+
* module.exports = cds.service.impl ((srv) => {
622+
* srv.on("SomeEvent1", (req) => { ... })
623+
* })
545624
* ```
546625
*/
547626
class ImplMethodCallApplicationServiceDefinition extends MethodCallNode,
@@ -554,6 +633,40 @@ class ImplMethodCallApplicationServiceDefinition extends MethodCallNode,
554633
override FunctionNode getInitFunction() { result = this.getArgument(0) }
555634
}
556635

636+
/**
637+
* A user-defined application service that comes in a form of an exported
638+
* closure. e.g. Given the below code,
639+
* ``` javascript
640+
* const cds = require("@sap/cds");
641+
*
642+
* module.exports = (srv) => {
643+
* srv.before("SomeEvent1", "SomeEntity", (req, res) => { ... })
644+
* srv.on("SomeEvent2", (req) => { ... } )
645+
* srv.after("SomeEvent3", (req) => { ... } )
646+
* }
647+
* ```
648+
* This class captures the entire `(srv) => { ... }` function that is
649+
* exported.
650+
*/
651+
class ExportedClosureApplicationServiceDefinition extends FunctionNode,
652+
UserDefinedApplicationService
653+
{
654+
ExportedClosureApplicationServiceDefinition() {
655+
/*
656+
* ==================== HACK ====================
657+
* See issue #221.
658+
*/
659+
660+
exists(PropWrite moduleExports |
661+
moduleExports.getBase().asExpr().(VarAccess).getName() = "module" and
662+
moduleExports.getPropertyName() = "exports" and
663+
this = moduleExports.getRhs()
664+
)
665+
}
666+
667+
override FunctionNode getInitFunction() { result = this }
668+
}
669+
557670
abstract class InterServiceCommunicationMethodCall extends MethodCallNode {
558671
string name;
559672
ServiceInstance recipient;

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ class HandlerParameterOfExposedService extends HandlerParameter {
3030
* is known.
3131
*/
3232

33-
not exists(this.getHandler().getHandlerRegistration().getService().getDefinition())
33+
not exists(
34+
this.getHandler().getHandlerRegistration().getService().getDefinition().getCdsDeclaration()
35+
)
3436
}
3537
}
3638

@@ -54,7 +56,9 @@ class UserProvidedPropertyReadOfHandlerParameterOfExposedService extends RemoteF
5456

5557
UserProvidedPropertyReadOfHandlerParameterOfExposedService() {
5658
/* 1. `req.(data|params|headers|id)` */
57-
this = handlerParameterOfExposedService.getAPropertyRead(["data", "params", "headers", "id"])
59+
this =
60+
handlerParameterOfExposedService
61+
.getAPropertyRead(["data", "params", "headers", "id", "_queryOptions"])
5862
or
5963
/* 2. APIs stemming from `req.http.req`: Defined by Express.js */
6064
exists(PropRead reqHttpReq |

javascript/frameworks/cap/test/models/cds/remoteflowsources/ExposedServices.expected renamed to javascript/frameworks/cap/test/models/cds/remoteflowsources/HandlerParameterOfExposedService.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| srv/service1.js:52:29:52:31 | req |
1010
| srv/service1.js:58:29:58:31 | req |
1111
| srv/service1.js:64:29:64:31 | req |
12+
| srv/service1.js:70:30:70:32 | req |
1213
| srv/service2.js:4:27:4:29 | msg |
1314
| srv/service3.js:5:29:5:31 | req |
1415
| srv/service3.js:11:29:11:31 | req |
@@ -19,3 +20,4 @@
1920
| srv/service3.js:51:29:51:31 | req |
2021
| srv/service3.js:57:29:57:31 | req |
2122
| srv/service3.js:63:29:63:31 | req |
23+
| srv/service3.js:69:30:69:32 | req |

javascript/frameworks/cap/test/models/cds/remoteflowsources/remoteflowsource.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
| srv/service1.js:34:31:34:61 | req.htt ... eProp") |
1414
| srv/service1.js:35:31:35:60 | req.htt ... eProp") |
1515
| srv/service1.js:41:29:41:34 | req.id |
16+
| srv/service1.js:47:29:47:45 | req._queryOptions |
1617
| srv/service2.js:5:31:5:38 | msg.data |
1718
| srv/service3.js:6:33:6:40 | req.data |
1819
| srv/service3.js:12:33:12:42 | req.params |
@@ -29,3 +30,4 @@
2930
| srv/service3.js:33:31:33:61 | req.htt ... eProp") |
3031
| srv/service3.js:34:31:34:60 | req.htt ... eProp") |
3132
| srv/service3.js:40:29:40:34 | req.id |
33+
| srv/service3.js:46:29:46:45 | req._queryOptions |

javascript/frameworks/cap/test/models/cds/remoteflowsources/srv/service1.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,30 @@ module.exports = class Service1 extends cds.ApplicationService {
4444
});
4545

4646
this.on("send6", async (req) => {
47-
const messageToPass = req.locale; // SAFE: Not a taint source, Exposed service
47+
const messageToPass = req._queryOptions; // UNSAFE: Taint source, Exposed service
4848
const Service2 = await cds.connect.to("service-2");
4949
Service2.send("send2", { messageToPass });
5050
});
5151

5252
this.on("send7", async (req) => {
53-
const messageToPass = req.tenant; // SAFE: Not a taint source, Exposed service
53+
const messageToPass = req.locale; // SAFE: Not a taint source, Exposed service
5454
const Service2 = await cds.connect.to("service-2");
5555
Service2.send("send2", { messageToPass });
5656
});
5757

5858
this.on("send8", async (req) => {
59-
const messageToPass = req.timestamp; // SAFE: Not a taint source, Exposed service
59+
const messageToPass = req.tenant; // SAFE: Not a taint source, Exposed service
6060
const Service2 = await cds.connect.to("service-2");
6161
Service2.send("send2", { messageToPass });
6262
});
6363

6464
this.on("send9", async (req) => {
65+
const messageToPass = req.timestamp; // SAFE: Not a taint source, Exposed service
66+
const Service2 = await cds.connect.to("service-2");
67+
Service2.send("send2", { messageToPass });
68+
});
69+
70+
this.on("send10", async (req) => {
6571
const messageToPass = req.user; // SAFE: Not a taint source, Exposed service
6672
const Service2 = await cds.connect.to("service-2");
6773
Service2.send("send2", { messageToPass });

javascript/frameworks/cap/test/models/cds/remoteflowsources/srv/service3.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,30 @@ class Service3 extends cds.ApplicationService {
4343
});
4444

4545
this.on("send6", async (req) => {
46-
const messageToPass = req.locale; // SAFE: Not a taint source, Exposed service (fallback)
46+
const messageToPass = req._queryOptions; // UNSAFE: Taint source, Exposed service
4747
const Service2 = await cds.connect.to("service-2");
4848
Service2.send("send2", { messageToPass });
4949
});
5050

5151
this.on("send7", async (req) => {
52-
const messageToPass = req.tenant; // SAFE: Not a taint source, Exposed service (fallback)
52+
const messageToPass = req.locale; // SAFE: Not a taint source, Exposed service (fallback)
5353
const Service2 = await cds.connect.to("service-2");
5454
Service2.send("send2", { messageToPass });
5555
});
5656

5757
this.on("send8", async (req) => {
58-
const messageToPass = req.timestamp; // SAFE: Not a taint source, Exposed service (fallback)
58+
const messageToPass = req.tenant; // SAFE: Not a taint source, Exposed service (fallback)
5959
const Service2 = await cds.connect.to("service-2");
6060
Service2.send("send2", { messageToPass });
6161
});
6262

6363
this.on("send9", async (req) => {
64+
const messageToPass = req.timestamp; // SAFE: Not a taint source, Exposed service (fallback)
65+
const Service2 = await cds.connect.to("service-2");
66+
Service2.send("send2", { messageToPass });
67+
});
68+
69+
this.on("send10", async (req) => {
6470
const messageToPass = req.user; // SAFE: Not a taint source, Exposed service (fallback)
6571
const Service2 = await cds.connect.to("service-2");
6672
Service2.send("send2", { messageToPass });

javascript/frameworks/cap/test/models/cds/remoteflowsources/srv/service4.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,11 @@ module.exports = class Service4 extends cds.ApplicationService {
4141
const Service2 = await cds.connect.to("service-2");
4242
Service2.send("send2", { messageToPass });
4343
});
44+
45+
this.on("send6", async (req) => {
46+
const messageToPass = req._queryOptions; // UNSAFE: Taint source, Exposed service
47+
const Service2 = await cds.connect.to("service-2");
48+
Service2.send("send2", { messageToPass });
49+
});
4450
}
4551
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const cds = require("@sap/cds");
2+
3+
class Service1 extends cds.ApplicationService { // ES6ApplicationServiceDefinition
4+
init() {
5+
return super.init()
6+
}
7+
}
8+
9+
module.exports = Service1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const cds = require("@sap/cds");
2+
3+
module.exports = class LogService extends cds.Service { // UserDefinedService
4+
init() {
5+
return super.init()
6+
}
7+
}

0 commit comments

Comments
 (0)