Skip to content

Commit c95083b

Browse files
authored
Merge pull request #21697 from yearn/js/vercel-node-framework
JS: Add support for @vercel/node serverless functions
2 parents c2beef1 + 6f77447 commit c95083b

26 files changed

Lines changed: 406 additions & 0 deletions

docs/codeql/reusables/supported-frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ and the CodeQL library pack ``codeql/javascript-all`` (`changelog <https://githu
197197
superagent, Network communicator
198198
swig, templating language
199199
underscore, Utility library
200+
Vercel (@vercel/node), Serverless framework
200201
vue, HTML framework
201202

202203

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: feature
3+
---
4+
* Added support for [`@vercel/node`](https://www.npmjs.com/package/@vercel/node) Vercel serverless functions. Handlers are recognized via the `VercelRequest`/`VercelResponse` TypeScript parameter types, and standard security queries (`js/reflected-xss`, `js/request-forgery`, `js/sql-injection`, `js/command-line-injection`, etc.) now detect vulnerabilities in Vercel API route files.

javascript/ql/lib/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ import semmle.javascript.frameworks.TorrentLibraries
134134
import semmle.javascript.frameworks.Typeahead
135135
import semmle.javascript.frameworks.TrustedTypes
136136
import semmle.javascript.frameworks.UriLibraries
137+
import semmle.javascript.frameworks.VercelNode
137138
import semmle.javascript.frameworks.Vue
138139
import semmle.javascript.frameworks.Vuex
139140
import semmle.javascript.frameworks.Webix
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/**
2+
* Provides classes for working with [@vercel/node](https://www.npmjs.com/package/@vercel/node) Vercel serverless functions.
3+
*/
4+
5+
import javascript
6+
import semmle.javascript.frameworks.HTTP
7+
8+
/**
9+
* Provides classes for working with [@vercel/node](https://www.npmjs.com/package/@vercel/node) Vercel serverless functions.
10+
*
11+
* A Vercel serverless function is a module whose default export is a function
12+
* taking parameters `(req: VercelRequest, res: VercelResponse)`, where the
13+
* types are imported from the `@vercel/node` package. The default export may
14+
* be synchronous or `async`, and the Vercel runtime invokes it for every
15+
* incoming HTTP request.
16+
*/
17+
module VercelNode {
18+
/**
19+
* A Vercel serverless function handler, identified as the default export of a
20+
* module whose first two parameters are typed as `VercelRequest` and
21+
* `VercelResponse` from `@vercel/node`.
22+
*
23+
* Since `@vercel/node` is commonly imported as a type-only import, handlers
24+
* are recognized by their TypeScript parameter types. The default-export
25+
* constraint excludes private helpers or test utilities that share the
26+
* same signature.
27+
*/
28+
class RouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
29+
DataFlow::ParameterNode req;
30+
DataFlow::ParameterNode res;
31+
32+
RouteHandler() {
33+
this = any(Module m).getAnExportedValue("default").getAFunctionValue() and
34+
req = this.getParameter(0) and
35+
res = this.getParameter(1) and
36+
req.hasUnderlyingType(["@vercel/node", "@now/node"], ["NowRequest", "VercelRequest"]) and
37+
res.hasUnderlyingType(["@vercel/node", "@now/node"], ["NowResponse", "VercelResponse"])
38+
}
39+
40+
/** Gets the parameter that contains the request object. */
41+
DataFlow::ParameterNode getRequest() { result = req }
42+
43+
/** Gets the parameter that contains the response object. */
44+
DataFlow::ParameterNode getResponse() { result = res }
45+
}
46+
47+
/**
48+
* A Vercel request source, that is, the request parameter of a route handler.
49+
*/
50+
private class RequestSource extends Http::Servers::RequestSource {
51+
RouteHandler rh;
52+
53+
RequestSource() { this = rh.getRequest() }
54+
55+
override RouteHandler getRouteHandler() { result = rh }
56+
}
57+
58+
/**
59+
* A Vercel response source, that is, the response parameter of a route handler.
60+
*/
61+
private class ResponseSource extends Http::Servers::ResponseSource {
62+
RouteHandler rh;
63+
64+
ResponseSource() { this = rh.getResponse() }
65+
66+
override RouteHandler getRouteHandler() { result = rh }
67+
}
68+
69+
/**
70+
* A chained response, such as `res.status(200)`, `res.type('html')`, or `res.set(...)`.
71+
*
72+
* These methods return the response object and are commonly chained before `send` or `json`.
73+
*/
74+
private class ChainedResponseSource extends Http::Servers::ResponseSource {
75+
RouteHandler rh;
76+
77+
ChainedResponseSource() {
78+
exists(ResponseSource src |
79+
this = src.ref().getAMethodCall(["status", "type", "set"]) and
80+
rh = src.getRouteHandler()
81+
)
82+
}
83+
84+
override RouteHandler getRouteHandler() { result = rh }
85+
}
86+
87+
/**
88+
* An access to user-controlled input on a Vercel request.
89+
*
90+
* Covers `req.query`, `req.body`, `req.cookies`, and `req.url` (inherited
91+
* from Node's `IncomingMessage`). Named-header accesses like `req.headers.host`
92+
* are handled by `RequestHeaderAccess` below.
93+
*/
94+
private class RequestInputAccess extends Http::RequestInputAccess {
95+
RouteHandler rh;
96+
string kind;
97+
98+
RequestInputAccess() {
99+
exists(RequestSource src | rh = src.getRouteHandler() |
100+
this = src.ref().getAPropertyRead("query") and kind = "parameter"
101+
or
102+
this = src.ref().getAPropertyRead("body") and kind = "body"
103+
or
104+
this = src.ref().getAPropertyRead("cookies") and kind = "cookie"
105+
or
106+
this = src.ref().getAPropertyRead("url") and kind = "url"
107+
)
108+
or
109+
exists(RequestHeaderAccess access | this = access |
110+
rh = access.getRouteHandler() and
111+
kind = "header"
112+
)
113+
}
114+
115+
override RouteHandler getRouteHandler() { result = rh }
116+
117+
override string getKind() { result = kind }
118+
}
119+
120+
/**
121+
* An access to a named header on a Vercel request, for example
122+
* `req.headers.host` or `req.headers.referer`.
123+
*/
124+
private class RequestHeaderAccess extends Http::RequestHeaderAccess {
125+
RouteHandler rh;
126+
127+
RequestHeaderAccess() {
128+
exists(RequestSource src |
129+
this = src.ref().getAPropertyRead("headers").getAPropertyRead() and
130+
rh = src.getRouteHandler()
131+
)
132+
}
133+
134+
override string getAHeaderName() {
135+
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
136+
}
137+
138+
override RouteHandler getRouteHandler() { result = rh }
139+
140+
override string getKind() { result = "header" }
141+
}
142+
143+
/**
144+
* An argument to `res.send(...)`, `res.json(...)`, or `res.jsonp(...)` on a
145+
* Vercel response, including chained calls such as `res.status(200).json(...)`.
146+
*/
147+
private class ResponseSendArgument extends Http::ResponseSendArgument {
148+
RouteHandler rh;
149+
150+
ResponseSendArgument() {
151+
exists(Http::Servers::ResponseSource src |
152+
(src instanceof ResponseSource or src instanceof ChainedResponseSource) and
153+
this = src.ref().getAMethodCall(["send", "json", "jsonp"]).getArgument(0) and
154+
rh = src.getRouteHandler()
155+
)
156+
}
157+
158+
override RouteHandler getRouteHandler() { result = rh }
159+
}
160+
161+
/**
162+
* A call to `res.redirect(...)` on a Vercel response.
163+
*/
164+
private class RedirectInvocation extends Http::RedirectInvocation, DataFlow::MethodCallNode {
165+
RouteHandler rh;
166+
167+
RedirectInvocation() {
168+
exists(ResponseSource src |
169+
this = src.ref().getAMethodCall("redirect") and
170+
rh = src.getRouteHandler()
171+
)
172+
}
173+
174+
override DataFlow::Node getUrlArgument() { result = this.getLastArgument() }
175+
176+
override RouteHandler getRouteHandler() { result = rh }
177+
}
178+
179+
/**
180+
* A call to `res.setHeader(name, value)` on a Vercel response.
181+
*/
182+
private class SetHeader extends Http::ExplicitHeaderDefinition, DataFlow::CallNode {
183+
RouteHandler rh;
184+
185+
SetHeader() {
186+
exists(ResponseSource src |
187+
this = src.ref().getAMethodCall("setHeader") and
188+
rh = src.getRouteHandler()
189+
)
190+
}
191+
192+
override RouteHandler getRouteHandler() { result = rh }
193+
194+
override predicate definesHeaderValue(string headerName, DataFlow::Node headerValue) {
195+
headerName = this.getArgument(0).getStringValue().toLowerCase() and
196+
headerValue = this.getArgument(1)
197+
}
198+
199+
override DataFlow::Node getNameNode() { result = this.getArgument(0) }
200+
}
201+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition(
4+
Http::HeaderDefinition hd, string name, VercelNode::RouteHandler rh
5+
) {
6+
hd.getRouteHandler() = rh and name = hd.getAHeaderName()
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import javascript
2+
3+
query predicate test_RedirectInvocation(
4+
Http::RedirectInvocation call, DataFlow::Node url, VercelNode::RouteHandler rh
5+
) {
6+
call.getRouteHandler() = rh and url = call.getUrlArgument()
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import javascript
2+
3+
query predicate test_RequestInputAccess(
4+
Http::RequestInputAccess ria, string kind, VercelNode::RouteHandler rh
5+
) {
6+
ria.getRouteHandler() = rh and kind = ria.getKind()
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_RequestSource(Http::Servers::RequestSource src, VercelNode::RouteHandler rh) {
4+
src.getRouteHandler() = rh
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import javascript
2+
3+
query predicate test_ResponseSendArgument(
4+
Http::ResponseSendArgument arg, VercelNode::RouteHandler rh
5+
) {
6+
arg.getRouteHandler() = rh
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_ResponseSource(Http::Servers::ResponseSource src, VercelNode::RouteHandler rh) {
4+
src.getRouteHandler() = rh
5+
}

0 commit comments

Comments
 (0)