From bea30b94c9f90910a530ba1b870eafb681261bd6 Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Wed, 30 Sep 2026 10:44:24 +0200 Subject: [PATCH] Confirm MySQL SET forms that change server state SET PASSWORD, SET GLOBAL and SET PERSIST (also as @@GLOBAL.x) change the server or the database account rather than the session, so they need confirmation like other writes. Session variables stay a read. --- lib/Service/SqlGuard.php | 4 +++- tests/Unit/Service/SqlGuardTest.php | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/Service/SqlGuard.php b/lib/Service/SqlGuard.php index ca7b3fe..5b10cc1 100644 --- a/lib/Service/SqlGuard.php +++ b/lib/Service/SqlGuard.php @@ -43,13 +43,15 @@ class SqlGuard * What makes a statement that starts as a read change something: * a data-modifying CTE (WITH x AS (DELETE ...)), SELECT ... INTO, * SELECT ... FOR UPDATE, EXPLAIN ANALYZE (runs the statement), - * SET ROLE / SET SESSION AUTHORIZATION, and functions with side + * SET ROLE / SET SESSION AUTHORIZATION, MySQL's SET PASSWORD / + * GLOBAL / PERSIST, and functions with side * effects. Statements that don't start as a read always need * confirmation, so INSERT, DROP, CREATE etc. don't need to be here. */ private const WRITES_INSIDE_READ = [ '/\b(INSERT|UPDATE|DELETE|MERGE|INTO|ANALYZE)\b/i', '/^SET\s+(SESSION\s+|LOCAL\s+)?(ROLE|SESSION\s+AUTHORIZATION)\b/i', + '/^SET\s+(PASSWORD|GLOBAL|PERSIST|PERSIST_ONLY)\b|^SET\s+@@(GLOBAL|PERSIST|PERSIST_ONLY)\./i', '/\b(pg_terminate_backend|pg_cancel_backend|pg_reload_conf|set_config|setval|nextval|lo_create|lo_put|lo_unlink|pg_switch_wal|pg_create_restore_point|pg_advisory_lock)\s*\(/i', ]; diff --git a/tests/Unit/Service/SqlGuardTest.php b/tests/Unit/Service/SqlGuardTest.php index eb850d1..69812b8 100644 --- a/tests/Unit/Service/SqlGuardTest.php +++ b/tests/Unit/Service/SqlGuardTest.php @@ -71,6 +71,8 @@ class SqlGuardTest extends TestCase ['SHOW search_path'], ['EXPLAIN SELECT 1'], ["SET myvar.uid = 'alice'"], + ['SET @uid = 5'], + ['SET SESSION sql_mode = \'\''], ["SELECT \$\$DELETE FROM x\$\$"], ["SELECT E'it\\'s delete' FROM t"], ['VALUES (1)'], @@ -109,6 +111,10 @@ class SqlGuardTest extends TestCase ['EXPLAIN ANALYZE DELETE FROM t'], ['SET ROLE postgres'], ['SET SESSION AUTHORIZATION postgres'], + ["SET PASSWORD = 'x'"], + ['SET GLOBAL max_connections = 10'], + ['SET PERSIST max_connections = 10'], + ['SET @@GLOBAL.max_connections = 10'], ['SELECT pg_terminate_backend(123)'], ["SELECT set_config('x', 'y', false)"], ["SELECT setval('seq', 1)"],