Find insecure openapi-backend setups with CodeQL.
CodeQL queries and models for openapi-backend. Catch unenforced auth and validation before an attacker does.
- Flags APIs where failed auth or validation still reaches your operation handlers
- Reads your OpenAPI definition to skip public operations and operations without input
- Taint tracking for client-controlled
validatepredicates, operation lookups and definition paths - Makes CodeQL's built-in queries (SQL injection, XSS, path traversal, ...) see
context.requestas user input - Every query maps to a rule in the openapi-backend threat model
- Runnable vulnerable and fixed examples for every query, verified in CI
- Tuned for zero false positives on the openapi-backend example projects
Add both packs to your GitHub code scanning workflow:
- uses: github/codeql-action/init@v4
with:
languages: javascript-typescript
packs: |
openapistack/openapi-backend-queries
openapistack/openapi-backend-modelsOr run them with the CodeQL CLI:
codeql database create db --language=javascript-typescript
codeql database analyze db openapistack/openapi-backend-queries \
--download \
--model-packs=openapistack/openapi-backend-models \
--format=sarif-latest --output=results.sarif
| ID | Finds | Severity |
|---|---|---|
js/openapi-backend/unenforced-security |
Handlers that run when security requirements fail | error |
js/openapi-backend/unenforced-validation |
Handlers that run when request validation fails | warning |
js/openapi-backend/missing-security-handler |
Security schemes with no registered handler | warning |
js/openapi-backend/client-controlled-validation |
validate predicates the client can switch off |
error |
js/openapi-backend/client-controlled-operation |
Client input choosing the operation or mock example | error |
js/openapi-backend/untrusted-definition |
Definitions loaded from a client-influenced location | error |
Security handlers compute c.security.authorized, but they don't reject anything on their own. Without an
unauthorizedHandler or strict: true, openapi-backend still calls the operation handler for unauthorized requests.
const api = new OpenAPIBackend({
definition: './openapi.yml', // deleteUser requires a JWT
securityHandlers: {
jwt: (c) => verifyJwt(c.request.headers.authorization),
},
handlers: {
deleteUser: (c) => users.delete(c.request.params.id), // ❌ runs for invalid tokens too
},
});Fix it with strict: true, an unauthorizedHandler, or a c.security.authorized check in every protected handler.
validate: true computes c.validation, but doesn't reject invalid requests on its own. Without a validationFail
handler or strict: true, requests that fail your schema still reach the operation handler.
const api = new OpenAPIBackend({ definition: './openapi.yml' });
// schema: role is enum [user]
api.register('createUser', (c) => users.insert(c.request.requestBody)); // ❌ { role: 'admin' } gets inFix it with a validationFail handler or strict: true.
A security scheme used in your definition has no registered handler. It fails closed, but usually means auth was never wired up.
// openapi.yml uses both jwt and apiKey
const api = new OpenAPIBackend({ definition: './openapi.yml' });
api.registerSecurityHandler('jwt', verifyJwt); // ❌ apiKey is never registeredA validate predicate that depends on request data lets any client skip validation.
const api = new OpenAPIBackend({
definition: './openapi.yml',
validate: (c, req) => !req.headers['x-internal-request'], // ❌ curl -H 'x-internal-request: 1'
});Decide on something the client can't forge, like req.socket.remoteAddress.
mockResponseForOperation() and validateRequest() trust their arguments. Client input there picks which
operation, response or example gets used.
app.get('/mock/:operationId', (req, res) => {
const { status, mock } = api.mockResponseForOperation(req.params.operationId, {
example: req.query.example, // ❌ any example in the definition, internal ones included
});
res.status(status).json(mock);
});Use the operation the router matched: c.operation.operationId.
The definition decides which operations exist and which security requirements apply, and its external $refs get
resolved from the filesystem and the network.
app.use('/tenants/:tenant', async (req, res) => {
const api = new OpenAPIBackend({ definition: `./specs/${req.params.tenant}.yml` }); // ❌ ../../uploads/evil
await api.init();
return api.handleRequest(req, req, res);
});Load definitions from fixed paths at startup.
openapi-backend builds context.request inside node_modules, where CodeQL doesn't look. Without models, CodeQL's
built-in queries don't know that handler input comes from the client:
api.register('getPet', (c) => {
return db.query(`SELECT * FROM pets WHERE id = ${c.request.params.id}`); // ❌ missed without models
});The openapistack/openapi-backend-models pack marks c.request as user input in every operation handler, security
handler, lifecycle handler and validate predicate, including TypeScript handlers typed (c: Context) => ....
Your existing CodeQL setup then covers your openapi-backend handlers too.
The queries stay quiet when they can't be sure:
- If
definitionpoints to a YAML or JSON file in your repository, the queries read it. Public operations and operations without input are never flagged. - If they can't read the definition, they alert once per
OpenAPIBackendinstance, and only if no handler checks the result. - Handlers imported from another module, spread objects, wrapped handlers and
strict: process.env.STRICTcould all be doing the right thing, so they don't trigger alerts. - Checks in helper functions,
postSecurityHandlerandpreOperationHandlercount.
CI runs the queries against the openapi-backend example projects on every push and fails on any alert.
Every example marks the lines that should be flagged with a // $ Alert comment:
deleteUser: (c) => users.delete(c.request.params.id), // $ AlertCI checks the examples with codeql test run, and again end to end: it builds a database from examples/,
analyzes it with both packs, and compares the SARIF output to the markers with
scripts/verify-sarif.mjs.
Run the tests locally with:
for pack in queries models tests examples; do codeql pack install $pack; done
codeql test run tests examples
For assistance with securing openapi-backend in your company, reach out at support@openapistack.co.
openapi-backend-codeql is Free and Open Source Software. Issues and pull requests are more than welcome!
Found a false positive or a missed vulnerability? Open an issue
with a minimal snippet. New queries need a threat model reference, a vulnerable.js / fixed.js pair in
examples/ and edge case tests in tests/.
To release, bump version in queries/qlpack.yml and models/qlpack.yml, update the CHANGELOG and
publish a GitHub release tagged v<version>.