{"record":{"id":"dad815a812985ba5","repo":"jdx/mise","slug":"an-implied-source-entry-key-must-not-contain","errorCode":null,"errorMessage":"an implied source entry key must not contain '..'","messagePattern":"an implied source entry key must not contain '\\.\\.'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/files.rs","lineNumber":257,"sourceCode":"        }\n    }\n    Ok(implied_source)\n}\n\nfn logical_source_path(key: &str) -> Result<PathBuf> {\n    let path = file::replace_path(key);\n    if !path.is_relative() {\n        bail!(\n            \"destination variants require an explicit source unless the entry key is a relative source path\"\n        );\n    }\n    let mut relative = PathBuf::new();\n    for component in path.components() {\n        match component {\n            std::path::Component::Normal(component) => relative.push(component),\n            std::path::Component::CurDir => {}\n            std::path::Component::ParentDir => {\n                bail!(\"an implied source entry key must not contain '..'\");\n            }\n            std::path::Component::RootDir | std::path::Component::Prefix(_) => {\n                bail!(\"an implied source entry key must be relative\");\n            }\n        }\n    }\n    if relative.as_os_str().is_empty() {\n        bail!(\"an implied source entry key must not be empty\");\n    }\n    Ok(relative)\n}\n\n/// Inactive variants can contain another platform's absolute path syntax.\n/// The selected destination is still checked with native path rules before use.\nfn variant_target_is_absolute(target: &str) -> bool {\n    let bytes = target.as_bytes();\n    resolve_target_arg(target).is_absolute()\n        || target.starts_with('/')","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/files.rs#L239-L275","documentation":"`logical_source_path` builds the implied source from the entry key component by component. A `..` component would make the implied source escape the dotfiles root, which is a path-traversal risk and ambiguous to resolve, so the library rejects any entry key containing `..` when the key is used as an implied source.","triggerScenarios":"An entry key like `\"dotfiles/../secrets/rc\"` used with destination `variants` and no explicit `source`, so the key is interpreted as the source path.","commonSituations":"Users write `../shared/zshrc` style keys to reach a file outside the config directory while also declaring variants, not realizing implied sources must stay inside the dotfiles tree.","solutions":["Remove `..` from the entry key and point it directly at the file inside the dotfiles root","Set an explicit `source` field with the actual path instead of relying on the key","Reorganize files so the source lives under the dotfiles directory"],"exampleFix":"// before\n\"../shared/gitconfig\" = { variants = [ { target = \"~/.gitconfig\" } ] }\n// after\n\"~/.gitconfig\" = { source = \"shared/gitconfig\", variants = [ { target = \"~/.gitconfig\" } ] }","handlingStrategy":"validation","validationCode":"function keyHasParentDir(key) { return key.split('/').includes('..'); }\n// reject: if (keyHasParentDir(entryKey)) throw new Error('entry key must not contain ..');","typeGuard":"const isSafeRelativeKey = (key: string) => !key.split(/[\\\\/]/).includes('..') && !path.isAbsolute(key);","tryCatchPattern":null,"preventionTips":["Keep implied sources inside the dotfiles root","Prefer explicit `source` over `..`-relative keys","Lay out dotfiles so nothing needs to be referenced from outside the root"],"tags":["config","dotfiles","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}