{"record":{"id":"0dfc56be66af212c","repo":"Hmbown/CodeWhale","slug":"release-redirect-refused-target-hostname","errorCode":null,"errorMessage":"Release redirect refused: ${target.hostname}","messagePattern":"Release redirect refused: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/lib/computer-use-release.ts","lineNumber":120,"sourceCode":"      text += decoder.decode(value, { stream: true });\n    }\n    return JSON.parse(text + decoder.decode());\n  } finally { await reader.cancel(); reader.releaseLock(); }\n}\n\nconst WEB_HEADERS = { \"User-Agent\": \"codewhale-web\" };\n\n/** GET a release web endpoint, following at most three 302s and only onto GitHub's release hosts over https. */\nasync function fetchReleaseWeb(url: string): Promise<Response> {\n  for (let hops = 0; ; hops++) {\n    const response = await fetch(url, { redirect: \"manual\", headers: WEB_HEADERS, signal: AbortSignal.timeout(5000) });\n    if (![301, 302, 307, 308].includes(response.status)) return response;\n    await response.body?.cancel();\n    const location = response.headers.get(\"location\");\n    if (!location) throw new Error(\"Release redirect without a location\");\n    const target = new URL(location, url);\n    if (hops >= 3 || target.protocol !== \"https:\" || !RELEASE_HOSTS.has(target.hostname)) {\n      throw new Error(`Release redirect refused: ${target.hostname}`);\n    }\n    url = target.href;\n  }\n}\n\n/** Resolve the download without the GitHub API: read the latest receipt from the\n * release web endpoint, then confirm GitHub serves the archive the receipt names. */\nasync function receiptQualifiedRelease(): Promise<ComputerUseRelease> {\n  try {\n    const response = await fetchReleaseWeb(`${COMPUTER_USE_REPO}/releases/latest/download/release.json`);\n    if (response.status === 404) return { status: \"pending\" };\n    if (!response.ok) return { status: \"unavailable\" };\n    const receipt = record(await boundedJson(response, 16 * 1024));\n    const version = typeof receipt.version === \"string\" && /^\\d+\\.\\d+\\.\\d+$/.test(receipt.version) ? receipt.version : null;\n    const archive = `Codewhale-Computer-Use-${version}-macos-universal.zip`;\n    if (!version || receipt.platform !== \"macos\" || receipt.arch !== \"universal\" || receipt.notarized !== true\n      || receipt.archive !== archive || typeof receipt.sha256 !== \"string\" || !/^[0-9a-f]{64}$/.test(receipt.sha256)\n      || !Number.isSafeInteger(receipt.size) || (receipt.size as number) <= 0","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/lib/computer-use-release.ts#L102-L138","documentation":"fetchReleaseWeb follows at most 3 redirects and only to https URLs whose hostname is in the RELEASE_HOSTS allowlist. Any redirect exceeding those limits is refused with this error to prevent open-redirect or downgrade attacks during release downloads.","triggerScenarios":"A release URL redirect chain exceeds 3 hops, redirects to http:, or redirects to a hostname not in RELEASE_HOSTS.","commonSituations":"Release asset moved behind a third-party CDN; a mirror or proxy host redirecting off-domain; a redirect loop between two hosts; institutional proxy rewriting https to http.","solutions":["Print the refused hostname from the message and check whether it should be added to RELEASE_HOSTS.","Confirm the release URL resolves within the official hosts (e.g. github.com / objects.githubusercontent.com).","Check for proxies or security appliances rewriting the redirect chain and bypass them.","If a new legitimate host is needed, add it to the RELEASE_HOSTS allowlist and redeploy."],"exampleFix":"// before\nconst res = await fetchReleaseWeb('https://mirror.example.com/app.dmg');\n// after\nconst res = await fetchReleaseWeb('https://github.com/org/repo/releases/latest/download/app.dmg');","handlingStrategy":"try-catch","validationCode":"const u = new URL(releaseUrl);\nif (u.protocol !== 'https:' || !['github.com','objects.githubusercontent.com'].includes(u.hostname)) throw new Error('untrusted release host');","typeGuard":null,"tryCatchPattern":"try { await fetchReleaseWeb(url) } catch (e) { if (/Release redirect refused/.test(e.message)) log.warn('off-allowlist host:', e.message); throw e; }","preventionTips":["Only download from official release hosts.","Inspect redirect chains (curl -sIL) when adding new mirrors.","Never add unvetted hosts to RELEASE_HOSTS."],"tags":["network","redirect","security"],"backgroundTag":"unexpected-response-shape","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}