Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: refuse new agents named hive-<anything>

`hive-<hive>` is each hive's read-only forge account, and an agent's forge
account takes the agent's name, so an agent called `hive-<x>` would claim
hive `<x>`'s account. `hive_types::reserved_agent_prefix_violation` names
the rule, and `broken_name_rules` applies it, so both creation paths that
go through it (`POST /api/agents` and placing an undeclared agent on a
hive) refuse such a name for a new agent. An agent already in the roster
keeps its name with a warning, as for every other naming rule.

The email check between hive and agent accounts stays as the second
guard, for agents created before this rule.

Refs #4851
This commit is contained in:
atlas 2026-10-07 21:05:21 +02:00
commit 4e7142ae53
5 changed files with 78 additions and 13 deletions

View file

@ -58,12 +58,13 @@ creates what's missing and re-mints a token that's missing or no longer
matches the forge. A pass whose store or forge reads fail queues nothing, so matches the forge. A pass whose store or forge reads fail queues nothing, so
an outage never rotates every hive's token. an outage never rotates every hive's token.
The account name isn't reserved, so an agent may carry the name `hive-<x>` too. swarm-controller refuses to create an agent whose name starts with `hive-`:
swarm-controller creates the hive's account with the email these accounts own that prefix. An agent that already had such a
`hive-<hive>@hives.hyperhive.local` and refuses an account by that name with name keeps it, so the accounts are also told apart by email. swarm-controller
any other email: it never adds an agent's account to the team or mints a creates the hive's account with the email `hive-<hive>@hives.hyperhive.local`
token for it. An agent's own creation refuses the hive's account the same and refuses an account by that name with any other email: it never adds an
way. agent's account to the team or mints a token for it. An agent's own forge
account setup refuses the hive's account the same way.
hive-c0re reads the token with `forge::reader_token()`, under the hive's own hive-c0re reads the token with `forge::reader_token()`, under the hive's own
store certificate, every time it reads the forge. Where the store holds none, store certificate, every time it reads the forge. Where the store holds none,

View file

