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