{"record":{"id":"4c4e59a7d904e325","repo":"jdx/mise","slug":"invalid-or-unenrolled-permission-path-path","errorCode":null,"errorMessage":"invalid or unenrolled permission path {path}","messagePattern":"invalid or unenrolled permission path (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/manifest.rs","lineNumber":387,"sourceCode":"            }\n            let mut variants = std::collections::BTreeSet::new();\n            super::select::validate(&entry.variants)?;\n            for variant in &entry.variants {\n                let name = variant.name();\n                if name.contains('@')\n                    || !super::sync::layout::is_safe_branch_path(&name)\n                    || !variants.insert(name)\n                {\n                    bail!(\"invalid or repeated variant for {}\", entry.path);\n                }\n            }\n        }\n        for (path, bits) in &self.permissions {\n            if *bits > 0o777\n                || !super::sync::layout::is_safe_branch_path(path)\n                || !self.owns_stream(path)\n            {\n                bail!(\"invalid or unenrolled permission path {path}\");\n            }\n        }\n        Ok(())\n    }\n\n    pub(crate) fn read(repo: &HistoryRepo, tree: &str) -> Result<Option<Self>> {\n        let Some((mode, oid)) = repo.object_at(tree, PATH)? else {\n            return Ok(None);\n        };\n        if mode != \"100644\" {\n            bail!(\"dotfile enrollment metadata must be a regular file\");\n        }\n        #[derive(Deserialize)]\n        struct FormatHeader {\n            format: u64,\n        }\n        let bytes = repo.cat_object_bounded(&oid, 4 * 1024 * 1024)?;\n        let header: FormatHeader = serde_json::from_slice(&bytes)?;","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/manifest.rs#L369-L405","documentation":"Each entry in the manifest's permissions map must have mode bits no greater than 0o777, a path accepted by is_safe_branch_path, and the path must correspond to an enrolled stream via owns_stream. This error rejects permission entries that are malformed or reference paths not enrolled in the manifest.","triggerScenarios":"Calling validate on a manifest where a permissions key has bits > 0o777, is an unsafe branch path, or names a path for which owns_stream returns false (no matching stream enrollment).","commonSituations":"Setting permission bits with extra flag bits (setuid/setgid/sticky) in the manifest; adding permissions for a dotfile before enrolling its stream; typos in the path so it doesn't match an enrolled stream.","solutions":["Ensure the permissions path exactly matches an enrolled stream path (enroll the stream first).","Clamp permission bits to 0o777 (drop setuid/setgid/sticky bits).","Fix the path so it passes is_safe_branch_path (no traversal, relative, safe chars).","Remove the stale permissions entry if the stream was intentionally unenrolled."],"exampleFix":"// before (manifest JSON)\n{\"permissions\": {\"dotfiles/vimrc\": 0o4755}}\n// after\n{\"permissions\": {\"dotfiles/vimrc\": 0o755}}","handlingStrategy":"validation","validationCode":"let bits_ok = bits & !0o777 == 0;\nlet enrolled = manifest.streams.iter().any(|s| s.path == path);\nif !(bits_ok && enrolled) { return Err(format!(\"bad permission entry: {path}\")); }","typeGuard":"fn valid_permission(path: &str, bits: u32) -> bool { bits <= 0o777 && !path.contains(\"..\") }","tryCatchPattern":"if let Err(e) = manifest.validate() {\n    if e.to_string().contains(\"invalid or unenrolled permission path\") {\n        // enroll the stream or drop the permission entry\n    }\n}","preventionTips":["Enroll a stream before adding permissions for its path.","Mask permission bits with 0o777 before storing.","Keep permission paths verbatim identical to enrolled stream paths."],"tags":["validation","manifest","permissions","dotfiles"],"backgroundTag":"schema-validation-failed","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}