{"record":{"id":"806f9cd2f9689023","repo":"astrid-runtime/astrid","slug":"host-did-not-resolve-to-any-address","errorCode":null,"errorMessage":"host did not resolve to any address","messagePattern":"host did not resolve to any address","errorType":"exception","errorClass":"std::io::Error::NotFound","httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule/src/engine/wasm/host/http/ssrf.rs","lineNumber":98,"sourceCode":"\n            let (safe_addrs, saw_unsafe) = filter_safe_addrs(addrs, exempt);\n\n            if safe_addrs.is_empty() {\n                // All resolved addresses failed the airlock: a genuine SSRF\n                // block. Mark `tripped` so the caller can emit the typed\n                // `airlock-rejected` instead of a generic connection error.\n                if saw_unsafe {\n                    tripped.store(true, Ordering::Relaxed);\n                    return Err(Box::new(std::io::Error::new(\n                        std::io::ErrorKind::PermissionDenied,\n                        \"DNS resolved to an unauthorized private or local IP address\",\n                    ))\n                        as Box<dyn std::error::Error + Send + Sync>);\n                }\n                // Resolved to an empty address set: an ordinary resolution miss,\n                // not an airlock block — mark `dns_failed`, not `tripped`.\n                dns_failed.store(true, Ordering::Relaxed);\n                return Err(Box::new(std::io::Error::new(\n                    std::io::ErrorKind::NotFound,\n                    \"host did not resolve to any address\",\n                ))\n                    as Box<dyn std::error::Error + Send + Sync>);\n            }\n\n            let iter: reqwest::dns::Addrs = Box::new(safe_addrs.into_iter());\n            Ok(iter)\n        })\n    }\n}\n\n/// Partition resolved addresses into the airlock-safe set, reporting\n/// whether any address was dropped as unsafe. An all-unsafe result (empty\n/// safe set with `saw_unsafe == true`) is an airlock rejection; an empty\n/// input is an ordinary resolution miss.\n///\n/// `exempt` (the operator allowlist matched this host:port at pre-flight)","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule/src/engine/wasm/host/http/ssrf.rs#L80-L116","documentation":"SafeDnsResolver::resolve completed the DNS lookup but produced an empty address set, so there is no address to connect to. The resolver reports this as an io::Error with ErrorKind::NotFound (which the caller maps to the typed `dns-error`) and sets `dns_failed` — explicitly not an SSRF block, just an ordinary resolution miss. It is surfaced to guests as 'DNS could not resolve the hostname'.","triggerScenarios":"A guest requests a URL whose hostname resolves to zero addresses: newly registered or deleted DNS records, a host removed from internal DNS, IPv6-only/AAAA-only environments where lookup yields no usable addresses for the requested family, or empty stub-zone answers.","commonSituations":"Typo'd or decommissioned hostname in guest configuration; DNS TTL expiry after a service was torn down; split-horizon DNS where the sandbox's resolver has no record for an internal name; a service's DNS record removed during a deploy while guest code still calls it.","solutions":["Fix the hostname in the guest's request (check spelling and that the DNS record exists via an external resolver)","Verify the target service is still deployed and its DNS record (A/AAAA) is published in the resolver the sandbox uses","Retry after DNS propagation if the record was just created (new domains can take time to propagate)","If the name only exists on the operator's network, use the exempt/allowlisted host path or expose a public endpoint"],"exampleFix":"// before: stale hostname in guest config\nlet base = \"https://api-old.internal.example.com\"; // record deleted\n// after: current published hostname\nlet base = \"https://api.internal.example.com\";","handlingStrategy":"retry","validationCode":"// Pre-flight check that the host resolves to at least one address:\nfn host_resolves(host: &str) -> bool {\n    tokio::net::lookup_host((host, 0u16))\n        .map(|mut addrs| addrs.next().is_some())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.kind() == std::io::ErrorKind::NotFound\n        || e.to_string().contains(\"did not resolve\") => {\n        eprintln!(\"DNS miss for host; retrying with backoff\");\n        retry_with_backoff(host, 3);\n    }\n    other => propagate(other),\n}","preventionTips":["Validate hostnames in guest configuration against DNS before deployment","Retry with exponential backoff for freshly created DNS records (propagation delay)","Monitor service decommissioning: remove or update guest configs when a DNS record is deleted","Prefer stable service endpoints over ad-hoc internal names in guest code"],"tags":["dns","network","hostname","not-found"],"backgroundTag":"resource-not-found","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}