dashboard: redo validate_agent_name for mark-all-read (orphaned at #566 merge, mara nag)
This commit is contained in:
parent
036bf6c3c1
commit
bf735357b2
1 changed files with 58 additions and 0 deletions
|
|
@ -1263,6 +1263,36 @@ mod tests {
|
|||
p
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_agent_name_accepts_canonical_shapes() {
|
||||
assert!(validate_agent_name("damocles").is_none());
|
||||
assert!(validate_agent_name("hm1nd").is_none());
|
||||
assert!(validate_agent_name("agent-with-dashes").is_none());
|
||||
assert!(validate_agent_name("snake_case").is_none());
|
||||
assert!(validate_agent_name("mixed_2-3").is_none());
|
||||
let max = "a".repeat(63);
|
||||
assert!(validate_agent_name(&max).is_none(), "63-char name should pass");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_agent_name_rejects_bad_input() {
|
||||
assert!(validate_agent_name("").is_some());
|
||||
let too_long = "a".repeat(64);
|
||||
assert!(validate_agent_name(&too_long).is_some());
|
||||
// Path-traversal attempts.
|
||||
assert!(validate_agent_name("../etc/passwd").is_some());
|
||||
assert!(validate_agent_name("alice/bob").is_some());
|
||||
// Uppercase rejected — canonical lowercase convention.
|
||||
assert!(validate_agent_name("Alice").is_some());
|
||||
// No spaces, dots, special chars.
|
||||
assert!(validate_agent_name("alice bob").is_some());
|
||||
assert!(validate_agent_name("alice.bob").is_some());
|
||||
assert!(validate_agent_name("alice;DROP TABLE messages").is_some());
|
||||
// Non-ASCII (incl. unicode homoglyphs of ASCII dash).
|
||||
assert!(validate_agent_name("damóclès").is_some());
|
||||
assert!(validate_agent_name("alice\u{2013}bob").is_some()); // en-dash
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_symlinks_below_accepts_plain_dirs_and_files() {
|
||||
let root = tmproot("symlink-ok");
|
||||
|
|
@ -1748,6 +1778,31 @@ async fn post_retry_reminder(
|
|||
}
|
||||
}
|
||||
|
||||
/// Validate that a path-param agent name conforms to the hyperhive
|
||||
/// naming whitelist: 1-63 chars of `[a-z0-9_-]`. Rejects empty,
|
||||
/// uppercase, slashes, dots, and any non-ASCII (incl. unicode
|
||||
/// homoglyphs of dash/underscore). Returns `None` on accept, `Some(reason)`
|
||||
/// on reject — caller wraps the reason in a 400 response. Conservative
|
||||
/// whitelist matching `nixos-container` basename rules and the existing
|
||||
/// agent-name convention across the codebase. (mara nag on #566 — this
|
||||
/// is the local fix for `mark-all-read`; general extractor + rollout
|
||||
/// across other endpoints tracked at #572.)
|
||||
fn validate_agent_name(name: &str) -> Option<&'static str> {
|
||||
if name.is_empty() {
|
||||
return Some("agent name must not be empty");
|
||||
}
|
||||
if name.len() > 63 {
|
||||
return Some("agent name must be 63 characters or fewer");
|
||||
}
|
||||
if !name
|
||||
.bytes()
|
||||
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-' || b == b'_')
|
||||
{
|
||||
return Some("agent name must contain only [a-z0-9_-]");
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Operator-driven "clear this agent's inbox" — backs the side-panel
|
||||
/// "mark all read" button (#559). Marks every message addressed to the
|
||||
/// agent as acked (backfilling `delivered_at` for any still-pending
|
||||
|
|
@ -1758,6 +1813,9 @@ async fn post_mark_all_read(
|
|||
State(state): State<AppState>,
|
||||
AxumPath(name): AxumPath<String>,
|
||||
) -> Response {
|
||||
if let Some(reason) = validate_agent_name(&name) {
|
||||
return (StatusCode::BAD_REQUEST, format!("bad agent name: {reason}")).into_response();
|
||||
}
|
||||
match state.coord.broker.mark_all_read(&name) {
|
||||
Ok(n) => {
|
||||
tracing::info!(%name, marked = n, "operator marked all messages read");
|
||||
|
|
|
|||
Loading…
Reference in a new issue