VYPR
High severity8.2NVD Advisory· Published Mar 20, 2026· Updated Apr 8, 2026

CVE-2026-32763

CVE-2026-32763

Description

Kysely is a type-safe TypeScript SQL query builder. Versions up to and including 0.28.11 has a SQL injection vulnerability in JSON path compilation for MySQL and SQLite dialects. The visitJSONPathLeg() function appends user-controlled values from .key() and .at() directly into single-quoted JSON path string literals ('$.key') without escaping single quotes. An attacker can break out of the JSON path string context and inject arbitrary SQL. This is inconsistent with sanitizeIdentifier(), which properly doubles delimiter characters for identifiers — both are non-parameterizable SQL constructs requiring manual escaping, but only identifiers are protected. Version 0.28.12 fixes the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
kyselynpm
>= 0.26.0, < 0.28.120.28.12

Affected products

1
  • cpe:2.3:a:kysely:kysely:*:*:*:*:*:node.js:*:*
    Range: >=0.26.0,<0.28.12

Patches

1
0a602bff2f44

fix: eb.ref(col, '->$').key(key) is injectable. (#1727)

https://github.com/kysely-org/kyselyIgal KlebanovMar 4, 2026via ghsa
4 files changed · +35 3
  • src/query-compiler/default-query-compiler.ts+5 1 modified
    @@ -1625,7 +1625,11 @@ export class DefaultQueryCompiler
     
         this.append(isArrayLocation ? '[' : '.')
     
    -    this.append(String(node.value))
    +    this.append(
    +      typeof node.value === 'string'
    +        ? this.sanitizeStringLiteral(node.value)
    +        : String(node.value),
    +    )
     
         if (isArrayLocation) {
           this.append(']')
    
  • test/node/src/sanitize-identifiers.test.ts+0 1 modified
    @@ -6,7 +6,6 @@ import {
       TestContext,
       Person,
       testSql,
    -  NOT_SUPPORTED,
       DIALECTS,
     } from './test-setup.js'
     
    
  • test/node/src/sql-injection.test.ts+27 1 modified
    @@ -1,6 +1,6 @@
     import { expect } from 'chai'
     import { sql } from '../../../'
    -import { destroyTest, DIALECTS, initTest, TestContext } from './test-setup'
    +import { destroyTest, DIALECTS, initTest, type TestContext } from './test-setup'
     
     for (const dialect of DIALECTS) {
       describe(`${dialect}: select`, () => {
    @@ -56,6 +56,32 @@ for (const dialect of DIALECTS) {
           expect(results.rows).to.have.length(0)
           await assertDidNotDropTable(ctx, 'person')
         })
    +
    +    if (dialect === 'mysql') {
    +      it('should not allow SQL injection in $.key JSON paths', async () => {
    +        const injection =
    +          `first' as ${identifierWrapper}first${identifierWrapper} from ${identifierWrapper}people${identifierWrapper}; drop table ${identifierWrapper}person${identifierWrapper} -- ` as never
    +
    +        const query = ctx.db
    +          .with('people', () =>
    +            ctx.db
    +              .selectFrom('person')
    +              .select(
    +                sql<{ first: string }>`json_object('first', first_name)`.as(
    +                  'data',
    +                ),
    +              ),
    +          )
    +          .selectFrom('people')
    +          .select((eb) => eb.ref('data', '->$').key(injection).as('first'))
    +
    +        expect(query.compile().sql).to.equal(
    +          `with ${identifierWrapper}people${identifierWrapper} as (select json_object('first', first_name) as ${identifierWrapper}data${identifierWrapper} from ${identifierWrapper}person${identifierWrapper}) select ${identifierWrapper}data${identifierWrapper}->'$.first'' as ${identifierWrapper}first${identifierWrapper} from ${identifierWrapper}people${identifierWrapper}; drop table ${identifierWrapper}person${identifierWrapper} -- ' as ${identifierWrapper}first${identifierWrapper} from ${identifierWrapper}people${identifierWrapper}`,
    +        )
    +        await ctx.db.executeQuery(query)
    +        await assertDidNotDropTable(ctx, 'person')
    +      })
    +    }
       })
     }
     
    
  • test/node/src/test-setup.ts+3 0 modified
    @@ -138,6 +138,9 @@ const MYSQL_CONFIG: PoolOptions = {
       bigNumberStrings: true,
     
       connectionLimit: POOL_SIZE,
    +
    +  // used in sql injection tests.
    +  multipleStatements: true,
     }
     
     const MSSQL_CONFIG: ConnectionConfiguration = {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.