{"record":{"id":"279e35b8579ee878","repo":"Mintplex-Labs/anything-llm","slug":"agent-keenable-api-url-must-use-https-or-target-a-loopback","errorCode":null,"errorMessage":"AGENT_KEENABLE_API_URL must use https:// (or target a loopback host).","messagePattern":"AGENT_KEENABLE_API_URL must use https:// \\(or target a loopback host\\)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/agents/aibitat/plugins/web-browsing.js","lineNumber":1367,"sourceCode":"            return result;\n          },\n\n          _keenableSearch: async function (query) {\n            const apiKey = (process.env.AGENT_KEENABLE_API_KEY || \"\").trim();\n            let baseUrl = \"https://api.keenable.ai\";\n            if (process.env.AGENT_KEENABLE_API_URL) {\n              try {\n                const parsed = new URL(process.env.AGENT_KEENABLE_API_URL);\n                const isLoopback = [\n                  \"localhost\",\n                  \"127.0.0.1\",\n                  \"::1\",\n                  \"host.docker.internal\",\n                ].includes(parsed.hostname);\n                if (parsed.protocol === \"https:\" || isLoopback)\n                  baseUrl = parsed.origin;\n                else\n                  throw new Error(\n                    \"AGENT_KEENABLE_API_URL must use https:// (or target a loopback host).\"\n                  );\n              } catch (e) {\n                this.super.handlerProps.log(\n                  `invalid Keenable Search URL: ${e.message}`\n                );\n                return `Keenable search is misconfigured: ${e.message}`;\n              }\n            }\n\n            this.super.introspect(\n              `${this.caller}: Using Keenable to search for \"${\n                query.length > 100 ? `${query.slice(0, 100)}...` : query\n              }\"`\n            );\n\n            const headers = {\n              \"Content-Type\": \"application/json\",","sourceCodeStart":1349,"sourceCodeEnd":1385,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/a145d4d87d086bdb31d50f9bf9cd9c46d311780c/server/utils/agents/aibitat/plugins/web-browsing.js#L1349-L1385","documentation":"The Keenable web-search plugin validates the optional AGENT_KEENABLE_API_URL override before using it as the search endpoint. It parses the URL and rejects any value whose protocol is not https: unless the hostname is a loopback host (localhost, 127.0.0.1, ::1, host.docker.internal). This guard prevents API keys and search queries from being sent over plaintext http to non-local hosts, where they could be intercepted.","triggerScenarios":"AGENT_KEENABLE_API_URL is set to an http:// URL (e.g. http://api.keenable.ai or http://my-proxy.example.com) whose hostname is not one of the four whitelisted loopback names; the error is thrown inside setup when the plugin parses the env var at line 1357-1369.","commonSituations":"Developers self-hosting a Keenable proxy behind plain http on a remote VM, typos like 'http://' copied from docs, pointing at an internal IP (e.g. http://10.0.0.5:8080) that is not in the loopback whitelist, or forgetting a reverse-proxy TLS termination step. Note even 0.0.0.0 or a LAN hostname fails because only the four literal names pass.","solutions":["Change AGENT_KEENABLE_API_URL to use https:// (terminate TLS on the endpoint or front it with a reverse proxy like nginx/Caddy with a certificate).","If the endpoint is genuinely local, use one of the whitelisted hostnames: http://localhost:PORT, http://127.0.0.1:PORT, http://[::1]:PORT, or http://host.docker.internal:PORT (when running in Docker).","If the env var is not needed, unset AGENT_KEENABLE_API_URL entirely — the plugin then defaults to the built-in https://api.keenable.ai.","For a plain-http endpoint on another address, extend the loopback whitelist in server/utils/agents/aibitat/plugins/web-browsing.js (lines 1358-1363) — only do this on trusted networks."],"exampleFix":"// before\nAGENT_KEENABLE_API_URL=http://api.keenable.ai\n// after\nAGENT_KEENABLE_API_URL=https://api.keenable.ai","handlingStrategy":"validation","validationCode":"function isKeenableUrlSafe(raw) {\n  try {\n    const u = new URL(raw);\n    const loopback = [\"localhost\", \"127.0.0.1\", \"::1\", \"host.docker.internal\"];\n    return u.protocol === \"https:\" || loopback.includes(u.hostname);\n  } catch {\n    return false;\n  }\n}\n// before starting the agent:\nif (process.env.AGENT_KEENABLE_API_URL && !isKeenableUrlSafe(process.env.AGENT_KEENABLE_API_URL))\n  throw new Error(\"AGENT_KEENABLE_API_URL must use https:// (or target a loopback host).\");","typeGuard":"function isHttpsOrLoopback(url) {\n  if (!(url instanceof URL)) return false;\n  const loopback = [\"localhost\", \"127.0.0.1\", \"::1\", \"host.docker.internal\"];\n  return url.protocol === \"https:\" || loopback.includes(url.hostname);\n}","tryCatchPattern":"try {\n  await agent._keenableSearch(query);\n} catch (e) {\n  if (e.message.includes(\"must use https://\")) {\n    console.error(\"Fix AGENT_KEENABLE_API_URL:\", e.message);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Always configure https:// endpoints for non-local Keenable proxies.","Only use http:// with the literal loopback hostnames (localhost, 127.0.0.1, ::1, host.docker.internal).","Validate the env var at deployment/startup with a URL parse instead of discovering the error at first search.","Prefer omitting AGENT_KEENABLE_API_URL unless you actually self-host the API.","Terminate TLS with a reverse proxy (Caddy/nginx) rather than relaxing the whitelist."],"tags":["config","security","url-validation","env-var"],"backgroundTag":"invalid-url-format","analyzedSha":"a145d4d87d086bdb31d50f9bf9cd9c46d311780c","analyzedAt":"2026-09-15T14:32:06.641Z","contentChangedAt":"2026-09-15T14:32:06.641Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}