@ -93,6 +93,24 @@ pub fn forge_username_violation(name: &str) -> Option<&'static str> {
} }
} }
/// The prefix no new agent's name may start with. `hive-<hive>` is each hive's
/// own forge account (`swarm_secret_client::forge::hive_login`), and every
/// agent's forge account takes the agent's name, so an agent called
/// `hive-<x>` would claim hive `<x>`'s account.
pub const RESERVED_AGENT_PREFIX: &str = "hive-";
/// The reserved-prefix rule `name` breaks as a new agent's name, or `None`.
///
/// Creation sites only, for the reason given at [`is_reserved_name`]. Agent
/// names only: hive names are kept off `hive` by
/// `nix/reserved-hive-fragments.nix`, and the other [`Ident`]s never become a
/// forge user.
#[must_use]
pub fn reserved_agent_prefix_violation(name: &str) -> Option<&'static str> {
name.starts_with(RESERVED_AGENT_PREFIX)
.then_some("must not start with 'hive-': that prefix is reserved for hive forge accounts")
}
/// A validated hive identifier: 1-63 chars of `[a-z0-9-]`. /// A validated hive identifier: 1-63 chars of `[a-z0-9-]`.
/// ///
/// The single ident type for agent names, forge labels, and matrix / github /// The single ident type for agent names, forge labels, and matrix / github
@ -187,7 +205,7 @@ impl<'de> serde::Deserialize<'de> for Ident {
mod ident_tests { mod ident_tests {
use super::{ use super::{
FORGE_USERNAME_MAX_LEN, Ident, forge_username_violation, is_reserved_name, FORGE_USERNAME_MAX_LEN, Ident, forge_username_violation, is_reserved_name,
parse_reserved_names, parse_reserved_names, reserved_agent_prefix_violation,
}; };
#[test] #[test]
@ -302,6 +320,20 @@ mod ident_tests {
} }
} }
#[test]
fn the_hive_prefix_is_refused_for_an_agent() {
for bad in ["hive-x", "hive-alpha", "hive-"] {
assert!(
reserved_agent_prefix_violation(bad).is_some_and(|r| r.contains("hive forge")),
"{bad:?}"
);
}
// The accept arm: `hive` elsewhere in the name, or without the dash.
for ok in ["hivemind", "xhive-y", "hive", "my-hive", "hives-x"] {
assert_eq!(reserved_agent_prefix_violation(ok), None, "{ok:?}");
}
}
#[test] #[test]
fn round_trips_and_serde_validates() { fn round_trips_and_serde_validates() {
let id = Ident::parse("damocles").unwrap(); let id = Ident::parse("damocles").unwrap();

View file

@ -10,9 +10,12 @@
//! token the forge still lists with the same last eight characters and scopes //! token the forge still lists with the same last eight characters and scopes
//! is kept, and a pass whose reads fail queues nothing. //! is kept, and a pass whose reads fail queues nothing.
//! //!
//! ⚠️ The account name is not reserved: an agent may be called `hive-<x>`. //! A new agent may not be called `hive-<x>`
//! Each side refuses an account carrying the other's email, so neither ever //! (`hive_types::reserved_agent_prefix_violation`, checked at agent creation),
//! locks down, joins the team with, or mints a token for the other's account. //! which is the primary guard against an agent claiming a hive's account. An
//! agent created before that rule can still hold such a name, so each side
//! also refuses an account carrying the other's email: neither ever locks
//! down, joins the team with, or mints a token for the other's account.
use std::sync::Arc; use std::sync::Arc;

View file

@ -1882,6 +1882,9 @@ fn broken_name_rules(
"agent name {agent:?} {rule}: the forge refuses it as a username" "agent name {agent:?} {rule}: the forge refuses it as a username"
)); ));
} }
if let Some(rule) = hive_types::reserved_agent_prefix_violation(agent) {
broken.push(format!("agent name {agent:?} {rule}"));
}
broken broken
} }
@ -3892,6 +3895,32 @@ mod tests {
} }
} }
/// The `hive-` prefix belongs to hives' forge accounts, so a new agent
/// may not take it. `hivemind` and `xhive-y` are the control.
#[test]
fn a_new_agent_with_the_hive_prefix_is_refused() {
let raw = nix_reserved();
let mut warnings = Vec::new();
let broken = super::broken_name_rules("hive-x", Some(&raw), &mut warnings);
let err =
super::name_verdict("hive-x", &broken, Ok(false), &mut warnings).expect_err("hive-x");
assert!(err.contains("reserved for hive forge accounts"), "{err}");
for ok in ["hivemind", "xhive-y"] {
let broken = super::broken_name_rules(ok, Some(&raw), &mut warnings);
assert!(broken.is_empty(), "{ok:?}: {broken:?}");
}
assert!(warnings.is_empty(), "{warnings:?}");
}
/// The prefix the rule reserves is the one hive accounts are named with.
#[test]
fn the_reserved_prefix_is_the_hive_account_prefix() {
assert!(
swarm_secret_client::forge::hive_login("alpha")
.starts_with(hive_types::RESERVED_AGENT_PREFIX)
);
}
/// Re-creating an agent under its own name must keep working: a name /// Re-creating an agent under its own name must keep working: a name
/// already in the roster is warned about, never refused. /// already in the roster is warned about, never refused.
#[test] #[test]

View file

@ -40,9 +40,9 @@ pub fn agent_token_path(agent: &str) -> Result<String, Error> {
/// basic-auth header that token travels in. /// basic-auth header that token travels in.
/// ///
/// The `hive-` prefix keeps the account reading as a hive's rather than an /// The `hive-` prefix keeps the account reading as a hive's rather than an
/// agent's, as [`crate::matrix::hive_localpart`] does on the homeserver. It /// agent's, as [`crate::matrix::hive_localpart`] does on the homeserver.
/// does not make the name unique: an agent may be called `hive-<x>` too, and /// `swarm-controller` refuses a new agent named with this prefix, and tells an
/// `swarm-controller` tells the two apart by the account's email. /// older one apart from the hive's account by the account's email.
#[must_use] #[must_use]
pub fn hive_login(hive: &str) -> String { pub fn hive_login(hive: &str) -> String {
format!("hive-{hive}") format!("hive-{hive}")