Skip to content

Commit fa0b2a2

Browse files
Duncanwpfleger96
andcommitted
fix(relay): reject any unparseable BUZZ_ADMIN_HOST authority at config parse
The bare-IPv6 bracket guard names the honest `::1` shape but skips unclosed-bracket typos like `[::1` and `[::1:3000` — they start with `[`, pass the guard, then interpolate into an unparseable `http://[::1` NIP-11 advertisement and NIP-98 u-tag URL. Same defect class as the bare-IPv6 case, just a typo shape. Add a catch-all after the bracket guard: url::Url::parse("http://{host}") must succeed, else reject with an error naming the host. This is a validity gate only — the host is still stored verbatim, not normalized. It kills every malformed authority in one guard, including shapes not enumerated. url is already a buzz-relay dep. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
1 parent f77a5e2 commit fa0b2a2

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

crates/buzz-relay/src/config.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,22 @@ impl Config {
10821082
)));
10831083
}
10841084

1085+
// Catch-all authority gate: every accepted host is interpolated
1086+
// verbatim into the NIP-11 advertisement and NIP-98 `u`-tag URLs,
1087+
// so it must form a parseable authority. The bracket guard above
1088+
// names the honest bare-IPv6 shape; this rejects any remaining
1089+
// malformed authority (e.g. the unclosed-bracket typos `[::1`
1090+
// and `[::1:3000`) that would otherwise produce an unparseable
1091+
// `http://{host}`. This is a validity gate only — the host is
1092+
// still stored verbatim, not normalized.
1093+
if url::Url::parse(&format!("http://{host}")).is_err() {
1094+
return Err(ConfigError::InvalidValue(format!(
1095+
"BUZZ_ADMIN_HOST={host} is not a valid URL authority; \
1096+
it must be a host with an optional port, e.g. \
1097+
relay.example.com:8443 or [::1]:3000"
1098+
)));
1099+
}
1100+
10851101
// Parse BUZZ_ADMIN_AUTH. Accepted values: "token" (default when
10861102
// unset), "disabled", "nip98". Any other non-empty value is a
10871103
// startup error (typo-proofing).
@@ -1446,6 +1462,29 @@ mod tests {
14461462
}
14471463
}
14481464

1465+
#[test]
1466+
fn admin_host_malformed_authority_fails_closed() {
1467+
// Shapes that slip the bare-IPv6 bracket guard (they start with `[`)
1468+
// but still cannot form a valid URL authority — the unclosed-bracket
1469+
// typos. Without the catch-all parse gate these would produce an
1470+
// unparseable `http://[::1` advertisement and NIP-98 `u`-tag URL.
1471+
let _guard = ENV_MUTEX.lock().unwrap();
1472+
for host in ["[::1", "[::1:3000", "[not-closed"] {
1473+
let result = config_with_admin_env(&[
1474+
("BUZZ_ADMIN_HOST", Some(host)),
1475+
("BUZZ_ADMIN_TOKEN", Some(VALID_ADMIN_TOKEN)),
1476+
]);
1477+
assert!(
1478+
matches!(
1479+
result,
1480+
Err(ConfigError::InvalidValue(ref message))
1481+
if message.contains("BUZZ_ADMIN_HOST") && message.contains("valid URL authority")
1482+
),
1483+
"malformed authority {host:?} must be rejected: {result:?}"
1484+
);
1485+
}
1486+
}
1487+
14491488
#[test]
14501489
fn admin_host_bracketed_ipv6_literal_is_accepted() {
14511490
let _guard = ENV_MUTEX.lock().unwrap();

0 commit comments

Comments
 (0)