swarm-controller: validate create_agent's name via hive_types::Ident

This commit is contained in:
damocles 2026-08-16 23:18:33 +02:00 committed by mara
commit 7577d149cf
3 changed files with 19 additions and 2 deletions

1
Cargo.lock generated
View file

@ -4590,6 +4590,7 @@ dependencies = [
"futures-util",
"hive-jobq",
"hive-jobq-wire",
"hive-types",
"reqwest 0.13.1",
"serde",
"serde_json",

View file

@ -34,6 +34,10 @@ futures-util.workspace = true
# same shape `hive-c0re/src/job_queue/scheduler.rs` uses over its own graph.
hive-jobq.workspace = true
hive-jobq-wire.workspace = true
# Validates `POST /api/agents`' `name` before it becomes `agent`/`repo`
# everywhere downstream — see `create_agent`'s doc comment for why this is
# defense-in-depth, not the only gate (per an argus review finding).
hive-types.workspace = true
# `auth`'s bridge client — same crate the bridge itself uses to define the
# request/response shape, so the two ends cannot drift. `forge.rs` also
# uses this directly for `StatusCode` in its error-classification helpers.

View file

@ -508,12 +508,21 @@ struct CreateAgentResponse {
/// `SwarmNodeKind` variants have: each node kind's `dead_code` bound is
/// the whole reason the executor arm and this endpoint had to land in the
/// same change as the variant, not as a follow-up.
///
/// `name` is validated with [`hive_types::Ident::parse`] before it becomes
/// `agent`/`repo` anywhere downstream — not the *only* gate (`CreateIdentity`
/// runs first and validates server-side too), but the upstream check is a
/// few hops removed from where an unvalidated name would do damage
/// (`forge::seed_agent_config` interpolates `agent` into a nix comment
/// line). Flagged in review as safe today but fragile if a second caller
/// of these nodes ever appears; validating here closes it locally.
#[utoipa::path(
post,
path = "/api/agents",
request_body = CreateAgentRequest,
responses(
(status = 200, description = "job chain queued", body = CreateAgentResponse),
(status = 400, description = "`name` is not a valid identifier", body = String),
(status = 500, description = "the job chain could not be queued", body = String),
),
tag = "agents"
@ -522,12 +531,15 @@ async fn create_agent(
State(state): State<AppState>,
Json(req): Json<CreateAgentRequest>,
) -> Result<Json<CreateAgentResponse>, (axum::http::StatusCode, String)> {
let agent = hive_types::Ident::parse(&req.name)
.map_err(|reason| (axum::http::StatusCode::BAD_REQUEST, reason.to_owned()))?
.into_string();
let repo = agent.clone();
let mut sched = state
.jobq
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let agent = req.name;
let repo = agent.clone();
let ids = sched
.insert_job(None, |b| {
let create_identity = b.node(SwarmNodeKind::CreateIdentity {