{"record":{"id":"74c8c475e4b2e2dc","repo":"iOfficeAI/AionUi","slug":"update-errors-hostnotallowed","errorCode":"update.errors.hostNotAllowed","errorMessage":"update.errors.hostNotAllowed","messagePattern":"update\\.errors\\.hostNotAllowed","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/desktop/src/process/bridge/updateBridge.ts","lineNumber":282,"sourceCode":"const resolveRepo = (requestRepo?: string): string => {\n  const envRepo = process.env.AIONUI_GITHUB_REPO?.trim();\n  const repo = (requestRepo || envRepo || DEFAULT_REPO).trim();\n  return repo || DEFAULT_REPO;\n};\n\nconst assertAllowedUrl = async (rawUrl: string) => {\n  let parsed: URL;\n  try {\n    parsed = new URL(rawUrl);\n  } catch {\n    throw new Error((await getI18n()).t('update.errors.invalidUrl'));\n  }\n\n  if (parsed.protocol !== 'https:') {\n    throw new Error((await getI18n()).t('update.errors.httpsOnly'));\n  }\n  if (!ALLOWED_DOWNLOAD_HOSTS.has(parsed.hostname)) {\n    throw new Error((await getI18n()).t('update.errors.hostNotAllowed', { host: parsed.hostname }));\n  }\n};\n\nconst fetchWithAllowlistedRedirects = async (rawUrl: string, signal: AbortSignal): Promise<Response> => {\n  let current = rawUrl;\n\n  for (let i = 0; i <= MAX_REDIRECTS; i++) {\n    await assertAllowedUrl(current);\n\n    const res = await fetch(current, {\n      signal,\n      redirect: 'manual',\n      headers: {\n        'User-Agent': DEFAULT_USER_AGENT,\n      },\n    });\n\n    if (res.status >= 300 && res.status < 400) {","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/iOfficeAI/AionUi/blob/711aa0550ee183ea495dc33e2c05c7943b70a60a/packages/desktop/src/process/bridge/updateBridge.ts#L264-L300","documentation":"Thrown by assertAllowedUrl when the URL's hostname is not in ALLOWED_DOWNLOAD_HOSTS. The updater uses a strict allowlist so downloads/redirects can only hit known-good hosts (e.g. GitHub, the project CDN), blocking SSRF-style exfiltration through the update channel.","triggerScenarios":"Passing an update URL whose host is not on the allowlist — e.g. a self-hosted release server, a GitHub Enterprise host, or a proxy domain. The failing hostname is interpolated into the message via the host variable.","commonSituations":"Self-hosting releases behind a custom domain; corporate mirrors; pointing the updater at a staging host that was never allowlisted; the allowlist constant edited/removed during refactoring; redirects that land on a CDN host outside the list.","solutions":["Use an allowlisted host (check the ALLOWED_DOWNLOAD_HOSTS constant in updateBridge.ts for the accepted set)","If you self-host, add your host to ALLOWED_DOWNLOAD_HOSTS in a fork/local build","Verify you are not accidentally redirecting to an off-list CDN","Confirm DNS/mirror config serves from the expected allowlisted domain"],"exampleFix":"// before\nconst ALLOWED_DOWNLOAD_HOSTS = new Set(['github.com']);\n\n// after (self-hosted setup)\nconst ALLOWED_DOWNLOAD_HOSTS = new Set([\n  'github.com',\n  'releases.mycompany.com',\n]);","handlingStrategy":"validation","validationCode":"import { ALLOWED_DOWNLOAD_HOSTS } from './updateBridge';\nconst isAllowedHost = (u: string): boolean => {\n  try { return ALLOWED_DOWNLOAD_HOSTS.has(new URL(u).hostname); } catch { return false; }\n};\nif (!isAllowedHost(downloadUrl)) throw new Error('host not in update allowlist');\nawait fetchWithAllowlistedRedirects(downloadUrl, signal);","typeGuard":"const isAllowlistedHost = (u: string): u is string =>\n  (() => { try { return ALLOWED_DOWNLOAD_HOSTS.has(new URL(u).hostname); } catch { return false; } })();","tryCatchPattern":"try {\n  await fetchWithAllowlistedRedirects(url, signal);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('hostNotAllowed')) {\n    // surface to user: this mirror/host is unsupported; fall back to GitHub releases\n  } else throw err;\n}","preventionTips":["Document the allowlisted hosts to users","Keep the allowlist in sync with any new CDN you adopt","Fall back to the GitHub release path when CDN host is rejected"],"tags":["security","allowlist","updater","ssrf"],"backgroundTag":"host-not-allowlisted","analyzedSha":"711aa0550ee183ea495dc33e2c05c7943b70a60a","analyzedAt":"2026-08-28T07:56:06.558Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}