hive-c0re: decide matrix token presence from metadata, not by reading it

hive-priv writes an agent's matrix-token 0600 and owned by the agent,
so hive-c0re, running as hive-core, cannot read it. The read-based
token-present guard in ensure_user_for therefore never fired, and every
matrix sweep (boot, every 30 minutes, every rebuild) re-minted each
agent's token through the appservice login and restarted its
hive-matrix-daemon.

Decide presence with a stat instead: a non-empty regular file counts as
present. hive-core can stat the file through the 0755 state dir.

Closes #4665
This commit is contained in:
atlas 2026-09-24 11:01:03 +02:00 • committed by mara
commit 46d0b46670

View file

@ -132,6 +132,15 @@ fn token_path(name: &hive_types::Ident) -> PathBuf {
Coordinator::agent_notes_dir(name).join("matrix-token")
}
/// Whether an agent's token file is present: a non-empty regular file.
/// Decided from metadata alone, because hive-priv writes the file 0600
/// and owned by the agent, so `hive-core` cannot read it but can stat it
/// through the 0755 state dir. A check that reads the file always
/// fails here and makes every sweep re-mint the token.
fn token_file_present(path: &std::path::Path) -> bool {
std::fs::metadata(path).is_ok_and(|m| m.is_file() && m.len() > 0)
}
/// Password file for the agent's matrix account. Stored OUTSIDE the
/// purgeable `agent_state_root` tree so it survives `destroy --purge`
/// and allows re-login recovery when the same agent name is re-spawned.
@ -719,11 +728,7 @@ pub async fn ensure_user_for(client: &reqwest::Client, name: &str, as_token: &st
use std::os::unix::fs::PermissionsExt;
let agent = hive_types::Ident::parse(name)
.map_err(|e| anyhow::anyhow!("invalid agent name {name:?}: {e}"))?;
let path = token_path(&agent);
if path.exists()
&& let Ok(existing) = std::fs::read_to_string(&path)
&& !existing.trim().is_empty()
{
if token_file_present(&token_path(&agent)) {
tracing::debug!(%name, "matrix: token already present");
return Ok(());
}
@ -2096,4 +2101,30 @@ mod tests {
let err = extract_access_token(&body).unwrap_err();
assert!(err.to_string().contains("missing access_token"));
}
#[test]
fn token_file_present_only_for_a_non_empty_regular_file() {
let dir = tempfile::tempdir().expect("tempdir");
let token = dir.path().join("matrix-token");
assert!(!token_file_present(&token), "missing file");
std::fs::write(&token, "").unwrap();
assert!(!token_file_present(&token), "empty file");
std::fs::write(&token, "tok\n").unwrap();
assert!(token_file_present(&token), "non-empty file");
assert!(!token_file_present(dir.path()), "directory");
}
/// The sweep runs as `hive-core`, which cannot read the agent-owned
/// 0600 token file. `chmod 000` does not stop root, so this test uses
/// content that `read_to_string` rejects instead: the predicate must
/// still report the file as present, which holds only if it never
/// reads the content.
#[test]
fn token_file_present_does_not_read_the_content() {
let dir = tempfile::tempdir().expect("tempdir");
let token = dir.path().join("matrix-token");
std::fs::write(&token, [0xff, 0xfe]).unwrap();
assert!(std::fs::read_to_string(&token).is_err());
assert!(token_file_present(&token));
}
}