Files
codeql/javascript/ql/lib/semmle/javascript/frameworks/Next.qll
2026-01-13 10:34:25 +01:00

362 lines
12 KiB
Plaintext

/**
* 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", _) }
private Folder packageRoot() { result = getANextPackage().getFile().getParentContainer() }
private Folder srcRoot() { result = [packageRoot(), packageRoot().getFolder("src")] }
private Folder appRoot() { result = srcRoot().getFolder("app") }
private Folder pagesRoot() { result = [srcRoot(), appRoot()].getFolder("pages") }
private Folder apiRoot() { result = [pagesRoot(), appRoot()].getFolder("api") }
private Folder appFolder() {
result = appRoot()
or
result = appFolder().getAFolder()
}
private Folder pagesFolder() {
result = pagesRoot()
or
result = pagesFolder().getAFolder()
}
/**
* Gets a "pages" folder in a `Next.js` application.
* JavaScript files inside these folders are mapped to routes.
*/
deprecated predicate getAPagesFolder = pagesFolder/0;
/**
* Gets a module corresponding to a `Next.js` page.
*/
Module getAPagesModule() {
result.getFile() = [pagesFolder().getAFile(), appFolder().getJavaScriptFile("page")]
}
/**
* 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"
or
mod.getFile().getStem() = "route" and mod.getFile().getParentContainer() = appFolder()
)
|
this =
mod.getAnExportedValue([any(Http::RequestMethodName m), "middleware", "proxy"])
.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" }
}
}