GitHub Security Advisory: GHSA-pv5w-4p9q-p3v2
Package: kysely (NPM)
Severity: HIGH (CVSS 7.5)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'), CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes, CWE-1284: Improper Validation of Specified Quantity in Input
Vulnerable versions: >= 0.26.0, < 0.28.17
Patched in: 0.28.17

Summary: Kysely: JSON-path traversal injection via unsanitized path-leg metacharacters in `JSONPathBuilder.key()` / `.at()`

Details:
## Summary

Kysely 0.28.12 added a `sanitizeStringLiteral()` call inside `DefaultQueryCompiler.visitJSONPathLeg` (commit `0a602bf`, PR #1727) to fix CVE-2026-32763 (`GHSA-wmrf-hv6w-mr66`). The fix only doubles single quotes (`'` → `''`); it does **not** escape JSON-path metacharacters (`.`, `[`, `]`, `*`, `**`, `?`). When attacker-controlled input flows into `eb.ref(col, '->$').key(input)` or `.at(input)` — including type-safe code where the JSON column is shaped like `Record<string, T>` so `K extends string` is the inferred type — every dot becomes a path-leg separator, letting an attacker traverse from the intended key into sibling and child fields the developer never meant to expose. The result is read access (and, in update statements, write access) to JSON sub-fields outside the intended scope across MySQL, PostgreSQL `->$`/`->>$`, and SQLite.

* Project: Kysely — TypeScript SQL query builder (npm `kysely`); affects MySQL, PostgreSQL `->$`/`->>$`, and SQLite dialects.
* Source reviewed: `kysely-org/kysely` @ `master` (`73192e4`, version `0.28.16`).
* Deployed artefact validated: `kysely@0.28.16` from npm.
* Affected file(s):
  * `src/query-compiler/default-query-compiler.ts` (lines 1611–1639, 1821–1823)
  * `src/query-builder/json-path-builder.ts` (lines 93–196)
  * `src/dialect/mysql/mysql-query-compiler.ts` (overrides `sanitizeStringLiteral` but inherits the same behaviour for path legs — escapes `\` and `'`, nothing else)
* CWE: CWE-89 — Improper Neutralization of Special Elements used in an SQL Command, with CWE-915 / CWE-1284 (improper validation of specified quantity in input) flavours for the JSON-path sub-language.
* OWASP 2021: A03:2021 — Injection.

## Vulnerable code

`src/query-compiler/default-query-compiler.ts:1625-1639`:

```ts
protected override visitJSONPathLeg(node: JSONPathLegNode): void {
  const isArrayLocation = node.type === 'ArrayLocation'

  this.append(isArrayLocation ? '[' : '.')      // (1)

  this.append(
    typeof node.value === 'string'
      ? this.sanitizeStringLiteral(node.value)  // (2)
      : String(node.value),
  )

  if (isArrayLocation) {
    this.append(']')
  }
}
```

`src/query-compiler/default-query-compiler.ts:1821-1823`:

```ts
protected sanitizeStringLiteral(value: string): string {
  return value.replace(LIT_WRAP_REGEX, "''")    // (3)
}
```

with `LIT_WRAP_REGEX = /'/g`.

`src/query-builder/json-path-builder.ts:151-167`:

```ts
key<
  K extends any[] extends O
    ? never
    : O extends object
      ? keyof NonNullable<O> & string
      : never,
  O2 = undefined extends O
    ? null | NonNullable<NonNullable<O>[K]>
    : null extends O
      ? null | NonNullable<NonNullable<O>[K]>
      : // when the object has non-specific keys, e.g. Record<string, T>, should infer `T | null`!
        string extends keyof NonNullable<O>
        ? null | NonNullable<NonNullable<O>[K]>
        : NonNullable<O>[K],
>(key: K): TraversedJSONPathBuilder<S, O2> {
  return this.#createBuilderWithPathLeg('Member', k