Skip to content

Commit 02f4695

Browse files
authored
Merge pull request #1152 from esben-semmle/js/koa-improvements
Approved by xiemaisi
2 parents 54b4e59 + 2622fc6 commit 02f4695

File tree

8 files changed

+218
-31
lines changed

8 files changed

+218
-31
lines changed

change-notes/1.21/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## General improvements
44

55
* Support for the following frameworks and libraries has been improved:
6+
- [koa](https://github.com/koajs/koa)
67
- [socket.io](http://socket.io)
78

89
* The security queries now track data flow through Base64 decoders such as the Node.js `Buffer` class, the DOM function `atob`, and a number of npm packages intcluding [`abab`](https://www.npmjs.com/package/abab), [`atob`](https://www.npmjs.com/package/atob), [`btoa`](https://www.npmjs.com/package/btoa), [`base-64`](https://www.npmjs.com/package/base-64), [`js-base64`](https://www.npmjs.com/package/js-base64), [`Base64.js`](https://www.npmjs.com/package/Base64) and [`base64-js`](https://www.npmjs.com/package/base64-js).

javascript/ql/src/semmle/javascript/frameworks/Koa.qll

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
/**
23
* Provides classes for working with [Koa](https://koajs.com) applications.
34
*/
@@ -24,7 +25,7 @@ module Koa {
2425

2526
HeaderDefinition() {
2627
// ctx.set('Cache-Control', 'no-cache');
27-
astNode.calls(rh.getAContextExpr(), "set")
28+
astNode.calls(rh.getAResponseOrContextExpr(), "set")
2829
or
2930
// ctx.response.header('Cache-Control', 'no-cache')
3031
astNode.calls(rh.getAResponseExpr(), "header")
@@ -58,6 +59,18 @@ module Koa {
5859
* route handler.
5960
*/
6061
Expr getAContextExpr() { result.(ContextExpr).getRouteHandler() = this }
62+
63+
/**
64+
* Gets an expression that contains the context or response
65+
* object of a route handler invocation.
66+
*/
67+
Expr getAResponseOrContextExpr() { result = getAResponseExpr() or result = getAContextExpr() }
68+
69+
/**
70+
* Gets an expression that contains the context or request
71+
* object of a route handler invocation.
72+
*/
73+
Expr getARequestOrContextExpr() { result = getARequestExpr() or result = getAContextExpr() }
6174
}
6275

6376
/**
@@ -159,35 +172,39 @@ module Koa {
159172
string kind;
160173

161174
RequestInputAccess() {
162-
exists(Expr request | request = rh.getARequestExpr() |
163-
// `ctx.request.body`
164-
kind = "body" and
165-
this.asExpr().(PropAccess).accesses(request, "body")
166-
or
167-
kind = "parameter" and
168-
this = getAQueryParameterAccess(rh)
169-
or
175+
kind = "parameter" and
176+
this = getAQueryParameterAccess(rh)
177+
or
178+
exists(Expr e | rh.getARequestOrContextExpr() = e |
179+
// `ctx.request.url`, `ctx.request.originalUrl`, or `ctx.request.href`
170180
exists(string propName |
171-
// `ctx.request.url`, `ctx.request.originalUrl`, or `ctx.request.href`
172181
kind = "url" and
173-
this.asExpr().(PropAccess).accesses(request, propName)
182+
this.asExpr().(PropAccess).accesses(e, propName)
174183
|
175-
propName = "url" or
176-
propName = "originalUrl" or
184+
propName = "url"
185+
or
186+
propName = "originalUrl"
187+
or
177188
propName = "href"
178189
)
179-
)
180-
or
181-
exists(PropAccess cookies |
190+
or
191+
// `ctx.request.body`
192+
e instanceof RequestExpr and
193+
kind = "body" and
194+
this.asExpr().(PropAccess).accesses(e, "body")
195+
or
182196
// `ctx.cookies.get(<name>)`
183-
kind = "cookie" and
184-
cookies.accesses(rh.getAContextExpr(), "cookies") and
185-
this.asExpr().(MethodCallExpr).calls(cookies, "get")
186-
)
187-
or
188-
exists(RequestHeaderAccess access | access = this |
189-
rh = access.getRouteHandler() and
190-
kind = "header"
197+
exists(PropAccess cookies |
198+
e instanceof ContextExpr and
199+
kind = "cookie" and
200+
cookies.accesses(e, "cookies") and
201+
this = cookies.flow().(DataFlow::SourceNode).getAMethodCall("get")
202+
)
203+
or
204+
exists(RequestHeaderAccess access | access = this |
205+
rh = access.getRouteHandler() and
206+
kind = "header"
207+
)
191208
)
192209
}
193210

@@ -199,8 +216,11 @@ module Koa {
199216
}
200217

201218
private DataFlow::Node getAQueryParameterAccess(RouteHandler rh) {
202-
// `ctx.request.query.name`
203-
result.asExpr().(PropAccess).getBase().(PropAccess).accesses(rh.getARequestExpr(), "query")
219+
// `ctx.query.name` or `ctx.request.query.name`
220+
exists(PropAccess q |
221+
q.accesses(rh.getARequestOrContextExpr(), "query") and
222+
result = q.flow().(DataFlow::SourceNode).getAPropertyRead()
223+
)
204224
}
205225

206226
/**
@@ -210,18 +230,18 @@ module Koa {
210230
RouteHandler rh;
211231

212232
RequestHeaderAccess() {
213-
exists(Expr request | request = rh.getARequestExpr() |
233+
exists(Expr e | e = rh.getARequestOrContextExpr() |
214234
exists(string propName, PropAccess headers |
215235
// `ctx.request.header.<name>`, `ctx.request.headers.<name>`
216-
headers.accesses(request, propName) and
217-
this.asExpr().(PropAccess).accesses(headers, _)
236+
headers.accesses(e, propName) and
237+
this = headers.flow().(DataFlow::SourceNode).getAPropertyRead()
218238
|
219239
propName = "header" or
220240
propName = "headers"
221241
)
222242
or
223243
// `ctx.request.get(<name>)`
224-
this.asExpr().(MethodCallExpr).calls(request, "get")
244+
this.asExpr().(MethodCallExpr).calls(e, "get")
225245
)
226246
}
227247

@@ -264,10 +284,25 @@ module Koa {
264284

265285
ResponseSendArgument() {
266286
exists(DataFlow::PropWrite pwn |
267-
pwn.writes(DataFlow::valueNode(rh.getAResponseExpr()), "body", DataFlow::valueNode(this))
287+
pwn
288+
.writes(DataFlow::valueNode(rh.getAResponseOrContextExpr()), "body",
289+
DataFlow::valueNode(this))
268290
)
269291
}
270292

271293
override RouteHandler getRouteHandler() { result = rh }
272294
}
295+
296+
/**
297+
* An invocation of the `redirect` method of an HTTP response object.
298+
*/
299+
private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr {
300+
RouteHandler rh;
301+
302+
RedirectInvocation() { this.(MethodCallExpr).calls(rh.getAResponseOrContextExpr(), "redirect") }
303+
304+
override Expr getUrlArgument() { result = getArgument(0) }
305+
306+
override RouteHandler getRouteHandler() { result = rh }
307+
}
273308
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
import javascript
3+
4+
query predicate test_RedirectInvocation(
5+
HTTP::RedirectInvocation redirect, Expr url, HTTP::RouteHandler rh
6+
) {
7+
redirect.getUrlArgument() = url and
8+
redirect.getRouteHandler() = rh
9+
}

javascript/ql/test/library-tests/frameworks/koa/src/koa.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,31 @@ app2.use(function handler2(ctx){ // HTTP::RouteHandler
2626
ctx.request.get('bar');
2727
ctx.cookies.get('baz');
2828
});
29+
30+
app2.use(async ctx => {
31+
ctx.body = x;
32+
ctx.body;
33+
ctx.query.foo;
34+
ctx.url;
35+
ctx.originalUrl;
36+
ctx.href;
37+
ctx.header.bar;
38+
ctx.headers.bar;
39+
ctx.set('bar');
40+
ctx.get('bar');
41+
42+
var url = ctx.query.target;
43+
ctx.redirect(url);
44+
ctx.response.redirect(url);
45+
});
46+
47+
app2.use(async ctx => {
48+
var cookies = ctx.cookies;
49+
cookies.get();
50+
51+
var query = ctx.query;
52+
query.foo;
53+
54+
var headers = ctx.headers;
55+
headers.foo;
56+
});

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
test_RouteSetup
22
| src/koa.js:8:1:8:18 | app2.use(handler1) |
33
| src/koa.js:10:1:28:2 | app2.us ... z');\\n}) |
4+
| src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) |
5+
| src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) |
46
test_RequestInputAccess
57
| src/koa.js:19:3:19:18 | ctx.request.body | body | src/koa.js:10:10:28:1 | functio ... az');\\n} |
68
| src/koa.js:20:3:20:23 | ctx.req ... ery.foo | parameter | src/koa.js:10:10:28:1 | functio ... az');\\n} |
@@ -11,6 +13,17 @@ test_RequestInputAccess
1113
| src/koa.js:25:3:25:25 | ctx.req ... ers.bar | header | src/koa.js:10:10:28:1 | functio ... az');\\n} |
1214
| src/koa.js:26:3:26:24 | ctx.req ... ('bar') | header | src/koa.js:10:10:28:1 | functio ... az');\\n} |
1315
| src/koa.js:27:3:27:24 | ctx.coo ... ('baz') | cookie | src/koa.js:10:10:28:1 | functio ... az');\\n} |
16+
| src/koa.js:33:2:33:14 | ctx.query.foo | parameter | src/koa.js:30:10:45:1 | async c ... url);\\n} |
17+
| src/koa.js:34:2:34:8 | ctx.url | url | src/koa.js:30:10:45:1 | async c ... url);\\n} |
18+
| src/koa.js:35:2:35:16 | ctx.originalUrl | url | src/koa.js:30:10:45:1 | async c ... url);\\n} |
19+
| src/koa.js:36:2:36:9 | ctx.href | url | src/koa.js:30:10:45:1 | async c ... url);\\n} |
20+
| src/koa.js:37:2:37:15 | ctx.header.bar | header | src/koa.js:30:10:45:1 | async c ... url);\\n} |
21+
| src/koa.js:38:2:38:16 | ctx.headers.bar | header | src/koa.js:30:10:45:1 | async c ... url);\\n} |
22+
| src/koa.js:40:2:40:15 | ctx.get('bar') | header | src/koa.js:30:10:45:1 | async c ... url);\\n} |
23+
| src/koa.js:42:12:42:27 | ctx.query.target | parameter | src/koa.js:30:10:45:1 | async c ... url);\\n} |
24+
| src/koa.js:49:2:49:14 | cookies.get() | cookie | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
25+
| src/koa.js:52:2:52:10 | query.foo | parameter | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
26+
| src/koa.js:55:2:55:12 | headers.foo | header | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
1427
test_RouteHandler_getAResponseHeader
1528
| src/koa.js:10:10:28:1 | functio ... az');\\n} | header1 | src/koa.js:11:3:11:25 | this.se ... 1', '') |
1629
| src/koa.js:10:10:28:1 | functio ... az');\\n} | header2 | src/koa.js:12:3:12:37 | this.re ... 2', '') |
@@ -29,6 +42,7 @@ test_ResponseExpr
2942
| src/koa.js:15:13:15:24 | ctx.response | src/koa.js:10:10:28:1 | functio ... az');\\n} |
3043
| src/koa.js:16:3:16:5 | rsp | src/koa.js:10:10:28:1 | functio ... az');\\n} |
3144
| src/koa.js:18:3:18:14 | ctx.response | src/koa.js:10:10:28:1 | functio ... az');\\n} |
45+
| src/koa.js:44:2:44:13 | ctx.response | src/koa.js:30:10:45:1 | async c ... url);\\n} |
3246
test_RouteHandler_getAContextExpr
3347
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:11:3:11:6 | this |
3448
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:12:3:12:6 | this |
@@ -45,15 +59,34 @@ test_RouteHandler_getAContextExpr
4559
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:25:3:25:5 | ctx |
4660
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:26:3:26:5 | ctx |
4761
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:27:3:27:5 | ctx |
62+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:31:2:31:4 | ctx |
63+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:32:2:32:4 | ctx |
64+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:33:2:33:4 | ctx |
65+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:34:2:34:4 | ctx |
66+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:35:2:35:4 | ctx |
67+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:36:2:36:4 | ctx |
68+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:37:2:37:4 | ctx |
69+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:38:2:38:4 | ctx |
70+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:39:2:39:4 | ctx |
71+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:40:2:40:4 | ctx |
72+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:42:12:42:14 | ctx |
73+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:43:2:43:4 | ctx |
74+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:44:2:44:4 | ctx |
75+
| src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:48:16:48:18 | ctx |
76+
| src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:51:14:51:16 | ctx |
77+
| src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:54:16:54:18 | ctx |
4878
test_HeaderDefinition
4979
| src/koa.js:11:3:11:25 | this.se ... 1', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} |
5080
| src/koa.js:12:3:12:37 | this.re ... 2', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} |
5181
| src/koa.js:13:3:13:24 | ctx.set ... 3', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} |
5282
| src/koa.js:14:3:14:36 | ctx.res ... 4', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} |
5383
| src/koa.js:16:3:16:27 | rsp.hea ... 5', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} |
84+
| src/koa.js:39:2:39:15 | ctx.set('bar') | src/koa.js:30:10:45:1 | async c ... url);\\n} |
5485
test_RouteSetup_getServer
5586
| src/koa.js:8:1:8:18 | app2.use(handler1) | src/koa.js:5:12:5:20 | new Koa() |
5687
| src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | src/koa.js:5:12:5:20 | new Koa() |
88+
| src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | src/koa.js:5:12:5:20 | new Koa() |
89+
| src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | src/koa.js:5:12:5:20 | new Koa() |
5790
test_HeaderDefinition_getAHeaderName
5891
| src/koa.js:11:3:11:25 | this.se ... 1', '') | header1 |
5992
| src/koa.js:12:3:12:37 | this.re ... 2', '') | header2 |
@@ -64,23 +97,33 @@ test_HeaderAccess
6497
| src/koa.js:24:3:24:24 | ctx.req ... der.bar | bar |
6598
| src/koa.js:25:3:25:25 | ctx.req ... ers.bar | bar |
6699
| src/koa.js:26:3:26:24 | ctx.req ... ('bar') | bar |
100+
| src/koa.js:37:2:37:15 | ctx.header.bar | bar |
101+
| src/koa.js:38:2:38:16 | ctx.headers.bar | bar |
102+
| src/koa.js:40:2:40:15 | ctx.get('bar') | bar |
103+
| src/koa.js:55:2:55:12 | headers.foo | foo |
67104
test_RouteHandler_getAResponseExpr
68105
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:12:3:12:15 | this.response |
69106
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:14:3:14:14 | ctx.response |
70107
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:15:13:15:24 | ctx.response |
71108
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:16:3:16:5 | rsp |
72109
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:18:3:18:14 | ctx.response |
110+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:44:2:44:13 | ctx.response |
73111
test_ResponseSendArgument
74112
| src/koa.js:18:23:18:23 | x | src/koa.js:10:10:28:1 | functio ... az');\\n} |
113+
| src/koa.js:31:13:31:13 | x | src/koa.js:30:10:45:1 | async c ... url);\\n} |
75114
test_RouteSetup_getARouteHandler
76115
| src/koa.js:8:1:8:18 | app2.use(handler1) | src/koa.js:7:1:7:22 | functio ... r1() {} |
77116
| src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | src/koa.js:10:10:28:1 | functio ... az');\\n} |
117+
| src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | src/koa.js:30:10:45:1 | async c ... url);\\n} |
118+
| src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
78119
test_AppDefinition
79120
| src/koa.js:2:12:2:33 | new (re ... oa'))() |
80121
| src/koa.js:5:12:5:20 | new Koa() |
81122
test_RouteHandler
82123
| src/koa.js:7:1:7:22 | functio ... r1() {} | src/koa.js:5:12:5:20 | new Koa() |
83124
| src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:5:12:5:20 | new Koa() |
125+
| src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:5:12:5:20 | new Koa() |
126+
| src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:5:12:5:20 | new Koa() |
84127
test_RequestExpr
85128
| src/koa.js:19:3:19:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} |
86129
| src/koa.js:20:3:20:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} |
@@ -115,3 +158,22 @@ test_ContextExpr
115158
| src/koa.js:25:3:25:5 | ctx | src/koa.js:10:10:28:1 | functio ... az');\\n} |
116159
| src/koa.js:26:3:26:5 | ctx | src/koa.js:10:10:28:1 | functio ... az');\\n} |
117160
| src/koa.js:27:3:27:5 | ctx | src/koa.js:10:10:28:1 | functio ... az');\\n} |
161+
| src/koa.js:31:2:31:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
162+
| src/koa.js:32:2:32:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
163+
| src/koa.js:33:2:33:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
164+
| src/koa.js:34:2:34:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
165+
| src/koa.js:35:2:35:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
166+
| src/koa.js:36:2:36:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
167+
| src/koa.js:37:2:37:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
168+
| src/koa.js:38:2:38:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
169+
| src/koa.js:39:2:39:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
170+
| src/koa.js:40:2:40:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
171+
| src/koa.js:42:12:42:14 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
172+
| src/koa.js:43:2:43:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
173+
| src/koa.js:44:2:44:4 | ctx | src/koa.js:30:10:45:1 | async c ... url);\\n} |
174+
| src/koa.js:48:16:48:18 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
175+
| src/koa.js:51:14:51:16 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
176+
| src/koa.js:54:16:54:18 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} |
177+
test_RedirectInvocation
178+
| src/koa.js:43:2:43:18 | ctx.redirect(url) | src/koa.js:43:15:43:17 | url | src/koa.js:30:10:45:1 | async c ... url);\\n} |
179+
| src/koa.js:44:2:44:27 | ctx.res ... ct(url) | src/koa.js:44:24:44:26 | url | src/koa.js:30:10:45:1 | async c ... url);\\n} |

javascript/ql/test/library-tests/frameworks/koa/tests.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ import RouteHandler
1616
import RequestExpr
1717
import RouteHandler_getARequestExpr
1818
import ContextExpr
19+
import RedirectInvocation

0 commit comments

Comments
 (0)