{"record":{"id":"41b19d93a3fb9d70","repo":"Hmbown/CodeWhale","slug":"persistent-path-allow-rules-must-stay-within-the-w","errorCode":null,"errorMessage":"persistent path allow rules must stay within the workspace","messagePattern":"persistent path allow rules must stay within the workspace","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":5353,"sourceCode":"            else {\n                bail!(\"persistent allow rules must be scoped to a workspace\");\n            };\n            if rule.command.is_some() && !rule.command_exact {\n                bail!(\"persistent command allow rules must use exact matching\");\n            }\n            if rule.command.is_none() && rule.path.is_none() {\n                bail!(\"persistent allow rules must match an exact command or path\");\n            }\n            if let Some(command) = rule.command.as_deref()\n                && command.trim().is_empty()\n            {\n                bail!(\"persistent command allow rules must not be empty\");\n            }\n            if let Some(path) = rule.path.as_deref()\n                && codewhale_execpolicy::normalize_workspace_relative_path(path, &workspace)\n                    .is_none_or(|path| path.is_empty())\n            {\n                bail!(\"persistent path allow rules must stay within the workspace\");\n            }\n        }\n        self.append_permission_rules(rules, PermissionAction::Allow)\n    }\n\n    fn append_permission_rules(\n        &mut self,\n        rules: &[ToolAskRule],\n        expected_action: PermissionAction,\n    ) -> Result<usize> {\n        if rules.is_empty() {\n            return Ok(0);\n        }\n        if rules.iter().any(|rule| rule.action != expected_action) {\n            bail!(\n                \"permission rule action does not match requested {:?} persistence\",\n                expected_action\n            );","sourceCodeStart":5335,"sourceCodeEnd":5371,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/lib.rs#L5335-L5371","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Use a workspace-relative path that stays inside the workspace (e.g. `src/**` or `target/`)","Strip leading absolute prefixes and re-express the path relative to the workspace directory","Remove the `path` field and constrain the rule by exact `command` instead"],"exampleFix":"// before\nPermissionRule { path: Some(\"/home/user/project/target\".into()), .. }\n// after\nPermissionRule { path: Some(\"target\".into()), .. }","handlingStrategy":"validation","validationCode":"use codewhale_execpolicy::normalize_workspace_relative_path;\nfn path_stays_in_workspace(path: &str, workspace: &Path) -> bool {\n    normalize_workspace_relative_path(path, workspace)\n        .map_or(false, |p| !p.is_empty())\n}","typeGuard":null,"tryCatchPattern":"match config.append_persistent_allow_rules(&rules) {\n    Err(e) if e.to_string().contains(\"stay within the workspace\") => {\n        // convert path to workspace-relative and retry\n    }\n    result => result?,\n}","preventionTips":["Only offer workspace-relative paths in path-allow UIs","Reject absolute paths and `..` segments at input time","Run `normalize_workspace_relative_path` on user paths before building rules"],"tags":["config","permissions","path","validation"],"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"}