{"record":{"id":"23d9263d4984a26d","repo":"paperclipai/paperclip","slug":"invalid-url-urlstring","errorCode":null,"errorMessage":"Invalid URL: ${urlString}","messagePattern":"Invalid URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/plugin-host-services.ts","lineNumber":165,"sourceCode":" * resolve to a safe IP during validation, then to a private IP when fetch() runs.\n *\n * @returns Request-routing metadata used to connect directly to the resolved IP\n *          while preserving the original hostname for HTTP Host and TLS SNI.\n */\ninterface ValidatedFetchTarget {\n  parsedUrl: URL;\n  resolvedAddress: string;\n  hostHeader: string;\n  tlsServername?: string;\n  useTls: boolean;\n}\n\nasync function validateAndResolveFetchUrl(urlString: string): Promise<ValidatedFetchTarget> {\n  let parsed: URL;\n  try {\n    parsed = new URL(urlString);\n  } catch {\n    throw new Error(`Invalid URL: ${urlString}`);\n  }\n\n  if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {\n    throw new Error(\n      `Disallowed protocol \"${parsed.protocol}\" — only http: and https: are permitted`,\n    );\n  }\n\n  // Resolve the hostname to an IP and check for private ranges.\n  // We pin the resolved IP into the URL to eliminate the TOCTOU window\n  // between DNS resolution here and the second resolution fetch() would do.\n  const originalHostname = parsed.hostname.replace(/^\\[|\\]$/g, \"\"); // strip IPv6 brackets\n  const hostHeader = parsed.host; // includes port if non-default\n\n  // Race the DNS lookup against a timeout to prevent indefinite hangs\n  // when DNS is misconfigured or unresponsive.\n  const dnsPromise = dnsLookup(originalHostname, { all: true });\n  const timeoutPromise = new Promise<never>((_, reject) => {","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/services/plugin-host-services.ts#L147-L183","documentation":"validateAndResolveFetchUrl could not parse the plugin-supplied URL string with the URL constructor. This is the SSRF-guard input gate for plugin fetch: the raw urlString is malformed (bad scheme, spaces, etc.) before any protocol or DNS checks run.","triggerScenarios":"Thrown at server/src/services/plugin-host-services.ts:165 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Provide a well-formed URL string for the plugin host service request."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}