fix(#2391): reject ".." in credential/snapshot names (path-traversal hardening)

Per mara's review: validate_credential_name allowed any [A-Za-z0-9_.-]
byte sequence, which permits a literal ".." substring. Not currently
exploitable (snapshot_path() embeds the label inside a single
format!()'d path component with no "/" in the allowed charset, so
there's no directory to traverse into), but it's a landmine for any
future caller that builds a path via PathBuf::from(name) directly
instead of the current string-embedding. Reject ".." outright in the
shared validator, plus a matching client-side check in hivectl for
fail-fast UX (hive-priv's copy is still the authoritative one).
This commit is contained in:
atlas 2026-07-14 18:42:17 +02:00 committed by mara
commit 720ac81235
2 changed files with 13 additions and 0 deletions

View file

@ -1751,6 +1751,9 @@ async fn subvol_snapshot_create(name: &str, label: String) -> Result<()> {
if !label.starts_with("hive-") {
bail!("snapshot label {label:?} must start with \"hive-\"");
}
if label.contains("..") {
bail!("snapshot label {label:?} must not contain \"..\"");
}
let path = hive_c0re::priv_client::snapshot_agent_subvolume(name, &label)
.await
.with_context(|| format!("snapshot {name} state subvolume (label {label:?})"))?;

View file

@ -455,6 +455,16 @@ fn validate_credential_name(name: &str) -> Result<()> {
{
bail!("invalid credential name {name:?}: must be non-empty [A-Za-z0-9_.-]");
}
// `.` is in the allowed charset (systemd credential ids and snapshot
// labels both legitimately use dots), but a bare `..` reads as a
// directory-traversal token to anyone auditing this path — and a
// caller that builds a path via `PathBuf::from(name)` instead of the
// current `format!("...{name}...")` embedding would actually be
// exploitable. Reject it outright rather than relying on every future
// caller getting the embedding right.
if name.contains("..") {
bail!("invalid credential name {name:?}: must not contain \"..\"");
}
Ok(())
}