{"record":{"id":"22d7652b8f6753a3","repo":"zeroclaw-labs/zeroclaw","slug":"edge-tts-binary-path-must-be-one-of-got-ra","errorCode":null,"errorMessage":"Edge TTS binary_path must be one of {:?}, got: {raw_path}","messagePattern":"Edge TTS binary_path must be one of (.+?), got: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/tts.rs","lineNumber":651,"sourceCode":"}\n\nimpl EdgeTtsProvider {\n    /// Allowed basenames for the Edge TTS binary.\n    const ALLOWED_BINARIES: &[&str] = &[\"edge-tts\", \"edge-playback\"];\n\n    pub fn new(alias: &str, config: &TtsProviderConfig) -> Result<Self> {\n        let raw_path = config\n            .binary_path\n            .clone()\n            .filter(|p| !p.trim().is_empty())\n            .unwrap_or_else(|| \"edge-tts\".to_string());\n        if raw_path.contains('/') || raw_path.contains('\\\\') {\n            bail!(\n                \"Edge TTS binary_path must be a bare command name without path separators, got: {raw_path}\"\n            );\n        }\n        if !Self::ALLOWED_BINARIES.contains(&raw_path.as_str()) {\n            bail!(\n                \"Edge TTS binary_path must be one of {:?}, got: {raw_path}\",\n                Self::ALLOWED_BINARIES,\n            );\n        }\n        Ok(Self {\n            alias: alias.to_string(),\n            binary_path: raw_path,\n            #[cfg(test)]\n            binary_args: Vec::new(),\n            timeout: TTS_HTTP_TIMEOUT,\n        })\n    }\n\n    /// Test-only constructor that accepts a script path and timeout so tests\n    /// can drive the `edge-tts` subprocess. The production [`new`](Self::new)\n    /// allowlist stays a security boundary; this exists only in Unix test builds.\n    #[cfg(all(test, unix))]\n    fn new_with_binary(alias: &str, binary_path: &str, timeout: std::time::Duration) -> Self {","sourceCodeStart":633,"sourceCodeEnd":669,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/tts.rs#L633-L669","documentation":"After the path-separator check, EdgeTtsProvider::new requires binary_path to be exactly one of ALLOWED_BINARIES = [\"edge-tts\", \"edge-playback\"]. Together with the separator rule this closes the allowlist: config cannot make the runtime execute any other binary name, so a hostile config edit cannot swap in an arbitrary command.","triggerScenarios":"binary_path = \"edge-tts.exe\" (Windows suffix), \"edge_tts\" (underscore), a renamed wrapper script like \"edge-tts-custom\", or a package-manager alias. Everything except the two exact strings is rejected at construction time.","commonSituations":"Windows users write the .exe suffix out of habit. Teams rename a pinned wrapper for version control and forget the allowlist. The error names the allowed values verbatim, so the fix is mechanical.","solutions":["Use exactly \"edge-tts\" (default) or \"edge-playback\".","On Windows, install the pip package (`pip install edge-tts`); the .exe is then found by its bare name through PATH.","If you need a pinned/wrapped binary, expose it under the exact name \"edge-tts\" earlier in PATH instead of renaming it in config."],"exampleFix":"# before\n[providers.tts.edge.main]\nbinary_path = \"edge-tts.exe\"\n\n# after\n[providers.tts.edge.main]\nbinary_path = \"edge-tts\"","handlingStrategy":"validation","validationCode":"const ALLOWED: &[&str] = &[\"edge-tts\", \"edge-playback\"];\nif let Some(bin) = &edge_cfg.binary_path {\n    assert!(ALLOWED.contains(&bin.as_str()), \"binary_path must be one of {ALLOWED:?}\");\n}","typeGuard":"fn is_allowed_edge_binary(p: &str) -> bool {\n    matches!(p, \"edge-tts\" | \"edge-playback\")\n}","tryCatchPattern":null,"preventionTips":["Write the bare exact strings \"edge-tts\" or \"edge-playback\"; no .exe suffix, no renames.","On Windows, rely on PATH resolution of the pip-installed edge-tts.exe.","For pinned versions, wrap in a script named exactly edge-tts placed earlier in PATH."],"tags":["edge-tts","tts","config","validation","allowlist"],"backgroundTag":"config-validation-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}