use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
struct Sandbox {
base: PathBuf,
source: PathBuf,
mind_home: PathBuf,
claude_home: PathBuf,
}
struct Run {
stdout: String,
stderr: String,
success: bool,
}
impl Sandbox {
fn new() -> Sandbox {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base =
std::env::temp_dir().join(format!("mind-prefix-guard-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let source = base.join("agents");
let sb = Sandbox {
base: base.clone(),
source: source.clone(),
mind_home: base.join("mind"),
claude_home: base.join("claude"),
};
write(
&source.join("skills/review/SKILL.md"),
"---\nname: review\ndescription: Review the diff for bugs\n---\n# review skill\n",
);
git(&source, &["-c", "init.defaultBranch=main", "init", "-q"]);
git(&source, &["config", "user.email", "t@t"]);
git(&source, &["config", "user.name", "t"]);
git(&source, &["add", "-A"]);
git(&source, &["commit", "-qm", "initial"]);
std::fs::create_dir_all(&sb.mind_home).unwrap();
sb
}
fn mind(&self, args: &[&str]) -> Run {
let out = Command::new(env!("CARGO_BIN_EXE_mind"))
.args(args)
.env("MIND_HOME", &self.mind_home)
.env("CLAUDE_HOME", &self.claude_home)
.env_remove("MIND_AGENT_HOMES")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null())
.output()
.expect("run mind");
Run {
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
success: out.status.success(),
}
}
fn source_spec(&self) -> String {
self.source.to_string_lossy().into_owned()
}
fn declare_prefix(&self, prefix: &str) {
write(
&self.source.join("mind.toml"),
&format!("[source]\nprefix = \"{prefix}\"\n"),
);
git(&self.source, &["add", "-A"]);
git(&self.source, &["commit", "-qm", "declare prefix"]);
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.base);
}
}
fn write(path: &Path, contents: &str) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, contents).unwrap();
}
fn git(repo: &Path, args: &[&str]) {
std::fs::create_dir_all(repo).unwrap();
let status = Command::new("git")
.args(args)
.current_dir(repo)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
const BLOCKED_UNICODE_CAUSE: &str = "invisible, bidi, or zero-width character";
#[test]
fn meld_namespace_with_bidi_override_is_refused_as_unsafe_prefix() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("pay{}oot", '\u{202E}');
let r = sb.mind(&["meld", &spec, "--namespace", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld --namespace with a bidi-override prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_namespace_short_flag_with_bidi_override_is_refused() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("pay{}oot", '\u{202E}');
let r = sb.mind(&["meld", &spec, "-N", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld -N with a bidi-override prefix must be refused"
);
assert!(r.stderr.contains(BLOCKED_UNICODE_CAUSE), "{}", r.stderr);
}
#[test]
fn meld_namespace_with_tag_block_character_is_refused_as_unsafe_prefix() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("acme{}", '\u{E0041}');
let r = sb.mind(&["meld", &spec, "--namespace", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld --namespace with a tag-block character in the prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_namespace_with_variation_selector_is_refused_as_unsafe_prefix() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("acme{}", '\u{FE0F}');
let r = sb.mind(&["meld", &spec, "--namespace", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld --namespace with a variation-selector character in the prefix must be refused"
);
assert!(r.stderr.contains(BLOCKED_UNICODE_CAUSE), "{}", r.stderr);
}
#[test]
fn meld_declared_prefix_with_bidi_override_is_refused_on_load() {
let sb = Sandbox::new();
let bad_prefix = format!("pay{}oot", '\u{202E}');
sb.declare_prefix(&bad_prefix);
let spec = sb.source_spec();
let r = sb.mind(&["meld", &spec, "--yes"]);
assert!(
!r.success,
"meld of a source declaring an unsafe [source].prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_declared_prefix_with_tag_block_character_is_refused_on_load() {
let sb = Sandbox::new();
let bad_prefix = format!("acme{}", '\u{E0041}');
sb.declare_prefix(&bad_prefix);
let spec = sb.source_spec();
let r = sb.mind(&["meld", &spec, "--yes"]);
assert!(
!r.success,
"meld of a source declaring a tag-block [source].prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_namespace_with_clean_prefix_still_succeeds() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let r = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(
r.success,
"a clean prefix must still be accepted: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
recall.contains("acme:review"),
"the clean prefix must apply normally: {recall}"
);
}