From 2b9164c04254c5b611faca7f7a6822521871cfab Mon Sep 17 00:00:00 2001 From: affaan-m <124439313+affaan-m@users.noreply.github.com> Date: Mon, 28 Sep 2026 03:56:57 -0400 Subject: [PATCH] fix(gateguard): inspect SQL clients behind supported launchers Preserve contributor history while applying the exact reviewed current-main repair. Source-PR: https://github.com/affaan-m/ECC/pull/2829 Source-Parent: c4b18b452da394ea1ab0ffda749f6e00e2175ceb Review-Manifest-SHA256: 16b9e4c854b6cfc38efc99d0913fe7f3018a68b9b1c194b50f4a1e06511975cd --- scripts/hooks/gateguard-fact-force.js | 4 +- tests/hooks/gateguard-fact-force.test.js | 47 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/scripts/hooks/gateguard-fact-force.js b/scripts/hooks/gateguard-fact-force.js index 059a54d52..b23055b72 100644 --- a/scripts/hooks/gateguard-fact-force.js +++ b/scripts/hooks/gateguard-fact-force.js @@ -545,7 +545,7 @@ function wrapperValueOption(arg, valueFlags) { return null; } -// Explicit external-launcher argv grammars for dd and shell-wrapper discovery. +// Explicit external-launcher argv grammars for dd, SQL clients and shell-wrapper discovery. // Unknown flags do not justify guessing which later argument executes. // This literal allowlist cannot prove arbitrary custom-wrapper semantics or // resolve dynamically selected executables; quoted operand text stays data. @@ -777,7 +777,7 @@ function unwrapLeadWrappers(tokens, allowShellBuiltins = true, allowDdLaunchers */ function isDestructiveSqlClient(tokens) { if (!tokens || tokens.length === 0) return false; - const argv = unwrapLeadWrappers(tokens); + const argv = unwrapLeadWrappers(tokens, true, true); if (!SQL_CLIENT_COMMANDS.has(commandBasename(argv[0]))) return false; return DESTRUCTIVE_SQL.test(stripSqlLiterals(argv.join(' '))); } diff --git a/tests/hooks/gateguard-fact-force.test.js b/tests/hooks/gateguard-fact-force.test.js index eeaad8d18..c4da85d77 100644 --- a/tests/hooks/gateguard-fact-force.test.js +++ b/tests/hooks/gateguard-fact-force.test.js @@ -602,6 +602,53 @@ function runDdRegressionTests() { ]; try { hook = loadDirectHook(); + // Launcher operands stay data; only the resolved SQL client consumes SQL. + const wrappedSqlDestructive = [ + 'timeout 5 psql -c "drop table users"', + 'time psql -c "truncate audit_log"', + '/usr/bin/time -f "%E" psql -c "drop table users"', + '/usr/bin/time -q --output-file timing.log mysql -e "delete from sessions"', + 'nice -n 5 mariadb -e "delete from sessions"', + 'nohup sqlite3 fixture.db "drop table users"', + 'stdbuf -oL psql -c "truncate audit_log"', + 'ionice -c 2 -n 4 psql -c "drop table users"', + 'setsid -w sqlcmd -Q "drop table users"', + 'xargs -r -n 1 psql -c "drop table users"', + "env -S 'timeout 5 psql' -c 'drop table users'", + 'timeout 5 nice -n 1 nohup psql -c "drop table users"', + 'time -p command -- psql -c "truncate audit_log"', + "timeout 5 sh -c 'psql -c \"drop table users\"'" + ]; + const wrappedSqlPassive = [ + 'timeout 5 echo "psql -c drop table users"', + 'time -p printf "%s" "truncate audit_log"', + '/usr/bin/time -f "psql drop table" echo ok', + '/usr/bin/time -o psql echo "drop table users"', + 'nice -n psql echo "drop table users"', + 'ionice -c psql echo "drop table users"', + 'stdbuf -o psql echo "drop table users"', + 'xargs -I psql echo "drop table users"', + 'xargs -E psql echo "drop table users"', + 'setsid --help psql -c "drop table users"', + 'ionice -p 123 psql -c "drop table users"', + '/usr/bin/time --help psql -c "drop table users"', + '/usr/bin/time --version psql -c "drop table users"', + 'command -v psql "drop table users"', + 'timeout 5 psql -c "SELECT \'drop table\' AS label"', + "time '-p' psql -c 'drop table users'", + 'env time command psql -c "drop table users"', + 'echo "timeout 5 psql -c drop table users"' + ]; + for (const [commands, expected] of [ + [wrappedSqlDestructive, ['gateguard.bash-compatible-destructive']], + [wrappedSqlPassive, []] + ]) { + for (const command of commands) { + check(`SQL launcher classification: ${JSON.stringify(command)}`, () => { + assert.deepStrictEqual(hook.classifyDestructiveCommand('Bash', command), expected); + }); + } + } for (const command of destructive) { check(`dd/preservation destructive: ${JSON.stringify(command)}`, () => { assert.deepStrictEqual(hook.classifyDestructiveCommand('Bash', command), [