forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNext.qll
More file actions
354 lines (311 loc) · 11.7 KB
/
Next.qll
File metadata and controls
354 lines (311 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/**
* Provides classes and predicates for reasoning about [Next.js](https://www.npmjs.com/package/next).
*/
import javascript
/**
* Provides classes and predicates modeling [Next.js](https://www.npmjs.com/package/next).
*/
module NextJS {
/**
* Gets a `package.json` that depends on the `Next.js` library.
*/
PackageJson getANextPackage() { result.getDependencies().getADependency("next", _) }
bindingset[base, name]
pragma[inline_late]
private Folder getOptionalFolder(Folder base, string name) {
result = base.getFolder(name)
or
not exists(base.getFolder(name)) and
result = base
}
private Folder packageRoot() { result = getANextPackage().getFile().getParentContainer() }
private Folder srcRoot() { result = getOptionalFolder(packageRoot(), "src") }
private Folder appRoot() { result = srcRoot().getFolder("app") }
private Folder pagesRoot() { result = [srcRoot(), appRoot()].getFolder("pages") }
private Folder apiRoot() { result = [pagesRoot(), appRoot()].getFolder("api") }
/**
* Gets a "pages" folder in a `Next.js` application.
* JavaScript files inside these folders are mapped to routes.
*/
Folder getAPagesFolder() {
result = pagesRoot()
or
result = getAPagesFolder().getAFolder()
}
/**
* Gets a module corrosponding to a `Next.js` page.
*/
Module getAPagesModule() { result.getFile().getParentContainer() = getAPagesFolder() }
/**
* Gets a module inside a "pages" folder where `fallback` from `getStaticPaths` is not set to false.
* In such a module the `getStaticProps` method can be called with user-defined parameters.
* If `fallback` is set to false, then only values defined by `getStaticPaths` are allowed.
*/
Module getAModuleWithFallbackPaths() {
result = getAPagesModule() and
exists(DataFlow::FunctionNode staticPaths, DataFlow::Node fallback |
staticPaths = result.getAnExportedValue("getStaticPaths").getAFunctionValue() and
fallback = staticPaths.getAReturn().getALocalSource().getAPropertyWrite("fallback").getRhs() and
not fallback.mayHaveBooleanValue(false)
)
}
/**
* A user defined path or query parameter in `Next.js`.
*/
class NextParams extends RemoteFlowSource {
NextParams() {
this =
getAModuleWithFallbackPaths()
.getAnExportedValue("getStaticProps")
.getAFunctionValue()
.getParameter(0)
.getAPropertyRead("params")
or
this = getServerSidePropsFunction(_).getParameter(0).getAPropertyRead(["params", "query"])
or
this = nextRouter().getAPropertyRead("query")
}
override string getSourceType() { result = "Next request parameter" }
}
/**
* Gets the `getStaticProps` function in a Next.js page.
* This function is executed at build time, or when a page with a new URL is requested for the first time (if `fallback` is not false).
*/
DataFlow::FunctionNode getStaticPropsFunction(Module pageModule) {
pageModule = getAPagesModule() and
result = pageModule.getAnExportedValue("getStaticProps").getAFunctionValue()
}
/**
* Gets the `getServerSideProps` function in a Next.js page.
* This function is executed on the server every time a request for the page is made.
* The function receives a context parameter, which includes HTTP request/response objects.
*/
DataFlow::FunctionNode getServerSidePropsFunction(Module pageModule) {
pageModule = getAPagesModule() and
result = pageModule.getAnExportedValue("getServerSideProps").getAFunctionValue()
}
/**
* Gets the `getInitialProps` function in a Next.js page.
* This function is executed on the server every time a request for the page is made.
* The function receives a context parameter, which includes HTTP request/response objects.
*/
DataFlow::FunctionNode getInitialProps(Module pageModule) {
pageModule = getAPagesModule() and
(
result =
pageModule
.getAnExportedValue("default")
.getAFunctionValue()
.getAPropertyWrite("getInitialProps")
.getRhs()
.getAFunctionValue()
or
result =
pageModule
.getAnExportedValue("default")
.getALocalSource()
.getAstNode()
.(ReactComponent)
.getStaticMethod("getInitialProps")
.flow()
)
}
/**
* Gets a reference to a `props` object computed by the Next.js server.
* This `props` object is both used both by the server and client to render the page.
*/
DataFlow::Node getAPropsSource(Module pageModule) {
pageModule = getAPagesModule() and
(
result =
[getStaticPropsFunction(pageModule), getServerSidePropsFunction(pageModule)]
.getAReturn()
.getALocalSource()
.getAPropertyWrite("props")
.getRhs()
or
result = getInitialProps(pageModule).getAReturn()
)
}
/**
* A step modeling the flow from the server-computed props object to the default exported function that renders the page.
*/
class NextJSStaticPropsStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(Module pageModule, DataFlow::FunctionNode function |
pageModule = getAPagesModule() and
function = pageModule.getAnExportedValue("default").getAFunctionValue() and
pred = getAPropsSource(pageModule) and
succ = function.getParameter(0)
)
}
}
/**
* A step modeling the flow from the server-computed props object to the default exported React component that renders the page.
*/
class NextJSStaticReactComponentPropsStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(Module pageModule, ReactComponent component |
pageModule = getAPagesModule() and
pageModule.getAnExportedValue("default").getALocalSource() = DataFlow::valueNode(component) and
pred = getAPropsSource(pageModule) and
succ = component.getADirectPropsAccess()
)
}
}
/**
* A Next.js function that is exected on the server for every request, seen as a routehandler.
*/
class NextHttpRouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
NextHttpRouteHandler() { this = getServerSidePropsFunction(_) or this = getInitialProps(_) }
}
/**
* A function that handles both a request and response from Next.js, seen as a routehandler.
*/
class NextReqResHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
DataFlow::ParameterNode req;
DataFlow::ParameterNode res;
NextReqResHandler() {
res = this.getAParameter() and
req = this.getAParameter() and
req.hasUnderlyingType("next", "NextApiRequest") and
res.hasUnderlyingType("next", "NextApiResponse")
}
/** Gets the request parameter */
DataFlow::ParameterNode getRequest() { result = req }
/** Gets the response parameter */
DataFlow::ParameterNode getResponse() { result = res }
}
/**
* A NodeJS HTTP request object in a Next.js page.
*/
class NextHttpRequestSource extends NodeJSLib::RequestSource {
Http::RouteHandler rh;
NextHttpRequestSource() {
this = rh.(NextHttpRouteHandler).getParameter(0).getAPropertyRead("req") or
this = rh.(NextReqResHandler).getRequest()
}
override Http::RouteHandler getRouteHandler() { result = rh }
}
/**
* A NodeJS HTTP response object in a Next.js page.
*/
class NextHttpResponseSource extends NodeJSLib::ResponseSource {
Http::RouteHandler rh;
NextHttpResponseSource() {
this = rh.(NextHttpRouteHandler).getParameter(0).getAPropertyRead("res") or
this = rh.(NextReqResHandler).getResponse()
}
override Http::RouteHandler getRouteHandler() { result = rh }
}
/**
* Gets a folder that contains API endpoints for a Next.js application.
* These API endpoints act as Express-like route-handlers.
* It matches both the Pages Router (`pages/api/`) Next.js 12 or earlier and
* the App Router (`app/api/`) Next.js 13+ structures.
*/
Folder apiFolder() {
result = apiRoot() or
result = apiFolder().getAFolder()
}
/**
* A Next.js route handler for an API endpoint.
* The response (res) includes a set of Express.js-like methods,
* and we therefore model the routehandler as an Express.js routehandler.
*/
class NextApiRouteHandler extends DataFlow::FunctionNode, Express::RouteHandler,
Http::Servers::StandardRouteHandler
{
NextApiRouteHandler() {
exists(Module mod | mod.getFile().getParentContainer() = apiFolder() |
this = mod.getAnExportedValue("default").getAFunctionValue()
)
}
override DataFlow::ParameterNode getRouteHandlerParameter(string kind) {
kind = "request" and result = this.getParameter(0)
or
kind = "response" and result = this.getParameter(1)
}
}
/**
* Gets a reference to a [Next.js router](https://nextjs.org/docs/api-reference/next/router).
*/
DataFlow::SourceNode nextRouter() {
result = API::moduleImport("next/router").getMember("useRouter").getACall()
or
result =
API::moduleImport("next/router")
.getMember("withRouter")
.getParameter(0)
.getParameter(0)
.getMember("router")
.asSource()
}
/**
* Provides classes and predicates modeling the `next-auth` library.
*/
private module NextAuth {
/**
* A random string used to hash tokens, sign cookies and generate cryptographic keys as a `CredentialsNode`.
*/
private class SecretKey extends CredentialsNode {
SecretKey() {
this = API::moduleImport("next-auth").getParameter(0).getMember("secret").asSink()
}
override string getCredentialsKind() { result = "jwt key" }
}
}
/**
* A route handler for Next.js 13+ App Router API endpoints, which are defined by exporting
* HTTP method functions (like `GET`, `POST`, `PUT`, `DELETE`) from route.js files inside
* the `app/api/` directory.
*/
class NextAppRouteHandler extends DataFlow::FunctionNode, Http::Servers::StandardRouteHandler {
NextAppRouteHandler() {
exists(Module mod |
mod.getFile().getParentContainer() = apiFolder() or
mod.getFile().getStem() = "middleware"
|
this =
mod.getAnExportedValue([any(Http::RequestMethodName m), "middleware"]).getAFunctionValue()
)
}
/**
* Gets the request parameter, which is either a `NextRequest` object (from `next/server`) or a standard web `Request` object.
*/
DataFlow::SourceNode getRequest() { result = this.getParameter(0) }
}
/**
* A source of user-controlled data from a `NextRequest` object (from `next/server`) or a standard web `Request` object
* in a Next.js App Router route handler.
*/
class NextAppRequestSource extends Http::RequestInputAccess {
NextAppRouteHandler handler;
string kind;
NextAppRequestSource() {
(
this =
handler.getRequest().getAMethodCall(["json", "formData", "blob", "arrayBuffer", "text"])
or
this = handler.getRequest().getAPropertyRead("body")
) and
kind = "body"
or
this = handler.getRequest().getAPropertyRead(["url", "nextUrl"]) and
kind = "url"
or
this =
handler
.getRequest()
.getAPropertyRead("nextUrl")
.getAPropertyRead("searchParams")
.getAMemberCall("get") and
kind = "parameter"
or
this = handler.getRequest().getAPropertyRead("headers") and kind = "headers"
}
override string getKind() { result = kind }
override Http::RouteHandler getRouteHandler() { result = handler }
override string getSourceType() { result = "Next.js App Router request" }
}
}