| | |
| | |
| | |
| |
|
| | import { browser } from "$app/environment"; |
| | import { dev } from "$app/environment"; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export function validateMcpServerUrl(urlString: string): string | null { |
| | if (!urlString || typeof urlString !== "string") { |
| | return null; |
| | } |
| |
|
| | try { |
| | const url = new URL(urlString.trim()); |
| |
|
| | |
| | if (!["http:", "https:"].includes(url.protocol)) { |
| | return null; |
| | } |
| |
|
| | |
| | if (!dev && url.protocol === "http:" && browser) { |
| | console.warn( |
| | "Warning: Connecting to non-HTTPS MCP server in production. This may expose sensitive data." |
| | ); |
| | } |
| |
|
| | |
| | if (!dev && isPrivateOrLocalhost(url.hostname)) { |
| | console.warn("Warning: Localhost/private IP addresses are not recommended in production."); |
| | } |
| |
|
| | return url.toString(); |
| | } catch (error) { |
| | |
| | return null; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | function isPrivateOrLocalhost(hostname: string): boolean { |
| | |
| | if ( |
| | hostname === "localhost" || |
| | hostname === "127.0.0.1" || |
| | hostname === "::1" || |
| | hostname.endsWith(".localhost") |
| | ) { |
| | return true; |
| | } |
| |
|
| | |
| | const ipv4Regex = /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|0\.0\.0\.0|169\.254\.)/; |
| | if (ipv4Regex.test(hostname)) { |
| | return true; |
| | } |
| |
|
| | return false; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function sanitizeUrlForDisplay(urlString: string): string { |
| | try { |
| | const url = new URL(urlString); |
| | |
| | url.username = ""; |
| | url.password = ""; |
| | return url.toString(); |
| | } catch { |
| | return urlString; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function checkUrlSafety(urlString: string): string | null { |
| | const validated = validateMcpServerUrl(urlString); |
| | if (!validated) { |
| | return "Invalid URL. Please use http:// or https:// URLs only."; |
| | } |
| |
|
| | try { |
| | const url = new URL(validated); |
| |
|
| | |
| | if (!dev && url.protocol === "http:") { |
| | return "Non-HTTPS URLs are not recommended in production. Please use https:// for security."; |
| | } |
| |
|
| | return null; |
| | } catch { |
| | return "Invalid URL format."; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export function isSensitiveHeader(key: string): boolean { |
| | const sensitiveKeys = [ |
| | "authorization", |
| | "api-key", |
| | "api_key", |
| | "apikey", |
| | "token", |
| | "secret", |
| | "password", |
| | "bearer", |
| | "x-api-key", |
| | "x-auth-token", |
| | ]; |
| |
|
| | const lowerKey = key.toLowerCase(); |
| | return sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive)); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function validateHeader(key: string, value: string): string | null { |
| | if (!key || !key.trim()) { |
| | return "Header name is required"; |
| | } |
| |
|
| | if (!/^[a-zA-Z0-9_-]+$/.test(key)) { |
| | return "Header name can only contain letters, numbers, hyphens, and underscores"; |
| | } |
| |
|
| | if (!value) { |
| | return "Header value is required"; |
| | } |
| |
|
| | return null; |
| | } |
| |
|