From aefb6bf0ef0e132c056ae7aefdafb31e6bb47056 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 7 May 2026 19:07:41 +0800 Subject: [PATCH] fix: prevent SELECT with REPLACE/TRUNCATE functions from triggering danger warning The DANGER_RE regex matched keywords anywhere in the SQL text, causing SELECT queries using REPLACE() or TRUNCATE() functions to incorrectly trigger the destructive-SQL warning dialog. Check only the first keyword of each semicolon-separated statement instead. Also fix dialog content overflow by adding min-w-0 to the
 element,
and move macOS frameworks config from tauri.conf.json to CI-only
TAURI_CONFIG so local `tauri dev` no longer requires libltdl/libodbc
to be globally installed.

Closes #135
---
 .github/workflows/release.yml                 |  1 +
 src-tauri/tauri.conf.json                     |  3 -
 src/components/editor/DangerConfirmDialog.vue |  2 +-
 src/composables/useSqlExecution.ts            |  5 +-
 tests/useSqlExecution.test.ts                 | 68 +++++++++++++++++++
 5 files changed, 73 insertions(+), 6 deletions(-)
 create mode 100644 tests/useSqlExecution.test.ts

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b89607f6a9..59113aceed 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -113,6 +113,7 @@ jobs:
           APPLE_ID: ${{ startsWith(matrix.platform, 'macos') && secrets.APPLE_ID || '' }}
           APPLE_PASSWORD: ${{ startsWith(matrix.platform, 'macos') && secrets.APPLE_PASSWORD || '' }}
           APPLE_TEAM_ID: ${{ startsWith(matrix.platform, 'macos') && secrets.APPLE_TEAM_ID || '' }}
+          TAURI_CONFIG: ${{ startsWith(matrix.platform, 'macos') && '{"bundle":{"macOS":{"frameworks":["libodbc.2.dylib","libltdl.7.dylib"]}}}' || '{}' }}
         with:
           tagName: ${{ github.ref_name }}
           releaseName: 'DBX ${{ github.ref_name }}'
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index fb499af4cb..811ac26762 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -29,9 +29,6 @@
     "active": true,
     "targets": "all",
     "createUpdaterArtifacts": true,
-    "macOS": {
-      "frameworks": ["libodbc.2.dylib", "libltdl.7.dylib"]
-    },
     "icon": [
       "icons/32x32.png",
       "icons/128x128.png",
diff --git a/src/components/editor/DangerConfirmDialog.vue b/src/components/editor/DangerConfirmDialog.vue
index b71c04aa98..af9f490bfa 100644
--- a/src/components/editor/DangerConfirmDialog.vue
+++ b/src/components/editor/DangerConfirmDialog.vue
@@ -49,7 +49,7 @@ function onConfirm() {
         

{{ message || t("dangerDialog.message") }}

{{ details || sql }}
diff --git a/src/composables/useSqlExecution.ts b/src/composables/useSqlExecution.ts index f8ef088019..7438d2b5ee 100644 --- a/src/composables/useSqlExecution.ts +++ b/src/composables/useSqlExecution.ts @@ -6,7 +6,7 @@ import { useConnectionStore } from "@/stores/connectionStore"; import { useToast } from "@/composables/useToast"; import type { ConnectionConfig, QueryTab } from "@/types/database"; -const DANGER_RE = /\b(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i; +const DANGER_RE = /^\s*(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i; export function stripSqlComments(sql: string): string { return sql @@ -16,7 +16,8 @@ export function stripSqlComments(sql: string): string { } export function isDangerousSql(sql: string): boolean { - return DANGER_RE.test(stripSqlComments(sql)); + const cleaned = stripSqlComments(sql); + return cleaned.split(";").some((stmt) => DANGER_RE.test(stmt)); } export function useSqlExecution(deps: { diff --git a/tests/useSqlExecution.test.ts b/tests/useSqlExecution.test.ts new file mode 100644 index 0000000000..602c4d5f65 --- /dev/null +++ b/tests/useSqlExecution.test.ts @@ -0,0 +1,68 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { isDangerousSql, stripSqlComments } from "../src/composables/useSqlExecution.ts"; + +test("stripSqlComments removes block comments", () => { + assert.equal(stripSqlComments("SELECT /* drop */ 1").includes("drop"), false); +}); + +test("stripSqlComments removes line comments", () => { + assert.equal(stripSqlComments("SELECT 1 -- DROP TABLE").includes("DROP"), false); +}); + +test("stripSqlComments removes MySQL hash comments", () => { + assert.equal(stripSqlComments("SELECT 1 # DELETE").includes("DELETE"), false); +}); + +test("SELECT with REPLACE function is not dangerous", () => { + assert.equal(isDangerousSql("SELECT REPLACE(col, ';', ',') FROM t"), false); +}); + +test("SELECT with TRUNCATE function is not dangerous", () => { + assert.equal(isDangerousSql("SELECT TRUNCATE(1.234, 2)"), false); +}); + +test("REPLACE INTO is dangerous", () => { + assert.equal(isDangerousSql("REPLACE INTO t VALUES (1, 2)"), true); +}); + +test("DELETE FROM is dangerous", () => { + assert.equal(isDangerousSql("DELETE FROM t WHERE id = 1"), true); +}); + +test("DROP TABLE is dangerous", () => { + assert.equal(isDangerousSql("DROP TABLE t"), true); +}); + +test("UPDATE is dangerous", () => { + assert.equal(isDangerousSql("UPDATE t SET col = 1"), true); +}); + +test("ALTER TABLE is dangerous", () => { + assert.equal(isDangerousSql("ALTER TABLE t ADD COLUMN c INT"), true); +}); + +test("TRUNCATE TABLE is dangerous", () => { + assert.equal(isDangerousSql("TRUNCATE TABLE t"), true); +}); + +test("multi-statement with danger in second statement is dangerous", () => { + assert.equal(isDangerousSql("SELECT 1; DROP TABLE t"), true); +}); + +test("danger keyword inside comment is not dangerous", () => { + assert.equal(isDangerousSql("SELECT 1 /* DROP TABLE t */"), false); +}); + +test("leading whitespace before danger keyword is dangerous", () => { + assert.equal(isDangerousSql(" DELETE FROM t"), true); +}); + +test("complex SELECT with joins and functions is not dangerous", () => { + const sql = `select mkey, replace(xxxx,';',',') as xxxx_content, + SUBSTRING_INDEX(SUBSTRING_INDEX(pmid,'/',3),'/',-1) as xxx + from m_xxx left join ( + select xxx from m_alarm where alarm_time >= SUBDATE(now(), interval 3 minute) + ) as xxxx on m_alarm.mid = m_alarm_tmp.mid`; + assert.equal(isDangerousSql(sql), false); +});