From 87a43644de1f08fcabbe46056ed52610cd0094ad Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Mon, 1 Jun 2026 18:48:01 +0800 Subject: [PATCH] Redact quick connection endpoints (#598) * fix: redact quick connection endpoints * fix: redact host-like quick connection names --------- Co-authored-by: t8y2 <1156263951@qq.com> --- .../src/components/layout/WelcomeScreen.vue | 11 ++- .../desktop/src/lib/connectionPresentation.ts | 63 ++++++++++++++ .../app-tests/connectionPresentation.test.ts | 83 ++++++++++++++++++- 3 files changed, 153 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/layout/WelcomeScreen.vue b/apps/desktop/src/components/layout/WelcomeScreen.vue index ac3bd0faf..880fbfaae 100644 --- a/apps/desktop/src/components/layout/WelcomeScreen.vue +++ b/apps/desktop/src/components/layout/WelcomeScreen.vue @@ -2,7 +2,12 @@ import { useI18n } from "vue-i18n"; import { FilePlus2, Plus, History, Upload, Database, Search, ShieldCheck, Sparkles } from "lucide-vue-next"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; -import { connectionDriverLabel, connectionIconType, connectionOptionSubtitle } from "@/lib/connectionPresentation"; +import { + connectionDriverLabel, + connectionIconType, + connectionRedactedNameLabel, + connectionRedactedOptionSubtitle, +} from "@/lib/connectionPresentation"; import type { ConnectionConfig } from "@/types/database"; defineProps<{ @@ -64,9 +69,9 @@ const { t } = useI18n();
-
{{ connection.name }}
+
{{ connectionRedactedNameLabel(connection) }}
- {{ connectionOptionSubtitle(connection) || connectionDriverLabel(connection) }} + {{ connectionRedactedOptionSubtitle(connection) || connectionDriverLabel(connection) }}
diff --git a/apps/desktop/src/lib/connectionPresentation.ts b/apps/desktop/src/lib/connectionPresentation.ts index be4eb0a16..541959e1a 100644 --- a/apps/desktop/src/lib/connectionPresentation.ts +++ b/apps/desktop/src/lib/connectionPresentation.ts @@ -4,8 +4,11 @@ type ConnectionPresentationConfig = Pick< ConnectionConfig, "db_type" | "driver_profile" | "driver_label" | "host" | "port" | "database" >; +type ConnectionNamePresentationConfig = ConnectionPresentationConfig & Pick; const LOCAL_DATABASE_TYPES = new Set(["sqlite", "duckdb", "access"]); +const REDACTED_HOST_SEGMENT = "***"; +const REDACTED_PORT = "****"; export function connectionIconType(connection?: Pick): string { return connection?.driver_profile || connection?.db_type || "postgres"; @@ -24,6 +27,62 @@ export function connectionEndpointLabel(connection?: ConnectionPresentationConfi return connection.host || connection.database || ""; } +function redactConnectionHost(host: string): string { + const normalizedHost = host.trim(); + if (!normalizedHost) return ""; + + const unwrappedHost = + normalizedHost.startsWith("[") && normalizedHost.endsWith("]") ? normalizedHost.slice(1, -1) : normalizedHost; + const separator = unwrappedHost.includes(":") ? ":" : "."; + const segments = unwrappedHost.split(separator).filter(Boolean); + + if (segments.length >= 3) { + return [segments[0], ...segments.slice(1, -1).map(() => REDACTED_HOST_SEGMENT), segments[segments.length - 1]].join( + separator, + ); + } + + if (segments.length === 2) { + return [segments[0], REDACTED_HOST_SEGMENT].join(separator); + } + + return REDACTED_HOST_SEGMENT; +} + +export function connectionRedactedEndpointLabel(connection?: ConnectionPresentationConfig): string { + if (!connection) return ""; + if (LOCAL_DATABASE_TYPES.has(connection.db_type)) { + return connectionEndpointLabel(connection); + } + + const redactedHost = connection.host ? redactConnectionHost(connection.host) : ""; + if (redactedHost && connection.port) { + const endpointHost = redactedHost.includes(":") ? `[${redactedHost}]` : redactedHost; + return `${endpointHost}:${REDACTED_PORT}`; + } + + return redactedHost || connection.database || ""; +} + +export function connectionRedactedNameLabel(connection?: ConnectionNamePresentationConfig): string { + const name = connection?.name.trim() || ""; + if (!connection || !name || LOCAL_DATABASE_TYPES.has(connection.db_type)) return name; + + const host = connection.host.trim(); + if (!host) return name; + + const unwrappedHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host; + const hostNames = new Set([host, unwrappedHost]); + if (connection.port) { + hostNames.add(`${host}:${connection.port}`); + if (unwrappedHost.includes(":")) { + hostNames.add(`[${unwrappedHost}]:${connection.port}`); + } + } + + return hostNames.has(name) ? connectionRedactedEndpointLabel(connection) : name; +} + export function connectionUrlPlaceholder(dbType: DatabaseType): string { switch (dbType) { case "mysql": @@ -90,3 +149,7 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string { export function connectionOptionSubtitle(connection?: ConnectionPresentationConfig): string { return [connectionDriverLabel(connection), connectionEndpointLabel(connection)].filter(Boolean).join(" · "); } + +export function connectionRedactedOptionSubtitle(connection?: ConnectionPresentationConfig): string { + return [connectionDriverLabel(connection), connectionRedactedEndpointLabel(connection)].filter(Boolean).join(" · "); +} diff --git a/packages/app-tests/connectionPresentation.test.ts b/packages/app-tests/connectionPresentation.test.ts index 198a1c776..3a74898c8 100644 --- a/packages/app-tests/connectionPresentation.test.ts +++ b/packages/app-tests/connectionPresentation.test.ts @@ -1,7 +1,15 @@ import test from "node:test"; import assert from "node:assert/strict"; import type { ConnectionConfig } from "../../apps/desktop/src/types/database.ts"; -import { connectionDriverLabel, connectionEndpointLabel, connectionIconType, connectionOptionSubtitle } from "../../apps/desktop/src/lib/connectionPresentation.ts"; +import { + connectionDriverLabel, + connectionEndpointLabel, + connectionIconType, + connectionOptionSubtitle, + connectionRedactedEndpointLabel, + connectionRedactedNameLabel, + connectionRedactedOptionSubtitle, +} from "../../apps/desktop/src/lib/connectionPresentation.ts"; const baseConnection: ConnectionConfig = { id: "conn-1", @@ -49,3 +57,76 @@ test("uses file path as endpoint for local database connections", () => { assert.equal(connectionOptionSubtitle(accessConnection), "Microsoft Access · /tmp/Northwind.accdb"); }); + +test("redacts network endpoint labels for quick connection cards", () => { + assert.equal( + connectionRedactedEndpointLabel({ + ...baseConnection, + host: "192.168.1.100", + port: 3306, + }), + "192.***.***.100:****", + ); + assert.equal( + connectionRedactedOptionSubtitle({ + ...baseConnection, + host: "db.prod.example.com", + port: 5432, + }), + "TiDB · db.***.***.com:****", + ); + assert.equal( + connectionRedactedEndpointLabel({ + ...baseConnection, + host: "2001:db8:85a3::8a2e:370:7334", + port: 5432, + }), + "[2001:***:***:***:***:7334]:****", + ); +}); + +test("redacts host-like quick connection names", () => { + assert.equal( + connectionRedactedNameLabel({ + ...baseConnection, + name: "db.prod.example.com", + host: "db.prod.example.com", + port: 5432, + }), + "db.***.***.com:****", + ); + assert.equal( + connectionRedactedNameLabel({ + ...baseConnection, + name: "[2001:db8:85a3::8a2e:370:7334]:5432", + host: "2001:db8:85a3::8a2e:370:7334", + port: 5432, + }), + "[2001:***:***:***:***:7334]:****", + ); +}); + +test("keeps friendly quick connection names readable", () => { + assert.equal( + connectionRedactedNameLabel({ + ...baseConnection, + name: "Production Analytics", + host: "db.prod.example.com", + port: 5432, + }), + "Production Analytics", + ); +}); + +test("keeps local file database endpoint labels readable when redacting", () => { + const sqliteConnection: ConnectionConfig = { + ...baseConnection, + db_type: "sqlite", + driver_profile: "sqlite", + driver_label: "SQLite", + host: "/tmp/local.db", + port: 0, + }; + + assert.equal(connectionRedactedOptionSubtitle(sqliteConnection), "SQLite · /tmp/local.db"); +});