hive-forge: validate --forge <label> charset up front

argus flagged (approving) that an unvalidated label could build a path
outside the state dir; mara called it out as a usability issue in its
own right, not just a low-risk security nit — a typo'd label should
give a precise 'not a valid label' error, not a confusing file-not-found
or an unexpected traversal.

Reject anything outside the plain lowercase+digits+hyphens charset
dashboard/extra_forges.rs already enforces on write, before touching
the filesystem at all.
This commit is contained in:
iris 2026-07-14 19:37:20 +02:00
commit 46bdb78466

View file

@ -241,13 +241,24 @@ pub fn index(n: u64) -> Result<i64> {
/// the exact same two files `dashboard/extra_forges.rs` writes, so the
/// read side can't drift from the write side. An unknown label (either
/// file missing) is a clear error listing the labels actually found in
/// the state dir, not a raw file-not-found.
/// the state dir, not a raw file-not-found. A label outside the plain
/// identifier charset the dashboard accepts (e.g. a typo containing
/// `/` or `..`) is rejected up front with the same charset spelled out,
/// rather than silently building a nonsense/traversing path and
/// surfacing a confusing file error later.
fn resolve_credentials(forge_label: Option<&str>) -> Result<(String, String)> {
let Some(label) = forge_label else {
let base = std::env::var("HIVE_FORGE_URL").unwrap_or_else(|_| DEFAULT_URL.to_owned());
let token = read_token().context("read forge-token")?;
return Ok((base, token));
};
if !is_plain_ident(label) {
bail!(
"hive-forge: invalid --forge label {label:?} — must be lowercase \
letters, digits, and hyphens only (same rule the dashboard's \
FORGES tab enforces)"
);
}
let dir = state_dir();
let token_path = dir.join(format!("forge-{label}-token"));
@ -270,6 +281,19 @@ fn resolve_credentials(forge_label: Option<&str>) -> Result<(String, String)> {
bail!("hive-forge: no such forge {label:?} — provisioned forges: {known}");
}
/// Plain-identifier check matching `dashboard/extra_forges.rs`'s
/// `is_plain_ident` (itself matching hive-priv's `validate_name_chars`)
/// — lowercase ascii + digits + hyphens only. Rejecting anything else
/// up front (rather than just letting a weird label fail to resolve a
/// file) turns a confusing "no such forge" surprise into a precise
/// "that's not a valid label" one, and incidentally means a label like
/// `../../etc` can't be used to build a path outside the state dir.
fn is_plain_ident(s: &str) -> bool {
!s.is_empty()
&& s.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
}
/// Sidecar shape `dashboard/extra_forges.rs` writes alongside each
/// `forge-<label>-token` file: just the base URL, pinned to the same
/// `base_url` JSON key the write side uses.