From e332e76807453612a730c02a46291056c94ecfcf Mon Sep 17 00:00:00 2001 From: Surma Date: Fri, 6 Nov 2026 22:44:17 +0000 Subject: [PATCH] cred: reject the reserved host and migrate durable fixtures --- host/src/cred/items.rs | 3 ++- host/tests/cred_security.rs | 17 +++++++++++++++++ host/tests/durable.rs | 21 ++++++++++++++++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/host/src/cred/items.rs b/host/src/cred/items.rs index 2bff013..7292aef 100644 --- a/host/src/cred/items.rs +++ b/host/src/cred/items.rs @@ -184,7 +184,8 @@ pub(crate) fn validate(item: &Item) -> Result<()> { }) || r.hosts.iter().any(|p| { let h = p.strip_prefix("*.").unwrap_or(p); - h.is_empty() + p.eq_ignore_ascii_case("host.sausage") + || h.is_empty() || h.starts_with('.') || h.ends_with('.') || h.contains("..") diff --git a/host/tests/cred_security.rs b/host/tests/cred_security.rs index ab63353..1e7595c 100644 --- a/host/tests/cred_security.rs +++ b/host/tests/cred_security.rs @@ -25,6 +25,23 @@ fn parts(uri: &str) -> http::request::Parts { http::Request::get(uri).body(()).unwrap().into_parts().0 } #[test] +fn reserved_host_is_rejected_in_every_case() { + let dir = tempfile::tempdir().unwrap(); + let c = Cred::open(dir.path()).unwrap(); + let caller = Caller::default(); + for host in [ + "host.sausage", + "HOST.SAUSAGE", + "Host.Sausage", + "hOsT.sAuSaGe", + ] { + assert!( + c.create_item(config(host, None), &caller, c.unlocked().unwrap()) + .is_err() + ); + } +} +#[test] fn ports_before_and_after_transform() { let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); diff --git a/host/tests/durable.rs b/host/tests/durable.rs index 3f38086..7b718b6 100644 --- a/host/tests/durable.rs +++ b/host/tests/durable.rs @@ -5,6 +5,7 @@ use sausage::{ requests::{GrantRequest, Overrides}, store::Cred, types::Caller, + webauthn::Decision, }, harness::{run_module, run_module_with_credentials}, world::World, @@ -55,7 +56,7 @@ fn sigkill_preserves_request_wait() -> anyhow::Result<()> { fields: vec![], }, &admin, - "test", + cred.unlocked()?, )?; let mut child = Command::new(std::env::current_exe()?) .args(["--exact", "restart_child", "--nocapture"]) @@ -108,7 +109,12 @@ fn sigkill_preserves_request_wait() -> anyhow::Result<()> { } child.kill()?; child.wait()?; - cred.approve(&id, "yes", Overrides::default(), Some("test"))?; + cred.approve( + &id, + Decision::Approve, + Overrides::default(), + cred.unlocked()?, + )?; for _ in 0..2 { let mut resumed = Command::new(std::env::current_exe()?) .args(["--exact", "restart_child", "--nocapture"]) @@ -239,7 +245,7 @@ async fn decisions_wake_idle_conversation() -> anyhow::Result<()> { fields: vec![], }, &admin, - "test", + cred.unlocked()?, )?; cred.file_request( &admin, @@ -291,8 +297,13 @@ async fn decisions_wake_idle_conversation() -> anyhow::Result<()> { tokio::task::yield_now().await; } match state { - "approved" => cred.approve(&id, "yes", Overrides::default(), Some("test"))?, - "denied" => cred.deny(&id, "no", Some("test"))?, + "approved" => cred.approve( + &id, + Decision::Approve, + Overrides::default(), + cred.unlocked()?, + )?, + "denied" => cred.deny(&id, Decision::Deny, cred.unlocked()?)?, _ => { let db = rusqlite::Connection::open(dir.join("host/cred.sqlite"))?; db.execute("UPDATE requests SET expires=0 WHERE id=?", [&id])?;