{"record":{"id":"3d733eb3628277ab","repo":"nexu-io/open-design","slug":"blocked-non-public-brand-asset-host-host","errorCode":null,"errorMessage":"blocked non-public brand asset host: ${host}","messagePattern":"blocked non-public brand asset host: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/brands/safe-fetch.ts","lineNumber":69,"sourceCode":"}\n\n/**\n * Throw unless `url` is an http(s) URL whose host is a public address — checked\n * both as the literal host and, for a hostname, against every DNS answer.\n */\nexport async function assertPublicBrandUrl(url: string): Promise<void> {\n  let parsed: URL;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`invalid brand asset url: ${String(url)}`);\n  }\n  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n    throw new Error(`unsupported brand asset protocol: ${parsed.protocol}`);\n  }\n  const host = parsed.hostname.replace(/^\\[/, '').replace(/\\]$/, '').toLowerCase();\n  if (isNonPublicHost(host)) {\n    throw new Error(`blocked non-public brand asset host: ${host}`);\n  }\n  if (!isIpLiteral(host)) {\n    let addresses: Array<{ address: string }>;\n    try {\n      addresses = await dnsPromises.lookup(host, { all: true });\n    } catch {\n      // Let the actual fetch surface a resolution failure rather than masking it.\n      return;\n    }\n    for (const { address } of addresses) {\n      if (isNonPublicHost(String(address))) {\n        throw new Error(\n          `brand asset host resolves to a non-public address: ${host} -> ${address}`,\n        );\n      }\n    }\n  }\n}","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/brands/safe-fetch.ts#L51-L87","documentation":"Thrown when the URL's literal hostname (before any DNS lookup) is itself a non-public address: loopback (127.0.0.0/8, ::1, localhost), RFC1918 (10/8, 172.16/12, 192.168/16), CGNAT (100.64/10), link-local/metadata (169.254.169.254), IPv4 multicast (>=224), IPv6 multicast (ff00::/8), ULA (fc00::/7), or unspecified. This is the first SSRF stop, evaluated against the host string as written.","triggerScenarios":"An extracted brand asset URL points at 'http://localhost/...', 'http://127.0.0.1:8080', 'http://169.254.169.254/latest/meta-data/', 'http://10.0.0.1/admin', or 'http://[::1]/'. Either an attacker planted it in a scraped page or a test fixture points at a local service.","commonSituations":"Cloud-metadata exfiltration attempts via scraped hrefs; dev fixtures that point at a local mock server; brand.json committed with a localhost logo URL; an internal-tooling hostname that resolves to private space is mistakenly promoted to production brand data.","solutions":["Treat this throw as correct behavior — do not weaken isNonPublicHost. Investigate where the private URL entered the pipeline.","If the URL came from a scraped page, the source site is hostile or compromised; quarantine that brand extraction.","For dev/test, run the local mock on a hostname that resolves to a public IP, or disable network fallbacks via a feature flag rather than pointing at loopback.","Audit the extraction input (sourceUrl + discovered hrefs) and reject the brand if a private literal slips through upstream filtering."],"exampleFix":"// before — local mock hard-coded in fixture\nconst logoUrl = 'http://127.0.0.1:9000/logo.svg';\nawait fetchExternalBrandAsset(logoUrl);\n// after — gate network fallbacks in dev, never route private IPs through safe-fetch\nif (process.env.NODE_ENV === 'test') { /* use local fixture file, not fetch */ return; }\nawait fetchExternalBrandAsset(publicLogoUrl);","handlingStrategy":"try-catch","validationCode":"import { isLoopbackApiHost, isBlockedExternalApiHostname } from '@open-design/contracts/api/connectionTest';\nfunction looksPublicLiteral(host) {\n  const h = host.toLowerCase();\n  if (/^ff[0-9a-f]{2}:/.test(h)) return false;\n  return !isLoopbackApiHost(h) && !isBlockedExternalApiHostname(h);\n}","typeGuard":null,"tryCatchPattern":"try { await fetchExternalBrandAsset(u); }\ncatch (e) {\n  if (String(e.message).startsWith('blocked non-public brand asset host')) {\n    // security stop — quarantine the source, do not retry\n    recordSuspiciousAsset(u);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Treat the throw as a security stop; never weaken isNonPublicHost.","Never point brand fixtures at loopback or RFC1918 addresses.","Audit scraped hrefs and quarantine brands that yield private literals."],"tags":["ssrf","network","security","brand","safe-fetch","metadata"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}