Skip to main content
Glama
dsn-obfuscate.ts5.63 kB
import type { SSHTunnelConfig } from '../types/ssh.js'; import type { ConnectorType } from '../connectors/interface.js'; import { SafeURL } from './safe-url.js'; /** * Parsed connection information from a DSN string * Used to populate SourceConfig fields when DSN is provided */ export interface ParsedConnectionInfo { type?: ConnectorType; host?: string; port?: number; database?: string; user?: string; } /** * Parse connection information from a DSN string * Extracts host, port, database, user, and type without exposing password * * @param dsn - Database connection string * @returns Parsed connection info or null if parsing fails */ export function parseConnectionInfoFromDSN(dsn: string): ParsedConnectionInfo | null { if (!dsn) { return null; } try { const type = getDatabaseTypeFromDSN(dsn); if (typeof type === 'undefined') { return null; } // Handle SQLite specially - it only has a database path if (type === 'sqlite') { // SQLite DSN format: sqlite:///path const prefix = 'sqlite:///'; if (dsn.length > prefix.length) { const rawPath = dsn.substring(prefix.length); // Add leading '/' for Unix absolute paths only // Don't add '/' for: // - Memory database: starts with ':' // - Relative paths: starts with '.' or '~' // - Windows absolute: second char is ':' (e.g., C:/path) const firstChar = rawPath[0]; const isWindowsDrive = rawPath.length > 1 && rawPath[1] === ':'; const isSpecialPath = firstChar === ':' || firstChar === '.' || firstChar === '~' || isWindowsDrive; return { type, database: isSpecialPath ? rawPath : '/' + rawPath, }; } return { type }; } // Parse other database DSNs using SafeURL const url = new SafeURL(dsn); const info: ParsedConnectionInfo = { type }; if (url.hostname) { info.host = url.hostname; } if (url.port) { info.port = parseInt(url.port, 10); } if (url.pathname && url.pathname.length > 1) { // Remove leading '/' from pathname info.database = url.pathname.substring(1); } if (url.username) { info.user = url.username; } return info; } catch { // If parsing fails, return null return null; } } /** * Obfuscates the password in a DSN string for logging purposes * @param dsn The original DSN string * @returns DSN string with password replaced by asterisks */ export function obfuscateDSNPassword(dsn: string): string { if (!dsn) { return dsn; } try { const type = getDatabaseTypeFromDSN(dsn); // SQLite has no password to obfuscate if (type === 'sqlite') { return dsn; } // Parse DSN using SafeURL const url = new SafeURL(dsn); // No password to obfuscate if (!url.password) { return dsn; } // Reconstruct DSN with obfuscated password const obfuscatedPassword = '*'.repeat(Math.min(url.password.length, 8)); const protocol = dsn.split(':')[0]; let result; if (url.username) { result = `${protocol}://${url.username}:${obfuscatedPassword}@${url.hostname}`; } else { result = `${protocol}://${obfuscatedPassword}@${url.hostname}`; } if (url.port) { result += `:${url.port}`; } result += url.pathname; // Preserve query parameters if (url.searchParams.size > 0) { const params: string[] = []; url.forEachSearchParam((value, key) => { params.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); }); result += `?${params.join('&')}`; } return result; } catch { // If parsing fails, return original DSN return dsn; } } /** * Obfuscates sensitive information in SSH configuration for logging * @param config The SSH tunnel configuration * @returns SSH config with sensitive data replaced by asterisks */ export function obfuscateSSHConfig(config: SSHTunnelConfig): Partial<SSHTunnelConfig> { const obfuscated: Partial<SSHTunnelConfig> = { host: config.host, port: config.port, username: config.username, }; if (config.password) { obfuscated.password = '*'.repeat(8); } if (config.privateKey) { obfuscated.privateKey = config.privateKey; // Keep path as-is } if (config.passphrase) { obfuscated.passphrase = '*'.repeat(8); } return obfuscated; } /** * Extracts the database type from a DSN string * @param dsn The DSN string to analyze * @returns The database type or undefined if cannot be determined */ export function getDatabaseTypeFromDSN(dsn: string): ConnectorType | undefined { if (!dsn) { return undefined; } const protocol = dsn.split(':')[0]; return protocolToConnectorType(protocol); } /** * Maps a protocol string to a ConnectorType */ function protocolToConnectorType(protocol: string): ConnectorType | undefined { const mapping: Record<string, ConnectorType> = { 'postgres': 'postgres', 'postgresql': 'postgres', 'mysql': 'mysql', 'mariadb': 'mariadb', 'sqlserver': 'sqlserver', 'sqlite': 'sqlite' }; return mapping[protocol]; } /** * Get the default port for a database type * @param type The database connector type * @returns The default port or undefined for SQLite */ export function getDefaultPortForType(type: ConnectorType): number | undefined { const ports: Record<ConnectorType, number | undefined> = { 'postgres': 5432, 'mysql': 3306, 'mariadb': 3306, 'sqlserver': 1433, 'sqlite': undefined, }; return ports[type]; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bytebase/dbhub'

If you have feedback or need assistance with the MCP directory API, please join our Discord server