Hmbown/CodeWhale · error
read-only command must name a bare allowlisted executable
Error message
read-only command must name a bare allowlisted executable
What it means
`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.
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
Example fix
// before
run_readonly("./node_modules/.bin/tsc --version")
// after
run_readonly("tsc --version") Defensive patterns
Strategy: validation
Validate before calling
fn program_is_bare(cmd: &str) -> bool {
shell_words::split(cmd).ok()
.and_then(|argv| argv.first().cloned())
.map(|p| std::path::Path::new(&p).components().count() == 1)
.unwrap_or(false)
}
if !program_is_bare(&cmd) { eprintln!("use a bare executable name on PATH"); } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- classifier-approved Git read did not keep its subcommand in…
- allowlisted read-only executable
- classifier-approved Git read was missing its literal…
- Codewhale-owned OAuth path escaped the credentials directory
- Codewhale-owned OAuth path has an invalid basename
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/404de4f8cace90d8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/shell.rs:4391
fn readonly_sanitized_path(workspace: &std::path::Path) -> Option<String> {
let path = std::env::var_os("PATH")?;
readonly_sanitized_path_from(workspace, &path).map(|value| value.to_string_lossy().into_owned())
}
fn resolve_readonly_program(program: &str, workspace: &std::path::Path) -> Result<PathBuf> {
let path = std::env::var_os("PATH")
.ok_or_else(|| anyhow!("no executable search path is configured"))?;
resolve_readonly_program_from_path(program, workspace, &path)
}
fn resolve_readonly_program_from_path(
program: &str,
workspace: &std::path::Path,
path: &std::ffi::OsStr,
) -> Result<PathBuf> {
let workspace = workspace.canonicalize()?;
if std::path::Path::new(program).components().count() != 1 {
return Err(anyhow!(
"read-only command must name a bare allowlisted executable"
));
}
let safe_path = readonly_sanitized_path_from(&workspace, path).ok_or_else(|| {
anyhow!("no trusted executable search path remains outside the workspace")
})?;
let names = if cfg!(windows) {
vec![format!("{program}.exe"), format!("{program}.com")]
} else {
vec![program.to_string()]
};
for directory in std::env::split_paths(&safe_path) {
for name in &names {
let candidate = directory.join(name);
if !candidate.is_file() {
continue;
}
#[cfg(unix)]View on GitHub (pinned to 73e0f67d83)