|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|