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);
+});