diff --git a/crates/brokerd/src/grants.rs b/crates/brokerd/src/grants.rs index c244fcd..a36809c 100644 --- a/crates/brokerd/src/grants.rs +++ b/crates/brokerd/src/grants.rs @@ -260,6 +260,14 @@ fn check_grant(grant: &LoadedGrant, problems: &mut Vec) { None, format!("{:?} is not a valid absolute path", path), ); + } else if path.contains([':', ',']) { + // Mounted as `--volume=::ro`, where both are separators. + push( + problems, + file.clone(), + None, + format!("{:?} cannot be mounted: it contains ':' or ','", path), + ); } } // Rule 8: every host must be a valid host pattern. diff --git a/crates/brokerd/tests/grants_mount.rs b/crates/brokerd/tests/grants_mount.rs new file mode 100644 index 0000000..00041ac --- /dev/null +++ b/crates/brokerd/tests/grants_mount.rs @@ -0,0 +1,45 @@ +//! A grant path is mounted into the tool container as `--volume=::ro`, so a path with +//! `:` or `,` in it cannot be granted: the set is invalid, as for any other bad grant (M3b spec, +//! section 3). Do not edit. + +#[path = "support/tmp.rs"] +mod tmp; + +use brokerd::grants::load; +use tmp::TempDir; + +fn grant_with_path(path: &str) -> String { + format!( + "tool = \"read_file\"\nmode = \"auto\"\nmax_taint = \"secret\"\n[constraints]\npaths = [{path:?}]\n" + ) +} + +#[test] +fn a_path_with_a_colon_or_a_comma_makes_the_set_invalid() { + for path in ["/home/kyle/a:b", "/home/kyle/a,b", "/x:/y", "/n,ro"] { + let dir = TempDir::new("mount-bad"); + dir.write("notes.toml", &grant_with_path(path)); + let problems = load(dir.path()).expect_err(path); + assert_eq!(problems.len(), 1, "{path}: {problems:?}"); + assert_eq!(problems[0].file, "notes.toml"); + assert!( + problems[0].problem.contains("cannot be mounted"), + "{path}: {}", + problems[0].problem + ); + } +} + +#[test] +fn other_punctuation_is_still_fine() { + for path in [ + "/home/kyle/a b", + "/home/kyle/a;b", + "/home/kyle/a=b", + "/home/kyle/a.b-c_d", + ] { + let dir = TempDir::new("mount-ok"); + dir.write("notes.toml", &grant_with_path(path)); + assert!(load(dir.path()).is_ok(), "{path}"); + } +} diff --git a/docs/implementer-log.md b/docs/implementer-log.md index 931f4cc..b5afd48 100644 --- a/docs/implementer-log.md +++ b/docs/implementer-log.md @@ -419,4 +419,5 @@ each task's commit. Three rows were malformed by the first run's orchestrator: t six cells, and its notes had been pasted into task 05's row; task 10's notes into task 11's; and task 01's stopped row carried the notes of M2b task 11. Each is moved back or removed, with a bracketed mark, and pipes inside code are escaped so every row has its eight cells. +| M3b/03-brokerd-grant-mount-rule | 2026-09-22 | done | 1 | pass | none | Copied `tests/grants_mount.rs` from the plan's `files/`. Added a third arm to the path loop in `check_grant` (grants.rs:263), an `else if path.contains([':', ','])` checked only when the first two arms did not apply, reporting `"{:?} cannot be mounted: it contains ':' or ','"`. The `else if` chain means a path already reported as invalid is not reported twice. `cargo fmt --all` kept the `push` multi-line (the single-line form in the task exceeds 100 columns); the wording matches the task verbatim. Both suites pass (17 grants, 2 mount); `make gate` prints `gate: ok` on the first run. | ? |