Skip to content

Commit 4690aff

Browse files
authored
MySQL (major): enhance security with parameterized queries, add query validation (#928)
* Bump version to 2.0.0; enhance security with parameterized queries and add query validation * mention breaking change * only triggers do `validateQuery` * keep query as was * remove import
1 parent 85a9338 commit 4690aff

File tree

3 files changed

+20
-2
lines changed

3 files changed

+20
-2
lines changed

src/appmixer/mysql/bundle.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "appmixer.mysql",
3-
"version": "1.0.3",
3+
"version": "2.0.0",
44
"changelog": {
55
"1.0.0": [
66
"Initial version"
@@ -14,6 +14,9 @@
1414
],
1515
"1.0.3": [
1616
"Add catch and log errors while processing individual rows"
17+
],
18+
"2.0.0": [
19+
"Breaking change: Improve security by restricting all SQL queries to SELECT/WITH statements only and implementing parameterized queries."
1720
]
1821
}
1922
}

src/appmixer/mysql/common.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
23
const mysql = require('mysql');
34
const EventEmitter = require('events');
45

@@ -85,6 +86,18 @@ async function createConnection(context) {
8586
return conn;
8687
}
8788

89+
/**
90+
* Validates that a SQL query starts with SELECT or WITH (for CTEs).
91+
* Multiple statements are blocked by MySQL driver default (multipleStatements: false).
92+
* @param {string} query - The SQL query to validate
93+
* @throws {Error} If the query doesn't start with SELECT or WITH
94+
*/
95+
function validateQuery(query) {
96+
if (!/^\s*(select|with)\s/i.test(query)) {
97+
throw new Error('Only SELECT or WITH queries are allowed');
98+
}
99+
}
100+
88101
async function runQuery(conn, query, params) {
89102

90103
return await conn.query(query, params).stream({ highWaterMark: 10 });
@@ -100,6 +113,7 @@ module.exports = {
100113

101114
let conn;
102115
try {
116+
validateQuery(query);
103117
conn = await createConnection(context);
104118
const stream = await runQuery(conn, query, params);
105119
const concurrency = parseInt(context.config.concurrency, 10) || 100;
@@ -151,5 +165,6 @@ module.exports = {
151165
return returnStoreId;
152166
},
153167

154-
runQuery
168+
runQuery,
169+
validateQuery
155170
};

src/appmixer/mysql/logo.png

-39.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)