OXYGENHUB

Outbound: localhost names are refused without asking the resolver 32cfb089f5e6

A host with no hosts file (the Flow test VM) cannot resolve localhost, so
the refusal came back as 'could not be found' and the test failed in CI
(runs 286-288). localhost and *.localhost are loopback by definition
(RFC 6761); they are now refused as local before any lookup.

Refs #219
Author
Andy McCall @andymccall <[email protected]>
via
agent Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
When
· 2026-10-16 00:03 UTC
Signature
✔ signed by Claude Code
Hash
32cfb089f5e6a8eede757776fa39cf1fe98594bda3c89cfc867f07d768d7b510
Depends on
9a777fec4ed9
Depended on by
nothing yet
In bookmarks
api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
2 atoms, 0 tree operations, 1 file

Browse the tree at this change

modified crates/hub/src/net.rs +9 −0
…
75 unchanged lines
76 76
    if allowed(allow, &parsed) {
77 77
        return builder.build().map_err(|e| e.to_string());
78 78
    }
79 +
    // `localhost` names are loopback by definition (RFC 6761), whatever
80 +
    // the resolver says or whether it knows them.
81 +
    let lower = host.to_ascii_lowercase();
82 +
    if lower == "localhost" || lower.ends_with(".localhost") {
83 +
        return Err(format!(
84 +
            "{host} is a private or local address; the hub only calls public addresses (a site administrator can allow one with [outbound] allow in hub.toml)"
85 +
        ));
86 +
    }
79 87
    let addrs: Vec<SocketAddr> = match host.parse::<IpAddr>() {
80 88
        Ok(ip) => vec![SocketAddr::new(ip, port)],
81 89
        Err(_) => tokio::net::lookup_host((host.as_str(), port)).await.map_err(|_| format!("{host} could not be found"))?.collect(),
…
59 unchanged lines
141 149
            "http://[::1]/",
142 150
            "http://10.0.0.5/hook",
143 151
            "http://localhost:8080/",
152 +
            "http://app.LOCALHOST/",
144 153
        ] {
145 154
            let e = client(u, &[], Duration::from_secs(1)).await.unwrap_err();
146 155
            assert!(e.contains("private or local address"), "{u}: {e}");
…
5 unchanged lines