{"record":{"id":"d7d2131be1bd703a","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-https-external","errorCode":null,"errorMessage":"API 地址须使用 HTTPS，且不含账号、密码、查询参数或片段","messagePattern":"API 地址须使用 HTTPS，且不含账号、密码、查询参数或片段","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/external-api.mjs","lineNumber":4,"sourceCode":"// OpenAI-compatible Chat Completions. Never persist credentials in tree checkpoints.\nexport function apiEndpoint(raw){\n  let url;try{url=new URL(String(raw).trim());}catch{throw Error('请输入完整的 HTTPS API 地址');}\n  if(url.protocol!=='https:'||url.username||url.password||url.search||url.hash)throw Error('API 地址须使用 HTTPS，且不含账号、密码、查询参数或片段');\n  const path=url.pathname.replace(/\\/+$/,'');\n  url.pathname=path.endsWith('/chat/completions')?path:(path||'/v1')+'/chat/completions';\n  return url.href;\n}\n\nexport function apiConfig(input){\n  const channel=input?.channel==='external'?'external':'native';\n  const value={channel,baseUrl:String(input?.baseUrl||'').trim(),model:String(input?.model||'').trim(),key:String(input?.key||'').trim(),remember:input?.remember===true,speed:input?.speed==='provider'?'provider':'fast',revision:input?.revision||crypto.randomUUID()};\n  if(channel==='external'){\n    value.endpoint=apiEndpoint(value.baseUrl);\n    if(!value.model||value.model.length>200)throw Error('请填写 API 的模型名称');\n    if(!value.key||/[\\r\\n]/.test(value.key))throw Error('请在设置中填写有效 API Key');\n  }\n  return value;\n}\n\nexport function storedApiConfig(config){\n  const {channel,baseUrl,model,remember,speed,revision}=config;","sourceCodeStart":1,"sourceCodeEnd":22,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/external-api.mjs#L1-L22","documentation":"apiEndpoint() accepts only https: URLs and additionally rejects URLs that embed credentials (username/password), query strings (?...), or fragments (#...). This prevents leaking secrets via the URL and guarantees a deterministic endpoint path. It throws this error for any http:// URL or URL containing those components.","triggerScenarios":"apiEndpoint('http://api.example.com/v1'); a URL like https://user:pass@host/v1; URLs with ?api-key=... or #section; any of these after successful URL parsing.","commonSituations":"User configures a local HTTP-only proxy (http://localhost:11434/v1); user pastes a URL that already includes an API key as a query parameter; user includes credentials in the URL instead of the API Key field.","solutions":["Use https:// instead of http:// (put the service behind TLS or use a TLS-terminating reverse proxy)","Move the API key out of the URL into the key/password field — the code appends the key as a bearer credential, not a query param","Remove ?query=... and #fragment parts from the URL","Remove user:password@ from the URL"],"exampleFix":"// before\napiEndpoint('http://localhost:8080/v1?key=abc')\n// after\napiEndpoint('https://my-tls-proxy.example.com/v1') // key goes in the API Key field","handlingStrategy":"validation","validationCode":"function checkUrl(raw) {\n  try {\n    const u = new URL(String(raw).trim());\n    if (u.protocol !== 'https:') return 'must be https://';\n    if (u.username || u.password) return 'remove credentials from URL';\n    if (u.search || u.hash) return 'remove ?query and #fragment';\n    return null;\n  } catch { return 'not a valid absolute URL'; }\n}","typeGuard":"function isCleanHttpsUrl(u) { return u instanceof URL && u.protocol === 'https:' && !u.username && !u.password && !u.search && !u.hash; }","tryCatchPattern":"let endpoint;\ntry { endpoint = apiEndpoint(raw); } catch (e) { if (String(e).includes('HTTPS')) { showTlsGuidance(); } return; }","preventionTips":["Never embed API keys as ?key= query params — use the key field","Use TLS-terminating reverse proxies for local services instead of plain http","Strip #anchors copied from browser address bars","Keep the API key out of URLs entirely for security"],"tags":["url","https","security","validation"],"backgroundTag":"invalid-url","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}