Security advisory GHSA-wmrf-hv6w-mr66: SQL Injection via unsanitized JSON path keys when ignoring/silencing compilation errors or using `Kysely<any>`.
Package: kysely (npm), vulnerable version: 0.28.11, fixed in: 0.28.12
CWE: CWE-89
Files changed by the fix:
src/query-compiler/default-query-compiler.ts

Fix diff:
diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts
index ba0fab177..e06f2e3bd 100644
--- a/src/query-compiler/default-query-compiler.ts
+++ b/src/query-compiler/default-query-compiler.ts
@@ -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(']')
diff --git a/test/node/src/sanitize-identifiers.test.ts b/test/node/src/sanitize-identifiers.test.ts
index 24673672f..49b7a66ca 100644
--- a/test/node/src/sanitize-identifiers.test.ts
+++ b/test/node/src/sanitize-identifiers.test.ts
@@ -6,7 +6,6 @@ import {
   TestContext,
   Person,
   testSql,
-  NOT_SUPPORTED,
   DIALECTS,
 } from './test-setup.js'
 
diff --git a/test/node/src/sql-injection.test.ts b/test/node/src/sql-injection.test.ts
index 6ff8a3070..84d21554e 100644
--- a/test/node/src/sql-injection.test.ts
+++ b/test/node/src/sql-injection.test.ts
@@ -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')
+      })
+    }
   })
 }
 
diff --git a/test/node/src/test-setup.ts b/test/node/src/test-setup.ts
index 3a0a106da..21d032cdc 100644
--- a/test/node/src/test-setup.ts
+++ b/test/node/src/test-setup.ts
@@ -138,6 +138,9 @@ const MYSQL_CONFIG: PoolOptions = {
   bigNumberStrings: true,
 
   connectionLimit: POOL_SIZE,
+
+  // used in sql injection tests.
+  multipleStatements: true,
 }
 
 const MSSQL_CONFIG: ConnectionConfiguration = {