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 |