Redact quick connection endpoints (#598)

* fix: redact quick connection endpoints

* fix: redact host-like quick connection names

---------

Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
vrustx 2026-06-01 18:48:01 +08:00 committed by GitHub
parent 7eca75b325
commit 87a43644de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 153 additions and 4 deletions

View File

@ -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();
<DatabaseIcon :db-type="connectionIconType(connection)" class="h-4 w-4" />
<span class="h-5 w-1 rounded-full shrink-0" :style="{ backgroundColor: connection.color || '#9ca3af' }" />
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium">{{ connection.name }}</div>
<div class="truncate text-sm font-medium">{{ connectionRedactedNameLabel(connection) }}</div>
<div class="truncate text-xs text-muted-foreground">
{{ connectionOptionSubtitle(connection) || connectionDriverLabel(connection) }}
{{ connectionRedactedOptionSubtitle(connection) || connectionDriverLabel(connection) }}
</div>
</div>
<FilePlus2 class="h-4 w-4 text-muted-foreground" />

View File

@ -4,8 +4,11 @@ type ConnectionPresentationConfig = Pick<
ConnectionConfig,
"db_type" | "driver_profile" | "driver_label" | "host" | "port" | "database"
>;
type ConnectionNamePresentationConfig = ConnectionPresentationConfig & Pick<ConnectionConfig, "name">;
const LOCAL_DATABASE_TYPES = new Set(["sqlite", "duckdb", "access"]);
const REDACTED_HOST_SEGMENT = "***";
const REDACTED_PORT = "****";
export function connectionIconType(connection?: Pick<ConnectionConfig, "db_type" | "driver_profile">): 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(" · ");
}

View File

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