Hmbown/CodeWhale · error

persistent path allow rules must stay within the workspace

Error message

persistent path allow rules must stay within the workspace

What it means

Persistent path allow rules are normalized relative to the rule's workspace scope; if `normalize_workspace_relative_path` returns `None` (path escapes the workspace, e.g. via `..` or an absolute path) or an empty path, the rule is rejected so config can never contain an allow rule pointing outside the workspace.

Solutions

  1. Use a workspace-relative path that stays inside the workspace (e.g. `src/**` or `target/`)
  2. Strip leading absolute prefixes and re-express the path relative to the workspace directory
  3. Remove the `path` field and constrain the rule by exact `command` instead

Example fix

// before
PermissionRule { path: Some("/home/user/project/target".into()), .. }
// after
PermissionRule { path: Some("target".into()), .. }
Defensive patterns

Strategy: validation

Validate before calling

use codewhale_execpolicy::normalize_workspace_relative_path;
fn path_stays_in_workspace(path: &str, workspace: &Path) -> bool {
    normalize_workspace_relative_path(path, workspace)
        .map_or(false, |p| !p.is_empty())
}

Try / catch

match config.append_persistent_allow_rules(&rules) {
    Err(e) if e.to_string().contains("stay within the workspace") => {
        // convert path to workspace-relative and retry
    }
    result => result?,
}

Prevention

When it happens

Trigger: Calling the persistent allow-rule API with `path: Some(..)` where the path is absolute, contains `..` escaping the workspace root, or normalizes to empty (e.g. pointing at the workspace root itself with empty result).

Common situations: Users pasting absolute paths like `/etc` or `C:\dir` into a path allow field; paths like `../shared` that resolve outside the project; an empty path string left in a rule.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/41b19d93a3fb9d70. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/lib.rs:5353

            else {
                bail!("persistent allow rules must be scoped to a workspace");
            };
            if rule.command.is_some() && !rule.command_exact {
                bail!("persistent command allow rules must use exact matching");
            }
            if rule.command.is_none() && rule.path.is_none() {
                bail!("persistent allow rules must match an exact command or path");
            }
            if let Some(command) = rule.command.as_deref()
                && command.trim().is_empty()
            {
                bail!("persistent command allow rules must not be empty");
            }
            if let Some(path) = rule.path.as_deref()
                && codewhale_execpolicy::normalize_workspace_relative_path(path, &workspace)
                    .is_none_or(|path| path.is_empty())
            {
                bail!("persistent path allow rules must stay within the workspace");
            }
        }
        self.append_permission_rules(rules, PermissionAction::Allow)
    }

    fn append_permission_rules(
        &mut self,
        rules: &[ToolAskRule],
        expected_action: PermissionAction,
    ) -> Result<usize> {
        if rules.is_empty() {
            return Ok(0);
        }
        if rules.iter().any(|rule| rule.action != expected_action) {
            bail!(
                "permission rule action does not match requested {:?} persistence",
                expected_action
            );

View on GitHub (pinned to 73e0f67d83)