JS: Add support for @vercel/node serverless functions#21697
JS: Add support for @vercel/node serverless functions#21697murderteeth wants to merge 1 commit intogithub:mainfrom
Conversation
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>
There was a problem hiding this comment.
Pull request overview
Adds a new JavaScript framework model for @vercel/node Vercel serverless functions so existing CodeQL HTTP/dataflow-based security queries can recognize Vercel API handlers and treat req/res operations as sources/sinks.
Changes:
- Introduces
VercelNodeframework modeling library and wires it intojavascript.qll. - Adds library-tests for the new framework model plus several query-test fixtures/expected updates to demonstrate end-to-end findings.
- Updates changelog/change-note and supported-frameworks documentation to reflect the new support.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| javascript/ql/lib/semmle/javascript/frameworks/VercelNode.qll | Adds the Vercel Node framework model (route handler identification + HTTP sources/sinks). |
| javascript/ql/lib/javascript.qll | Imports the new VercelNode framework model so it’s included in the JS library pack. |
| javascript/ql/lib/change-notes/2026-04-12-vercel-node.md | Announces the new @vercel/node support in the JS library pack change notes. |
| docs/codeql/reusables/supported-frameworks.rst | Documents the new supported framework entry. |
| javascript/ql/test/library-tests/frameworks/vercel/tests.ql | Aggregates the Vercel framework library-tests. |
| javascript/ql/test/library-tests/frameworks/vercel/tests.expected | Expected results for the Vercel framework library-tests. |
| javascript/ql/test/library-tests/frameworks/vercel/src/vercel.ts | Positive test case covering request inputs and response APIs. |
| javascript/ql/test/library-tests/frameworks/vercel/src/notahandler.ts | Negative test case ensuring non-matching parameter positions don’t qualify as a handler. |
| javascript/ql/test/library-tests/frameworks/vercel/RouteHandler.qll | Library-test query for VercelNode::RouteHandler. |
| javascript/ql/test/library-tests/frameworks/vercel/RequestSource.qll | Library-test query for request parameter source modeling. |
| javascript/ql/test/library-tests/frameworks/vercel/ResponseSource.qll | Library-test query for response parameter/chained response source modeling. |
| javascript/ql/test/library-tests/frameworks/vercel/RequestInputAccess.qll | Library-test query for request input access kinds (query/body/cookies/url/headers). |
| javascript/ql/test/library-tests/frameworks/vercel/HeaderDefinition.qll | Library-test query for response header definition modeling. |
| javascript/ql/test/library-tests/frameworks/vercel/ResponseSendArgument.qll | Library-test query for res.send(...) sink modeling. |
| javascript/ql/test/library-tests/frameworks/vercel/RedirectInvocation.qll | Library-test query for res.redirect(...) modeling. |
| javascript/ql/test/query-tests/Security/CWE-918/vercel.ts | Query-test fixture demonstrating request-forgery detection in a Vercel handler. |
| javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected | Updated expected results including the new Vercel fixture findings. |
| javascript/ql/test/query-tests/Security/CWE-089/untyped/vercel.ts | Query-test fixture demonstrating SQL injection detection in a Vercel handler. |
| javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected | Updated expected results including the new Vercel fixture findings. |
| javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/vercel.ts | Query-test fixture demonstrating reflected XSS detection in a Vercel handler. |
| javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected | Updated expected results including the new Vercel fixture findings. |
| javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected | Updated expected results including the new Vercel fixture findings. |
| javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/vercel.ts | Query-test fixture demonstrating command injection detection in a Vercel handler. |
| javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected | Updated expected results including the new Vercel fixture findings. |
| * `VercelResponse` from `@vercel/node`. | ||
| * | ||
| * Since `@vercel/node` is commonly imported as a type-only import, handlers | ||
| * are recognised by their TypeScript parameter types. The default-export |
There was a problem hiding this comment.
Spelling consistency: this file uses British spelling (“recognised”), but the surrounding JavaScript framework libraries consistently use US spelling (“recognized”). Please change to “recognized” here (and keep wording consistent across related docs/tests).
| * are recognised by their TypeScript parameter types. The default-export | |
| * are recognized by their TypeScript parameter types. The default-export |
| * with signature `(req: VercelRequest, res: VercelResponse) => void`, where | ||
| * the types are imported from the `@vercel/node` package. The Vercel runtime | ||
| * invokes the default export for every incoming HTTP request. |
There was a problem hiding this comment.
The documentation comment says the handler signature returns void, but handlers can be async/return a Promise (and the PR’s own fixtures include an async function handler). Please update the comment to avoid implying a synchronous-only handler.
| * with signature `(req: VercelRequest, res: VercelResponse) => void`, where | |
| * the types are imported from the `@vercel/node` package. The Vercel runtime | |
| * invokes the default export for every incoming HTTP request. | |
| * taking parameters `(req: VercelRequest, res: VercelResponse)`, where the | |
| * types are imported from the `@vercel/node` package. The default export may | |
| * be synchronous or `async`, and the Vercel runtime invokes it for every | |
| * incoming HTTP request. |
| --- | ||
| category: newFeature | ||
| --- | ||
| * 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. |
There was a problem hiding this comment.
Spelling: change “recognised” to “recognized” for consistency with other JavaScript change notes (which use US spelling).
| * 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. | |
| * 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. |
| @@ -0,0 +1,27 @@ | |||
| import type { VercelRequest, VercelResponse } from "@vercel/node"; | |||
|
|
|||
| // A private helper with the same signature. Must NOT be recognised as a | |||
There was a problem hiding this comment.
Spelling in comment: change “recognised” to “recognized” to match the rest of the JavaScript codebase’s comment spelling.
| // A private helper with the same signature. Must NOT be recognised as a | |
| // A private helper with the same signature. Must NOT be recognized as a |
|
|
||
| // A default-exported function that has VercelRequest/VercelResponse at | ||
| // positions 1 and 2, not 0 and 1. Vercel does not invoke it this way, | ||
| // so it must NOT be recognised as a route handler. |
There was a problem hiding this comment.
Spelling in comment: change “recognised” to “recognized” to match the rest of the JavaScript codebase’s comment spelling.
| // so it must NOT be recognised as a route handler. | |
| // so it must NOT be recognized as a route handler. |
| superagent, Network communicator | ||
| swig, templating language | ||
| underscore, Utility library | ||
| vercel, Serverless framework |
There was a problem hiding this comment.
This row says “vercel”, but the added modeling is specifically for @vercel/node serverless functions. Consider renaming the entry to be more specific (for example, “Vercel (@vercel/node)” or similar) to avoid implying broader Vercel platform coverage.
| vercel, Serverless framework | |
| Vercel (@vercel/node), Serverless framework |
asgerf
left a comment
There was a problem hiding this comment.
Thanks! Simple and clean. I have one potential improvement, otherwise looks good.
| req.hasUnderlyingType("@vercel/node", "VercelRequest") and | ||
| res.hasUnderlyingType("@vercel/node", "VercelResponse") |
There was a problem hiding this comment.
| req.hasUnderlyingType("@vercel/node", "VercelRequest") and | |
| res.hasUnderlyingType("@vercel/node", "VercelResponse") | |
| req.hasUnderlyingType(["@vercel/node", "@now/node"], ["NowRequest", "VercelRequest"]) and | |
| res.hasUnderlyingType(["@vercel/node", "@now/node"], ["NowResponse", "VercelResponse"]) |
After testing this on some code in the wild, it seems there are some deprecated aliases for these types worth covering here.
Adds a JavaScript framework model for
@vercel/nodeVercel serverless functions, so CodeQL's existing security queries can detect vulnerabilities in handlers ofthe form:
Since
@vercel/nodeis almost always imported asimport type, handlers are identified by their TypeScript parameter types (same approach asNextReqResHandlerinNext.qll). TheRouteHandlerclassadditionally requires the function to be a default export with
VercelRequest/VercelResponseat parameter positions 0 and 1, which excludes private helpers and misordered signatures.What the model teaches CodeQL
VercelNode::RouteHandlerVercelRequestat arg 0 andVercelResponseat arg 1Http::Servers::RequestSourcereqparameterHttp::Servers::ResponseSourceresparameter, plus chainedres.status()/res.type()/res.set()Http::RequestInputAccessreq.query(parameter),req.body(body),req.cookies(cookie),req.url(url, inherited fromIncomingMessage)Http::RequestHeaderAccessreq.headers.host,req.headers.referer, etc. — needed for host-header-poisoning and referer-XSS queriesHttp::ResponseSendArgumentres.send(...)andres.status(...).send(...)Http::RedirectInvocationres.redirect(...)Http::ExplicitHeaderDefinitionres.setHeader(name, value)Testing
Library test —
javascript/ql/test/library-tests/frameworks/vercel/Exercises every predicate and includes two negative cases:
internalHelper— same signature but not a default export → must not matchnotAHandler— default-exported withVercelRequest/VercelResponseat positions 1 and 2 (not 0 and 1) → must not matchQuery-consistency fixtures — one minimal handler per query, demonstrating end-to-end detection through the stock queries:
js/reflected-xssjs/request-forgeryjs/sql-injectionjs/command-line-injectionAll tests pass locally against CodeQL CLI 2.25.0.
Files
javascript/ql/lib/semmle/javascript/frameworks/VercelNode.qlljavascript/ql/lib/change-notes/2026-04-12-vercel-node.md(category: newFeature)javascript/ql/lib/javascript.qll(oneimportline)javascript/ql/test/library-tests/frameworks/vercel/(8 files)vercel.tsquery-test fixtures +.expectedupdatesdocs/codeql/reusables/supported-frameworks.rst(+1 row)Notes for reviewers
@vercel/nodehas no meaningful value-level API that handlers import, so a YAML data extension model wasn't viable. A minimal QL library following the established framework-model pattern is the right fit.req.urlas a source, (3) adding named-header access as anHttp::RequestHeaderAccess, and (4) enforcing that the typed parameters are at positions 0 and 1.