Skip to content

Commit dfe0559

Browse files
murderteethclaude
andcommitted
JS: Add support for @vercel/node serverless functions
This adds a framework model for Vercel serverless functions so that CodeQL's existing JavaScript security queries can detect vulnerabilities in handlers of the form export default function handler(req: VercelRequest, res: VercelResponse) { ... } Handlers are identified as the default export of a module whose first two parameters are typed as `VercelRequest`/`VercelResponse` from `@vercel/node`. The default-export constraint excludes private helpers that share the same signature. Type-based detection follows the same pattern already used by `NextReqResHandler` in `Next.qll`. The framework model covers: - Route handler recognition (default-exported typed handlers only) - Request input sources: `query`, `body`, `cookies`, and `url` (the last inherited from Node's `IncomingMessage`) - Named header accesses like `req.headers.host` and `req.headers.referer`, modelled as `Http::RequestHeaderAccess` so header-specific queries fire - Response sinks: `res.send`, `res.status(...).send`, `res.redirect` - Header definitions via `res.setHeader` Includes a library test exercising each model predicate (including a negative case for private helpers) and query consistency fixtures demonstrating end-to-end detection for js/reflected-xss, js/request-forgery, js/sql-injection, and js/command-line-injection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ae85ada commit dfe0559

24 files changed

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