{"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":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":5278,"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":5260,"sourceCodeEnd":5296,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/lib.rs#L5260-L5296","documentation":"append_allow_rules (crates/config/src/lib.rs:5278) rejects a path allow rule whose path fails codewhale_execpolicy::normalize_workspace_relative_path relative to the rule's workspace, or normalizes to empty. Paths must stay inside the consenting workspace: absolute paths, '..' escapes, and '.'/'/' style empties are all refused so an allow cannot leak beyond the workspace boundary.","triggerScenarios":"A ToolAskRule with path = Some(\"/etc/passwd\"), path = \"../../secrets\", or path = \".\"/\"\" combined with an Allow action passed to append_allow_rules.","commonSituations":"UI sending an absolute path it had displayed, rules copied between workspaces whose relative targets no longer exist inside the new workspace, path built from user free-text input.","solutions":["Convert the approved path to a workspace-relative form (strip the workspace prefix, remove '..')","If the target genuinely lives outside the workspace, it cannot be persistently allowed — keep it session-scoped","Validate the path through normalize_workspace_relative_path in the UI before offering 'always allow'"],"exampleFix":"# before\npath = \"/home/me/project/target/bin/prog\"   # absolute -> rejected\n\n# after\nworkspace = \"/home/me/project\"\npath = \"target/bin/prog\"","handlingStrategy":"validation","validationCode":"let ws = normalize_workspace_scope(rule.workspace.as_deref().unwrap()).unwrap();\nassert!(codewhale_execpolicy::normalize_workspace_relative_path(rule.path.as_deref().unwrap(), &ws)\n    .is_some_and(|p| !p.is_empty())); // before append","typeGuard":"fn is_in_workspace_path(rule: &ToolAskRule) -> bool {\n    let Some(ws) = rule.workspace.as_deref().and_then(normalize_workspace_scope) else { return false };\n    rule.path.as_deref()\n        .is_none_or(|p| normalize_workspace_relative_path(p, &ws).is_some_and(|p| !p.is_empty()))\n}","tryCatchPattern":null,"preventionTips":["Strip the workspace prefix before persisting paths","Never put absolute paths in persistent allow rules","Reject '..' segments and free-text paths at the UI layer"],"tags":["rust","permissions","security","validation","path-traversal"],"backgroundTag":"permission-rule-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}