CVE-2019-10748
Description
Sequelize all versions prior to 3.35.1, 4.44.3, and 5.8.11 are vulnerable to SQL Injection due to JSON path keys not being properly escaped for the MySQL/MariaDB dialects.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Sequelize prior to versions 3.35.1, 4.44.3, and 5.8.11 allows SQL injection via unescaped JSON path keys in MySQL/MariaDB dialects.
Vulnerability
Overview Sequelize, a Node.js ORM, is vulnerable to SQL injection in the MySQL/MariaDB dialects due to improper escaping of JSON path keys [1][2]. The bug resides in the query generator's handling of JSON path expressions, where path segments are concatenated into a string without sanitization, allowing an attacker to inject arbitrary SQL [2].
Exploitation
An attacker can exploit this by supplying a crafted JSON object in a where clause. As demonstrated in a proof-of-concept by Snyk, using a key containing SQL syntax like 'a')) AS DECIMAL) = 1 UNION SELECT VERSION(); -- can execute arbitrary SQL commands [2]. No authentication is required if the application exposes such queries to user input.
Impact
Successful exploitation allows an attacker to execute arbitrary SQL statements, potentially leading to data exfiltration, modification, or deletion. The CVSS score is not yet provided by NVD [1], but the severity is high.
Mitigation
The vulnerability is fixed in Sequelize versions 3.35.1, 4.44.3, and 5.8.11 or higher [2]. Users should upgrade immediately. No workaround is documented.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
sequelizenpm | < 3.35.1 | 3.35.1 |
sequelizenpm | >= 4.0.0, < 4.44.3 | 4.44.3 |
sequelizenpm | >= 5.0.0, < 5.8.11 | 5.8.11 |
Affected products
2Patches
1a72a3f5fix(mariadb): properly escape json path key (#11089)
5 files changed · +34 −21
lib/dialects/abstract/query-generator.js+4 −4 modified@@ -1053,12 +1053,12 @@ class QueryGenerator { * https://bugs.mysql.com/bug.php?id=81896 */ paths = paths.map(subPath => Utils.addTicks(subPath, '"')); - pathStr = ['$'].concat(paths).join('.'); - return `(${quotedColumn}->>'${pathStr}')`; + pathStr = this.escape(['$'].concat(paths).join('.')); + return `(${quotedColumn}->>${pathStr})`; case 'mariadb': - pathStr = ['$'].concat(paths).join('.'); - return `json_unquote(json_extract(${quotedColumn},'${pathStr}'))`; + pathStr = this.escape(['$'].concat(paths).join('.')); + return `json_unquote(json_extract(${quotedColumn},${pathStr}))`; case 'sqlite': pathStr = this.escape(['$']
package.json+3 −3 modified@@ -72,16 +72,16 @@ "lint-staged": "^8.1.5", "mariadb": "^2.0.3", "markdownlint-cli": "^0.14.1", - "mocha": "^6.0.2", + "mocha": "6.0.2", "mysql2": "^1.6.5", "nyc": "^13.3.0", "pg": "^7.8.1", "pg-hstore": "^2.x", "pg-types": "^2.0.0", "rimraf": "^2.6.3", "semantic-release": "^15.13.3", - "sinon": "^7.2.6", - "sinon-chai": "^3.2.0", + "sinon": "7.2.6", + "sinon-chai": "3.2.0", "sqlite3": "^4.0.6", "tedious": "^6.0.0", "typescript": "^3.3.3333"
test/integration/include/findAndCountAll.test.js+1 −0 modified@@ -73,6 +73,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); }); }); + it('should be able to include a required model. Result rows should match count', function() { const User = this.sequelize.define('User', { name: DataTypes.STRING(40) }, { paranoid: true }), SomeConnection = this.sequelize.define('SomeConnection', {
test/integration/model/json.test.js+12 −0 modified@@ -685,6 +685,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); + it('should properly escape path keys', function() { + return this.Model.findAll({ + raw: true, + attributes: ['id'], + where: { + data: { + "a')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ": 1 + } + } + }); + }); + it('should properly escape the single quotes in array', function() { return this.Model.create({ data: {
test/unit/sql/where.test.js+14 −14 modified@@ -851,7 +851,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { prefix: 'User' }, { mariadb: "json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value'", - mysql: "(`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value'", + mysql: "(`User`.`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'", postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'", sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'" }); @@ -866,7 +866,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) IN (1, 2)", - mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) IN (1, 2)", + mysql: "CAST((`data`->>'$.\\\"nested\\\"') AS DECIMAL) IN (1, 2)", postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) IN (1, 2)", sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) IN (1, 2)" }); @@ -881,7 +881,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) BETWEEN 1 AND 2", - mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) BETWEEN 1 AND 2", + mysql: "CAST((`data`->>'$.\\\"nested\\\"') AS DECIMAL) BETWEEN 1 AND 2", postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) BETWEEN 1 AND 2", sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2" }); @@ -900,7 +900,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { tableName: 'User' })) }, { mariadb: "(json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.nested.prop')) != 'None')", - mysql: "((`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value' AND (`User`.`data`->>'$.\"nested\".\"prop\"') != 'None')", + mysql: "((`User`.`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value' AND (`User`.`data`->>'$.\\\"nested\\\".\\\"prop\\\"') != 'None')", postgres: "((\"User\".\"data\"#>>'{nested,attribute}') = 'value' AND (\"User\".\"data\"#>>'{nested,prop}') != 'None')", sqlite: "(json_extract(`User`.`data`, '$.nested.attribute') = 'value' AND json_extract(`User`.`data`, '$.nested.prop') != 'None')" }); @@ -919,7 +919,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { prefix: 'User' }, { mariadb: "(json_unquote(json_extract(`User`.`data`,'$.name.last')) = 'Simpson' AND json_unquote(json_extract(`User`.`data`,'$.employment')) != 'None')", - mysql: "((`User`.`data`->>'$.\"name\".\"last\"') = 'Simpson' AND (`User`.`data`->>'$.\"employment\"') != 'None')", + mysql: "((`User`.`data`->>'$.\\\"name\\\".\\\"last\\\"') = 'Simpson' AND (`User`.`data`->>'$.\\\"employment\\\"') != 'None')", postgres: "((\"User\".\"data\"#>>'{name,last}') = 'Simpson' AND (\"User\".\"data\"#>>'{employment}') != 'None')", sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')" }); @@ -933,7 +933,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "(CAST(json_unquote(json_extract(`data`,'$.price')) AS DECIMAL) = 5 AND json_unquote(json_extract(`data`,'$.name')) = 'Product')", - mysql: "(CAST((`data`->>'$.\"price\"') AS DECIMAL) = 5 AND (`data`->>'$.\"name\"') = 'Product')", + mysql: "(CAST((`data`->>'$.\\\"price\\\"') AS DECIMAL) = 5 AND (`data`->>'$.\\\"name\\\"') = 'Product')", postgres: "(CAST((\"data\"#>>'{price}') AS DOUBLE PRECISION) = 5 AND (\"data\"#>>'{name}') = 'Product')", sqlite: "(CAST(json_extract(`data`, '$.price') AS DOUBLE PRECISION) = 5 AND json_extract(`data`, '$.name') = 'Product')" }); @@ -948,7 +948,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'value'", - mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'value'", + mysql: "(`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'", postgres: "(\"data\"#>>'{nested,attribute}') = 'value'", sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'" }); @@ -963,7 +963,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) = 4", - mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) = 4", + mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) = 4", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4", sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4" }); @@ -980,7 +980,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) IN (3, 7)", - mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) IN (3, 7)", + mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) IN (3, 7)", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) IN (3, 7)", sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)" }); @@ -997,7 +997,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2", - mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2", + mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) > 2", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2", sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2" }); @@ -1014,7 +1014,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2", - mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2", + mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) > 2", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2", sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2" }); @@ -1032,7 +1032,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: `CAST(json_unquote(json_extract(\`data\`,'$.nested.attribute')) AS DATETIME) > ${sql.escape(dt)}`, - mysql: `CAST((\`data\`->>'$."nested"."attribute"') AS DATETIME) > ${sql.escape(dt)}`, + mysql: `CAST((\`data\`->>'$.\\"nested\\".\\"attribute\\"') AS DATETIME) > ${sql.escape(dt)}`, postgres: `CAST(("data"#>>'{nested,attribute}') AS TIMESTAMPTZ) > ${sql.escape(dt)}`, sqlite: `json_extract(\`data\`, '$.nested.attribute') > ${sql.escape(dt.toISOString())}` }); @@ -1047,7 +1047,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'true'", - mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'true'", + mysql: "(`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'true'", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true", sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1" }); @@ -1064,7 +1064,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } }, { mariadb: "json_unquote(json_extract(`meta_data`,'$.nested.attribute')) = 'value'", - mysql: "(`meta_data`->>'$.\"nested\".\"attribute\"') = 'value'", + mysql: "(`meta_data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'", postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'", sqlite: "json_extract(`meta_data`, '$.nested.attribute') = 'value'" });
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-j9xp-92vc-559jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-10748ghsaADVISORY
- github.com/sequelize/sequelize/commit/a72a3f5%2Cmitrex_refsource_MISC
- github.com/sequelize/sequelize/commit/a72a3f5,ghsaWEB
- github.com/sequelize/sequelize/pull/11089%2Cmitrex_refsource_MISC
- github.com/sequelize/sequelize/pull/11089,ghsaWEB
- snyk.io/vuln/SNYK-JS-SEQUELIZE-450221ghsax_refsource_MISCWEB
- www.npmjs.com/advisories/1018ghsaWEB
News mentions
0No linked articles in our index yet.