{"record":{"id":"88832c9dab315fb8","repo":"paperclipai/paperclip","slug":"disallowed-protocol-parsed-protocol-only-ht","errorCode":null,"errorMessage":"Disallowed protocol \"${parsed.protocol}\" — only http: and https: are permitted","messagePattern":"Disallowed protocol \"(.+?)\" — only http: and https: are permitted","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/plugin-host-services.ts","lineNumber":169,"sourceCode":" */\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) => {\n    setTimeout(\n      () => reject(new Error(`DNS lookup timed out after ${DNS_LOOKUP_TIMEOUT_MS}ms for ${originalHostname}`)),\n      DNS_LOOKUP_TIMEOUT_MS,\n    );","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/services/plugin-host-services.ts#L151-L187","documentation":"The plugin fetch target parsed successfully but its scheme is not http: or https: (e.g. file:, ftp:). The SSRF allowlist in validateAndResolveFetchUrl rejects anything outside the two web protocols before DNS resolution happens.","triggerScenarios":"Thrown at server/src/services/plugin-host-services.ts:169 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use an http: or https: URL; other protocols are not permitted."],"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-14T00:17:10.932Z"}