Skip to content

Commit 9522ce7

Browse files
anttiviljamiclaude
andcommitted
JS: Add models-as-data for openapi-backend
Mark `context.request` in openapi-backend operation handlers, security handlers, lifecycle handlers and `validate` predicates as a remote flow source, covering handlers passed to the constructor, `register()`, `registerHandler()`, `registerSecurityHandler()`, the awaited result of `init()`, and TypeScript handlers typed with `Context`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent b07d44a commit 9522ce7

6 files changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added support for the `openapi-backend` package. In particular, `context.request` in operation handlers, security handlers, lifecycle handlers and `validate` predicates is now seen as a source of untrusted user input.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: typeModel
5+
data:
6+
# OpenAPIBackend instances: `new OpenAPIBackend(...)` and `await api.init()`
7+
- ["openapi-backend.OpenAPIBackend", "openapi-backend", "Member[OpenAPIBackend,default].Instance"]
8+
- ["openapi-backend.OpenAPIBackend", "openapi-backend.OpenAPIBackend", "Member[init].ReturnValue.Awaited"]
9+
# Handler functions registered in the constructor
10+
- ["openapi-backend.Handler", "openapi-backend", "Member[OpenAPIBackend,default].Argument[0].Member[handlers,securityHandlers].AnyMember"]
11+
- ["openapi-backend.Handler", "openapi-backend", "Member[OpenAPIBackend,default].Argument[0].Member[validate]"]
12+
# Handler functions registered after construction
13+
- ["openapi-backend.Handler", "openapi-backend.OpenAPIBackend", "Member[register,registerHandler,registerSecurityHandler].Argument[1]"]
14+
- ["openapi-backend.Handler", "openapi-backend.OpenAPIBackend", "Member[register].Argument[0].AnyMember"]
15+
# The handler context, including TypeScript-annotated `(c: Context) => ...`
16+
- ["openapi-backend.Context", "openapi-backend.Handler", "Parameter[0]"]
17+
18+
- addsTo:
19+
pack: codeql/javascript-all
20+
extensible: sourceModel
21+
data:
22+
- ["openapi-backend.Context", "Member[request]", "remote"]

