{"record":{"id":"404de4f8cace90d8","repo":"Hmbown/CodeWhale","slug":"read-only-command-must-name-a-bare-allowlisted-exe","errorCode":null,"errorMessage":"read-only command must name a bare allowlisted executable","messagePattern":"read-only command must name a bare allowlisted executable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/shell.rs","lineNumber":4391,"sourceCode":"fn readonly_sanitized_path(workspace: &std::path::Path) -> Option<String> {\n    let path = std::env::var_os(\"PATH\")?;\n    readonly_sanitized_path_from(workspace, &path).map(|value| value.to_string_lossy().into_owned())\n}\n\nfn resolve_readonly_program(program: &str, workspace: &std::path::Path) -> Result<PathBuf> {\n    let path = std::env::var_os(\"PATH\")\n        .ok_or_else(|| anyhow!(\"no executable search path is configured\"))?;\n    resolve_readonly_program_from_path(program, workspace, &path)\n}\n\nfn resolve_readonly_program_from_path(\n    program: &str,\n    workspace: &std::path::Path,\n    path: &std::ffi::OsStr,\n) -> Result<PathBuf> {\n    let workspace = workspace.canonicalize()?;\n    if std::path::Path::new(program).components().count() != 1 {\n        return Err(anyhow!(\n            \"read-only command must name a bare allowlisted executable\"\n        ));\n    }\n    let safe_path = readonly_sanitized_path_from(&workspace, path).ok_or_else(|| {\n        anyhow!(\"no trusted executable search path remains outside the workspace\")\n    })?;\n    let names = if cfg!(windows) {\n        vec![format!(\"{program}.exe\"), format!(\"{program}.com\")]\n    } else {\n        vec![program.to_string()]\n    };\n    for directory in std::env::split_paths(&safe_path) {\n        for name in &names {\n            let candidate = directory.join(name);\n            if !candidate.is_file() {\n                continue;\n            }\n            #[cfg(unix)]","sourceCodeStart":4373,"sourceCodeEnd":4409,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/shell.rs#L4373-L4409","documentation":"`resolve_readonly_program_from_path` only resolves bare executable names for the read-only shell. If the command's program token contains a path separator (any component count other than 1, e.g. `./script.sh`, `/usr/bin/git`, `../bin/tool`), it is rejected so that a classifier-approved command cannot smuggle in an arbitrary or workspace-relative binary through the program position.","triggerScenarios":"Running a read-only command whose first token is a relative or absolute path: `./node_modules/.bin/tsc --version`, `/bin/ls`, `subdir/tool arg`. Any separator in the program token triggers the error before PATH lookup begins.","commonSituations":"Agents or scripts emitting absolute paths for well-known tools; invoking project-local binaries like `./gradlew`; Windows-style paths surviving `normalize_windows_command_paths`; users pasting shell commands with explicit paths.","solutions":["Invoke the tool by bare name (`tsc`, `ls`) and let the resolver find it on the sanitized PATH","Install the needed binary into a standard directory on PATH instead of calling it by path","If the tool is genuinely needed by path, use the normal permission-prompted shell path rather than the read-only fast path"],"exampleFix":"// before\nrun_readonly(\"./node_modules/.bin/tsc --version\")\n// after\nrun_readonly(\"tsc --version\")","handlingStrategy":"validation","validationCode":"fn program_is_bare(cmd: &str) -> bool {\n    shell_words::split(cmd).ok()\n        .and_then(|argv| argv.first().cloned())\n        .map(|p| std::path::Path::new(&p).components().count() == 1)\n        .unwrap_or(false)\n}\nif !program_is_bare(&cmd) { eprintln!(\"use a bare executable name on PATH\"); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always invoke allowlisted tools by bare name, never by path","Install project-local binaries globally or onto a system PATH directory","Keep the first token of read-only commands free of '/', '\\\\', and '.' separators"],"tags":["shell","security","path-traversal","allowlist"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}