diff --git a/docs/codeql/reusables/supported-frameworks.rst b/docs/codeql/reusables/supported-frameworks.rst index 472e463cf79b..930cdc6b629a 100644 --- a/docs/codeql/reusables/supported-frameworks.rst +++ b/docs/codeql/reusables/supported-frameworks.rst @@ -197,6 +197,7 @@ and the CodeQL library pack ``codeql/javascript-all`` (`changelog VercelRequest, NowResponse -> VercelResponse). +export default function handler(req: NowRequest, res: NowResponse) { + res.send(req.query.name); +} diff --git a/javascript/ql/test/library-tests/frameworks/vercel/src/vercel.ts b/javascript/ql/test/library-tests/frameworks/vercel/src/vercel.ts new file mode 100644 index 000000000000..0dae664e2c44 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/vercel/src/vercel.ts @@ -0,0 +1,27 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; + +// A private helper with the same signature. Must NOT be recognized as a +// route handler, since Vercel only invokes the default export. +function internalHelper(req: VercelRequest, res: VercelResponse) { + res.send(req.query.name); +} + +export default function handler(req: VercelRequest, res: VercelResponse) { + // Request inputs + const q = req.query; // source: parameter + const b = req.body; // source: body + const c = req.cookies; // source: cookie + const u = req.url; // source: url (inherited from IncomingMessage) + const host = req.headers.host; // source: header (named) + const ref = req.headers.referer; // source: header (named) + + // Response header definition + res.setHeader("Content-Type", "text/html"); + + // Response send (direct and chained) + res.send(q); + res.status(200).send(b); + + // Redirect + res.redirect(req.query.url as string); +} diff --git a/javascript/ql/test/library-tests/frameworks/vercel/tests.expected b/javascript/ql/test/library-tests/frameworks/vercel/tests.expected new file mode 100644 index 000000000000..a2929999f235 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/vercel/tests.expected @@ -0,0 +1,27 @@ +test_RouteHandler +| src/now.ts:5:16:7:1 | functio ... ame);\\n} | +| src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +test_RequestSource +| src/now.ts:5:33:5:35 | req | src/now.ts:5:16:7:1 | functio ... ame);\\n} | +| src/vercel.ts:9:33:9:35 | req | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +test_ResponseSource +| src/now.ts:5:50:5:52 | res | src/now.ts:5:16:7:1 | functio ... ame);\\n} | +| src/vercel.ts:9:53:9:55 | res | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:23:3:23:17 | res.status(200) | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +test_HeaderDefinition +| src/vercel.ts:19:3:19:44 | res.set ... /html") | content-type | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +test_RedirectInvocation +| src/vercel.ts:26:3:26:39 | res.red ... string) | src/vercel.ts:26:16:26:38 | req.que ... string | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +test_RequestInputAccess +| src/now.ts:6:12:6:20 | req.query | parameter | src/now.ts:5:16:7:1 | functio ... ame);\\n} | +| src/vercel.ts:11:13:11:21 | req.query | parameter | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:12:13:12:20 | req.body | body | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:13:13:13:23 | req.cookies | cookie | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:14:13:14:19 | req.url | url | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:15:16:15:31 | req.headers.host | header | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:16:15:16:33 | req.headers.referer | header | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:26:16:26:24 | req.query | parameter | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +test_ResponseSendArgument +| src/now.ts:6:12:6:25 | req.query.name | src/now.ts:5:16:7:1 | functio ... ame);\\n} | +| src/vercel.ts:22:12:22:12 | q | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | +| src/vercel.ts:23:24:23:24 | b | src/vercel.ts:9:16:27:1 | functio ... ing);\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/vercel/tests.ql b/javascript/ql/test/library-tests/frameworks/vercel/tests.ql new file mode 100644 index 000000000000..da4f5ff6c735 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/vercel/tests.ql @@ -0,0 +1,7 @@ +import RouteHandler +import RequestSource +import ResponseSource +import RequestInputAccess +import HeaderDefinition +import ResponseSendArgument +import RedirectInvocation diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected index f1d547bdfb1c..ddebc6baeaf3 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected @@ -110,6 +110,8 @@ | promisification.js:151:28:151:31 | code | promisification.js:141:18:141:25 | req.body | promisification.js:151:28:151:31 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | | promisification.js:152:25:152:28 | code | promisification.js:141:18:141:25 | req.body | promisification.js:152:25:152:28 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | | third-party-command-injection.js:6:21:6:27 | command | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | This command line depends on a $@. | third-party-command-injection.js:5:20:5:26 | command | user-provided value | +| vercel.ts:6:8:6:21 | "echo " + name | vercel.ts:5:16:5:24 | req.query | vercel.ts:6:8:6:21 | "echo " + name | This command line depends on a $@. | vercel.ts:5:16:5:24 | req.query | user-provided value | +| vercel.ts:6:8:6:21 | "echo " + name | vercel.ts:5:16:5:29 | req.query.name | vercel.ts:6:8:6:21 | "echo " + name | This command line depends on a $@. | vercel.ts:5:16:5:29 | req.query.name | user-provided value | edges | actions.js:8:9:8:13 | title | actions.js:9:16:9:20 | title | provenance | | | actions.js:8:17:8:57 | github. ... t.title | actions.js:8:9:8:13 | title | provenance | | @@ -340,6 +342,10 @@ edges | promisification.js:141:11:141:14 | code | promisification.js:152:25:152:28 | code | provenance | | | promisification.js:141:18:141:25 | req.body | promisification.js:141:11:141:14 | code | provenance | | | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | provenance | | +| vercel.ts:5:9:5:12 | name | vercel.ts:6:18:6:21 | name | provenance | | +| vercel.ts:5:16:5:24 | req.query | vercel.ts:5:9:5:12 | name | provenance | | +| vercel.ts:5:16:5:29 | req.query.name | vercel.ts:5:9:5:12 | name | provenance | | +| vercel.ts:6:18:6:21 | name | vercel.ts:6:8:6:21 | "echo " + name | provenance | | nodes | actions.js:8:9:8:13 | title | semmle.label | title | | actions.js:8:17:8:57 | github. ... t.title | semmle.label | github. ... t.title | @@ -591,6 +597,11 @@ nodes | promisification.js:152:25:152:28 | code | semmle.label | code | | third-party-command-injection.js:5:20:5:26 | command | semmle.label | command | | third-party-command-injection.js:6:21:6:27 | command | semmle.label | command | +| vercel.ts:5:9:5:12 | name | semmle.label | name | +| vercel.ts:5:16:5:24 | req.query | semmle.label | req.query | +| vercel.ts:5:16:5:29 | req.query.name | semmle.label | req.query.name | +| vercel.ts:6:8:6:21 | "echo " + name | semmle.label | "echo " + name | +| vercel.ts:6:18:6:21 | name | semmle.label | name | subpaths | promisification.js:116:32:116:34 | cmd | promisification.js:118:21:118:23 | cmd | promisification.js:117:29:117:35 | resolve [Return] [resolve-value] | promisification.js:117:16:119:10 | new Pro ... }) [PromiseValue] | | promisification.js:122:42:122:45 | code | promisification.js:116:32:116:34 | cmd | promisification.js:117:16:119:10 | new Pro ... }) [PromiseValue] | promisification.js:122:24:122:46 | createE ... e(code) [PromiseValue] | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/vercel.ts b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/vercel.ts new file mode 100644 index 000000000000..73754cb88181 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/vercel.ts @@ -0,0 +1,9 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import { exec } from "child_process"; + +export default function handler(req: VercelRequest, res: VercelResponse) { + const name = req.query.name as string; // $ Source + exec("echo " + name, (err, stdout) => { // $ Alert + res.send(stdout); + }); +} diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected index bb92027f9f75..8ee7067977cd 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected @@ -74,6 +74,8 @@ | tst2.js:113:12:113:17 | unsafe | tst2.js:105:9:105:9 | p | tst2.js:113:12:113:17 | unsafe | Cross-site scripting vulnerability due to a $@. | tst2.js:105:9:105:9 | p | user-provided value | | tst3.js:6:12:6:12 | p | tst3.js:5:9:5:9 | p | tst3.js:6:12:6:12 | p | Cross-site scripting vulnerability due to a $@. | tst3.js:5:9:5:9 | p | user-provided value | | tst3.js:12:12:12:15 | code | tst3.js:11:32:11:39 | reg.body | tst3.js:12:12:12:15 | code | Cross-site scripting vulnerability due to a $@. | tst3.js:11:32:11:39 | reg.body | user-provided value | +| vercel.ts:5:24:5:51 | `

${ ... }

` | vercel.ts:5:31:5:39 | req.query | vercel.ts:5:24:5:51 | `

${ ... }

` | Cross-site scripting vulnerability due to a $@. | vercel.ts:5:31:5:39 | req.query | user-provided value | +| vercel.ts:5:24:5:51 | `

${ ... }

` | vercel.ts:5:31:5:44 | req.query.name | vercel.ts:5:24:5:51 | `

${ ... }

` | Cross-site scripting vulnerability due to a $@. | vercel.ts:5:31:5:44 | req.query.name | user-provided value | edges | ReflectedXss.js:7:33:7:45 | req.params.id | ReflectedXss.js:7:14:7:45 | "Unknow ... rams.id | provenance | | | ReflectedXss.js:16:31:16:39 | params.id | ReflectedXss.js:16:12:16:39 | "Unknow ... rams.id | provenance | | @@ -259,6 +261,8 @@ edges | tst3.js:11:9:11:12 | code | tst3.js:12:12:12:15 | code | provenance | | | tst3.js:11:16:11:74 | prettie ... bel" }) | tst3.js:11:9:11:12 | code | provenance | | | tst3.js:11:32:11:39 | reg.body | tst3.js:11:16:11:74 | prettie ... bel" }) | provenance | | +| vercel.ts:5:31:5:39 | req.query | vercel.ts:5:24:5:51 | `

${ ... }

` | provenance | | +| vercel.ts:5:31:5:44 | req.query.name | vercel.ts:5:24:5:51 | `

${ ... }

` | provenance | | nodes | ReflectedXss.js:7:14:7:45 | "Unknow ... rams.id | semmle.label | "Unknow ... rams.id | | ReflectedXss.js:7:33:7:45 | req.params.id | semmle.label | req.params.id | @@ -497,5 +501,8 @@ nodes | tst3.js:11:16:11:74 | prettie ... bel" }) | semmle.label | prettie ... bel" }) | | tst3.js:11:32:11:39 | reg.body | semmle.label | reg.body | | tst3.js:12:12:12:15 | code | semmle.label | code | +| vercel.ts:5:24:5:51 | `

${ ... }

` | semmle.label | `

${ ... }

` | +| vercel.ts:5:31:5:39 | req.query | semmle.label | req.query | +| vercel.ts:5:31:5:44 | req.query.name | semmle.label | req.query.name | subpaths | ReflectedXssGood3.js:139:24:139:26 | url | ReflectedXssGood3.js:68:22:68:26 | value | ReflectedXssGood3.js:108:10:108:23 | parts.join('') | ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected index fa2886fb0cd3..a538fcd8ee70 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected @@ -72,3 +72,5 @@ | tst2.js:113:12:113:17 | unsafe | Cross-site scripting vulnerability due to $@. | tst2.js:105:9:105:9 | p | user-provided value | | tst3.js:6:12:6:12 | p | Cross-site scripting vulnerability due to $@. | tst3.js:5:9:5:9 | p | user-provided value | | tst3.js:12:12:12:15 | code | Cross-site scripting vulnerability due to $@. | tst3.js:11:32:11:39 | reg.body | user-provided value | +| vercel.ts:5:24:5:51 | `

${ ... }

` | Cross-site scripting vulnerability due to $@. | vercel.ts:5:31:5:39 | req.query | user-provided value | +| vercel.ts:5:24:5:51 | `

${ ... }

` | Cross-site scripting vulnerability due to $@. | vercel.ts:5:31:5:44 | req.query.name | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/vercel.ts b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/vercel.ts new file mode 100644 index 000000000000..dbd90171444d --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/vercel.ts @@ -0,0 +1,6 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; + +export default function handler(req: VercelRequest, res: VercelResponse) { + res.setHeader("Content-Type", "text/html"); + res.status(200).send(`

${req.query.name}

`); // $ Alert +} diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index f28fb93238d9..17d280d38096 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -156,6 +156,8 @@ | tst3.js:9:14:9:19 | query1 | tst3.js:8:16:8:34 | req.params.category | tst3.js:9:14:9:19 | query1 | This query string depends on a $@. | tst3.js:8:16:8:34 | req.params.category | user-provided value | | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | This query string depends on a $@. | tst4.js:8:46:8:60 | $routeParams.id | user-provided value | | tst.js:10:10:10:64 | 'SELECT ... d + '"' | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | This query string depends on a $@. | tst.js:10:46:10:58 | req.params.id | user-provided value | +| vercel.ts:7:14:7:51 | "SELECT ... " + id | vercel.ts:6:14:6:22 | req.query | vercel.ts:7:14:7:51 | "SELECT ... " + id | This query string depends on a $@. | vercel.ts:6:14:6:22 | req.query | user-provided value | +| vercel.ts:7:14:7:51 | "SELECT ... " + id | vercel.ts:6:14:6:25 | req.query.id | vercel.ts:7:14:7:51 | "SELECT ... " + id | This query string depends on a $@. | vercel.ts:6:14:6:25 | req.query.id | user-provided value | edges | athena.js:9:11:9:19 | userQuery | athena.js:14:30:14:38 | userQuery | provenance | | | athena.js:9:11:9:19 | userQuery | athena.js:24:22:24:30 | userQuery | provenance | | @@ -620,6 +622,10 @@ edges | tst3.js:8:16:8:34 | req.params.category | tst3.js:7:7:7:12 | query1 | provenance | | | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | provenance | | | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | provenance | | +| vercel.ts:6:9:6:10 | id | vercel.ts:7:50:7:51 | id | provenance | | +| vercel.ts:6:14:6:22 | req.query | vercel.ts:6:9:6:10 | id | provenance | | +| vercel.ts:6:14:6:25 | req.query.id | vercel.ts:6:9:6:10 | id | provenance | | +| vercel.ts:7:50:7:51 | id | vercel.ts:7:14:7:51 | "SELECT ... " + id | provenance | | nodes | athena.js:9:11:9:19 | userQuery | semmle.label | userQuery | | athena.js:9:23:9:30 | req.body | semmle.label | req.body | @@ -1029,4 +1035,9 @@ nodes | tst4.js:8:46:8:60 | $routeParams.id | semmle.label | $routeParams.id | | tst.js:10:10:10:64 | 'SELECT ... d + '"' | semmle.label | 'SELECT ... d + '"' | | tst.js:10:46:10:58 | req.params.id | semmle.label | req.params.id | +| vercel.ts:6:9:6:10 | id | semmle.label | id | +| vercel.ts:6:14:6:22 | req.query | semmle.label | req.query | +| vercel.ts:6:14:6:25 | req.query.id | semmle.label | req.query.id | +| vercel.ts:7:14:7:51 | "SELECT ... " + id | semmle.label | "SELECT ... " + id | +| vercel.ts:7:50:7:51 | id | semmle.label | id | subpaths diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/vercel.ts b/javascript/ql/test/query-tests/Security/CWE-089/untyped/vercel.ts new file mode 100644 index 000000000000..b511f107747f --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/vercel.ts @@ -0,0 +1,10 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +const mysql = require("mysql"); +const conn = mysql.createConnection({}); + +export default function handler(req: VercelRequest, res: VercelResponse) { + const id = req.query.id as string; // $ Source + conn.query("SELECT * FROM users WHERE id = " + id, (err: any, rows: any) => { // $ Alert + res.json(rows); + }); +} diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index 79383f585215..b3d939a30c5f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -40,6 +40,8 @@ | serverSide.js:145:5:145:25 | axios.g ... dedUrl) | serverSide.js:139:19:139:31 | req.query.url | serverSide.js:145:15:145:24 | encodedUrl | The $@ of this request depends on a $@. | serverSide.js:145:15:145:24 | encodedUrl | URL | serverSide.js:139:19:139:31 | req.query.url | user-provided value | | serverSide.js:147:5:147:25 | axios.g ... pedUrl) | serverSide.js:139:19:139:31 | req.query.url | serverSide.js:147:15:147:24 | escapedUrl | The $@ of this request depends on a $@. | serverSide.js:147:15:147:24 | escapedUrl | URL | serverSide.js:139:19:139:31 | req.query.url | user-provided value | | serverSide.js:151:1:151:15 | request(custom) | serverSide.js:150:16:150:51 | require ... ource() | serverSide.js:151:9:151:14 | custom | The $@ of this request depends on a $@. | serverSide.js:151:9:151:14 | custom | URL | serverSide.js:150:16:150:51 | require ... ource() | user-provided value | +| vercel.ts:5:26:5:35 | fetch(url) | vercel.ts:4:15:4:23 | req.query | vercel.ts:5:32:5:34 | url | The $@ of this request depends on a $@. | vercel.ts:5:32:5:34 | url | URL | vercel.ts:4:15:4:23 | req.query | user-provided value | +| vercel.ts:5:26:5:35 | fetch(url) | vercel.ts:4:15:4:27 | req.query.url | vercel.ts:5:32:5:34 | url | The $@ of this request depends on a $@. | vercel.ts:5:32:5:34 | url | URL | vercel.ts:4:15:4:27 | req.query.url | user-provided value | edges | Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | Request/app/api/proxy/route2.serverSide.ts:4:11:4:13 | url | provenance | | | Request/app/api/proxy/route2.serverSide.ts:4:11:4:13 | url | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | provenance | | @@ -147,6 +149,9 @@ edges | serverSide.js:146:31:146:35 | input | serverSide.js:146:24:146:36 | escape(input) | provenance | | | serverSide.js:150:7:150:12 | custom | serverSide.js:151:9:151:14 | custom | provenance | | | serverSide.js:150:16:150:51 | require ... ource() | serverSide.js:150:7:150:12 | custom | provenance | | +| vercel.ts:4:9:4:11 | url | vercel.ts:5:32:5:34 | url | provenance | | +| vercel.ts:4:15:4:23 | req.query | vercel.ts:4:9:4:11 | url | provenance | | +| vercel.ts:4:15:4:27 | req.query.url | vercel.ts:4:9:4:11 | url | provenance | | nodes | Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | semmle.label | { url } | | Request/app/api/proxy/route2.serverSide.ts:4:11:4:13 | url | semmle.label | url | @@ -277,4 +282,8 @@ nodes | serverSide.js:150:7:150:12 | custom | semmle.label | custom | | serverSide.js:150:16:150:51 | require ... ource() | semmle.label | require ... ource() | | serverSide.js:151:9:151:14 | custom | semmle.label | custom | +| vercel.ts:4:9:4:11 | url | semmle.label | url | +| vercel.ts:4:15:4:23 | req.query | semmle.label | req.query | +| vercel.ts:4:15:4:27 | req.query.url | semmle.label | req.query.url | +| vercel.ts:5:32:5:34 | url | semmle.label | url | subpaths diff --git a/javascript/ql/test/query-tests/Security/CWE-918/vercel.ts b/javascript/ql/test/query-tests/Security/CWE-918/vercel.ts new file mode 100644 index 000000000000..e383088489dc --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/vercel.ts @@ -0,0 +1,7 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; + +export default async function handler(req: VercelRequest, res: VercelResponse) { + const url = req.query.url as string; // $ Source[js/request-forgery] + const response = await fetch(url); // $ Alert[js/request-forgery] + res.json(await response.json()); +}