‎javascript/ql/test/query-tests/Security/CWE-089/untyped/DatabaseAccesses.expected‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@
4747
| mysql.js:8:9:11:47 | connect ... ds) {}) |
4848
| mysql.js:14:9:16:47 | connect ... ds) {}) |
4949
| mysql.js:19:9:20:48 | connect ... ds) {}) |
50+
| openapi-backend-typed.ts:6:40:6:105 | db.quer ... s.id}`) |
51+
| openapi-backend.js:10:20:10:83 | db.quer ... s.id}`) |
52+
| openapi-backend.js:11:24:11:90 | db.quer ... ms.id]) |
53+
| openapi-backend.js:14:20:14:97 | db.quer ... y']}'`) |
54+
| openapi-backend.js:20:10:20:62 | db.quer ... ame}'`) |
55+
| openapi-backend.js:24:21:24:96 | db.quer ... me}')`) |
56+
| openapi-backend.js:28:3:28:74 | db.quer ... sid}'`) |
57+
| openapi-backend.js:33:44:33:105 | db.quer ... s.id}`) |
5058
| pg-promise-types.ts:8:5:8:22 | this.db.one(taint) |
5159
| pg-promise.js:9:3:9:15 | db.any(query) |
5260
| pg-promise.js:10:3:10:16 | db.many(query) |

‎javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@
115115
| mongooseModelClient.js:12:16:12:34 | { id: req.body.id } | mongooseModelClient.js:12:22:12:29 | req.body | mongooseModelClient.js:12:16:12:34 | { id: req.body.id } | This query object depends on a $@. | mongooseModelClient.js:12:22:12:29 | req.body | user-provided value |
116116
| mysql.js:15:18:15:65 | 'SELECT ... + temp | mysql.js:6:16:6:31 | req.params.value | mysql.js:15:18:15:65 | 'SELECT ... + temp | This query string depends on a $@. | mysql.js:6:16:6:31 | req.params.value | user-provided value |
117117
| mysql.js:19:26:19:73 | 'SELECT ... + temp | mysql.js:6:16:6:31 | req.params.value | mysql.js:19:26:19:73 | 'SELECT ... + temp | This query string depends on a $@. | mysql.js:6:16:6:31 | req.params.value | user-provided value |
118+
| openapi-backend-typed.ts:6:49:6:104 | `SELECT ... ms.id}` | openapi-backend-typed.ts:6:84:6:92 | c.request | openapi-backend-typed.ts:6:49:6:104 | `SELECT ... ms.id}` | This query string depends on a $@. | openapi-backend-typed.ts:6:84:6:92 | c.request | user-provided value |
119+
| openapi-backend.js:10:29:10:82 | `SELECT ... ms.id}` | openapi-backend.js:10:62:10:70 | c.request | openapi-backend.js:10:29:10:82 | `SELECT ... ms.id}` | This query string depends on a $@. | openapi-backend.js:10:62:10:70 | c.request | user-provided value |
120+
| openapi-backend.js:14:29:14:96 | `SELECT ... ey']}'` | openapi-backend.js:14:64:14:72 | c.request | openapi-backend.js:14:29:14:96 | `SELECT ... ey']}'` | This query string depends on a $@. | openapi-backend.js:14:64:14:72 | c.request | user-provided value |
121+
| openapi-backend.js:20:19:20:61 | `SELECT ... name}'` | openapi-backend.js:19:20:19:28 | c.request | openapi-backend.js:20:19:20:61 | `SELECT ... name}'` | This query string depends on a $@. | openapi-backend.js:19:20:19:28 | c.request | user-provided value |
122+
| openapi-backend.js:24:30:24:95 | `INSERT ... ame}')` | openapi-backend.js:24:66:24:74 | c.request | openapi-backend.js:24:30:24:95 | `INSERT ... ame}')` | This query string depends on a $@. | openapi-backend.js:24:66:24:74 | c.request | user-provided value |
123+
| openapi-backend.js:28:12:28:73 | `SELECT ... .sid}'` | openapi-backend.js:28:50:28:58 | c.request | openapi-backend.js:28:12:28:73 | `SELECT ... .sid}'` | This query string depends on a $@. | openapi-backend.js:28:50:28:58 | c.request | user-provided value |
124+
| openapi-backend.js:33:53:33:104 | `DELETE ... ms.id}` | openapi-backend.js:33:84:33:92 | c.request | openapi-backend.js:33:53:33:104 | `DELETE ... ms.id}` | This query string depends on a $@. | openapi-backend.js:33:84:33:92 | c.request | user-provided value |
118125
| pg-promise-types.ts:8:17:8:21 | taint | pg-promise-types.ts:7:17:7:28 | req.params.x | pg-promise-types.ts:8:17:8:21 | taint | This query string depends on a $@. | pg-promise-types.ts:7:17:7:28 | req.params.x | user-provided value |
119126
| pg-promise.js:9:10:9:14 | query | pg-promise.js:7:16:7:34 | req.params.category | pg-promise.js:9:10:9:14 | query | This query string depends on a $@. | pg-promise.js:7:16:7:34 | req.params.category | user-provided value |
120127
| pg-promise.js:10:11:10:15 | query | pg-promise.js:7:16:7:34 | req.params.category | pg-promise.js:10:11:10:15 | query | This query string depends on a $@. | pg-promise.js:7:16:7:34 | req.params.category | user-provided value |
@@ -549,6 +556,16 @@ edges
549556
| mysql.js:6:16:6:31 | req.params.value | mysql.js:6:9:6:12 | temp | provenance | |
550557
| mysql.js:15:62:15:65 | temp | mysql.js:15:18:15:65 | 'SELECT ... + temp | provenance | |
551558
| mysql.js:19:70:19:73 | temp | mysql.js:19:26:19:73 | 'SELECT ... + temp | provenance | |
559+
| openapi-backend-typed.ts:6:84:6:92 | c.request | openapi-backend-typed.ts:6:49:6:104 | `SELECT ... ms.id}` | provenance | |
560+
| openapi-backend.js:10:62:10:70 | c.request | openapi-backend.js:10:29:10:82 | `SELECT ... ms.id}` | provenance | |
561+
| openapi-backend.js:14:64:14:72 | c.request | openapi-backend.js:14:29:14:96 | `SELECT ... ey']}'` | provenance | |
562+
| openapi-backend.js:19:9:19:16 | { name } | openapi-backend.js:19:11:19:14 | name | provenance | |
563+
| openapi-backend.js:19:11:19:14 | name | openapi-backend.js:20:55:20:58 | name | provenance | |
564+
| openapi-backend.js:19:20:19:28 | c.request | openapi-backend.js:19:9:19:16 | { name } | provenance | |
565+
| openapi-backend.js:20:55:20:58 | name | openapi-backend.js:20:19:20:61 | `SELECT ... name}'` | provenance | |
566+
| openapi-backend.js:24:66:24:74 | c.request | openapi-backend.js:24:30:24:95 | `INSERT ... ame}')` | provenance | |
567+
| openapi-backend.js:28:50:28:58 | c.request | openapi-backend.js:28:12:28:73 | `SELECT ... .sid}'` | provenance | |
568+
| openapi-backend.js:33:84:33:92 | c.request | openapi-backend.js:33:53:33:104 | `DELETE ... ms.id}` | provenance | |
552569
| pg-promise-types.ts:7:9:7:13 | taint | pg-promise-types.ts:8:17:8:21 | taint | provenance | |
553570
| pg-promise-types.ts:7:17:7:28 | req.params.x | pg-promise-types.ts:7:9:7:13 | taint | provenance | |
554571
| pg-promise.js:6:7:6:11 | query | pg-promise.js:9:10:9:14 | query | provenance | |
@@ -958,6 +975,23 @@ nodes
958975
| mysql.js:15:62:15:65 | temp | semmle.label | temp |
959976
| mysql.js:19:26:19:73 | 'SELECT ... + temp | semmle.label | 'SELECT ... + temp |
960977
| mysql.js:19:70:19:73 | temp | semmle.label | temp |
978+
| openapi-backend-typed.ts:6:49:6:104 | `SELECT ... ms.id}` | semmle.label | `SELECT ... ms.id}` |
979+
| openapi-backend-typed.ts:6:84:6:92 | c.request | semmle.label | c.request |
980+
| openapi-backend.js:10:29:10:82 | `SELECT ... ms.id}` | semmle.label | `SELECT ... ms.id}` |
981+
| openapi-backend.js:10:62:10:70 | c.request | semmle.label | c.request |
982+
| openapi-backend.js:14:29:14:96 | `SELECT ... ey']}'` | semmle.label | `SELECT ... ey']}'` |
983+
| openapi-backend.js:14:64:14:72 | c.request | semmle.label | c.request |
984+
| openapi-backend.js:19:9:19:16 | { name } | semmle.label | { name } |
985+
| openapi-backend.js:19:11:19:14 | name | semmle.label | name |
986+
| openapi-backend.js:19:20:19:28 | c.request | semmle.label | c.request |
987+
| openapi-backend.js:20:19:20:61 | `SELECT ... name}'` | semmle.label | `SELECT ... name}'` |
988+
| openapi-backend.js:20:55:20:58 | name | semmle.label | name |
989+
| openapi-backend.js:24:30:24:95 | `INSERT ... ame}')` | semmle.label | `INSERT ... ame}')` |
990+
| openapi-backend.js:24:66:24:74 | c.request | semmle.label | c.request |
991+
| openapi-backend.js:28:12:28:73 | `SELECT ... .sid}'` | semmle.label | `SELECT ... .sid}'` |
992+
| openapi-backend.js:28:50:28:58 | c.request | semmle.label | c.request |
993+
| openapi-backend.js:33:53:33:104 | `DELETE ... ms.id}` | semmle.label | `DELETE ... ms.id}` |
994+
| openapi-backend.js:33:84:33:92 | c.request | semmle.label | c.request |
961995
| pg-promise-types.ts:7:9:7:13 | taint | semmle.label | taint |
962996
| pg-promise-types.ts:7:17:7:28 | req.params.x | semmle.label | req.params.x |
963997
| pg-promise-types.ts:8:17:8:21 | taint | semmle.label | taint |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import OpenAPIBackend, { Context } from 'openapi-backend';
2+
import { Pool } from 'pg';
3+
4+
const db = new Pool();
5+
6+
const getOwner = async (c: Context) => db.query(`SELECT * FROM owners WHERE id = ${c.request.params.id}`); // $ Alert
7+
8+
export const api = new OpenAPIBackend({ definition: './openapi.yml', handlers: { getOwner } });
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const { OpenAPIBackend } = require('openapi-backend');
2+
const OpenAPIBackendDefault = require('openapi-backend').default;
3+
const { Pool } = require('pg');
4+
5+
const db = new Pool();
6+
7+
const api = new OpenAPIBackend({
8+
definition: './openapi.yml',
9+
handlers: {
10+
getPet: (c) => db.query(`SELECT * FROM pets WHERE id = ${c.request.params.id}`), // $ Alert
11+
getPetSafe: (c) => db.query('SELECT * FROM pets WHERE id = $1', [c.request.params.id]),
12+
},
13+
securityHandlers: {
14+
apiKey: (c) => db.query(`SELECT * FROM keys WHERE key = '${c.request.headers['x-api-key']}'`), // $ Alert
15+
},
16+
});
17+
18+
api.register('findPets', async (c) => {
19+
const { name } = c.request.query; // $ Source
20+
return db.query(`SELECT * FROM pets WHERE name = '${name}'`); // $ Alert
21+
});
22+
23+
api.register({
24+
createPet: (c) => db.query(`INSERT INTO pets (name) VALUES ('${c.request.requestBody.name}')`), // $ Alert
25+
});
26+
27+
api.registerSecurityHandler('session', (c) =>
28+
db.query(`SELECT * FROM sessions WHERE id = '${c.request.cookies.sid}'`), // $ Alert
29+
);
30+
31+
async function main() {
32+
const initialized = await new OpenAPIBackendDefault({ definition: './openapi.yml' }).init();
33+
initialized.register('deletePet', (c) => db.query(`DELETE FROM pets WHERE id = ${c.request.params.id}`)); // $ Alert
34+
}

0 commit comments

Comments
 (0)