{"record":{"id":"5e73c06b6f518828","repo":"ToolJet/ToolJet","slug":"url-scheme-blocked","errorCode":null,"errorMessage":"URL scheme blocked","messagePattern":"URL scheme blocked","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"critical","filePath":"marketplace/plugins/common/lib/ssrf-protection.ts","lineNumber":535,"sourceCode":"        'URL contains private IP address in credentials section which could be used for SSRF',\n        { hostname, credentials: potentialIP }\n      );\n    }\n  }\n\n  // 1. Static hostname blocklist — defense-in-depth before any DNS resolution\n  if (isBlockedHostname(hostname)) {\n    throw new QueryError(\n      'Hostname blocked',\n      'This hostname is not allowed for security reasons',\n      { hostname }\n    );\n  }\n\n  // 2. Check for blocked schemes\n  if (config.blockedSchemes && config.blockedSchemes.length > 0) {\n    if (isSchemeBlocked(scheme, config.blockedSchemes)) {\n      throw new QueryError(\n        'URL scheme blocked',\n        `The URL scheme '${scheme}' is not allowed. Blocked schemes: ${config.blockedSchemes.join(', ')}`,\n        { scheme, blockedSchemes: config.blockedSchemes }\n      );\n    }\n  }\n\n  // 3. Check if hostname is a private IP address.\n  // On self-hosted (allowPrivateNetworks=true) only block dangerous ranges\n  // (metadata endpoints, loopback) — RFC1918 is allowed for internal services.\n  const ipCheckFn = config.allowPrivateNetworks ? isDangerousPrivateIP : isPrivateIP;\n  if (ipCheckFn(hostname)) {\n    throw new QueryError(\n      'Private IP address blocked',\n      'Direct access to private IP addresses is not allowed for security reasons',\n      { hostname }\n    );\n  }","sourceCodeStart":517,"sourceCodeEnd":553,"githubUrl":"https://github.com/ToolJet/ToolJet/blob/20602a8e101f2e59686c9afde0d1402aac2c8871/marketplace/plugins/common/lib/ssrf-protection.ts#L517-L553","documentation":"SSRF guard that rejects URLs whose scheme is in config.blockedSchemes. Typical blocked schemes are file://, gopher://, ftp://, dict:// — protocols that can be abused to read local files or hit arbitrary TCP ports. The check uses isSchemeBlocked against the configured list and only runs when blockedSchemes is non-empty.","triggerScenarios":"A URL with a non-http(s) scheme that appears in blockedSchemes: file:///etc/passwd, gopher://internal:6379/_FLUSHALL, ftp://, dict://. Also fires if a template variable prefix produces an unexpected scheme.","commonSituations":"An end-user (or attacker) supplies file:// to read server files; a misconfigured template concatenates a scheme; a legacy integration that legitimately used ftp and is now blocked by the SSRF policy.","solutions":["Use http:// or https:// for the request instead of file/gopher/ftp.","If a non-http scheme is genuinely required on a self-hosted instance, review and adjust blockedSchemes in the SSRF config after a security review.","Sanitize template variables that could inject a scheme prefix.","Log these attempts — they are frequently reconnaissance."],"exampleFix":"// before — 'file:///etc/passwd'\n// after  — fetch the resource over HTTPS from a proper endpoint: 'https://config-store.example.com/passwd'","handlingStrategy":"validation","validationCode":"const ALLOWED_SCHEMES = new Set(['http:','https:']);\nfunction isSafeScheme(url: string): boolean { try { return ALLOWED_SCHEMES.has(new URL(url).protocol); } catch { return false; } }","typeGuard":null,"tryCatchPattern":"try { await validateUrlForSSRF(urlString); }\ncatch (e) {\n  if (e instanceof QueryError && e.message === 'URL scheme blocked') { /* require http/https */ }\n  throw e;\n}","preventionTips":["Restrict URL inputs to http/https at the UI layer.","Sanitize template variables that could prepend a scheme.","Log blocked-scheme attempts as possible reconnaissance."],"tags":["ssrf","security","scheme","blocklist","typescript"],"backgroundTag":null,"analyzedSha":"20602a8e101f2e59686c9afde0d1402aac2c8871","analyzedAt":"2026-08-13T05:58:54.221Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}