{"record":{"id":"baa3c1d2336beeb9","repo":"jdx/mise","slug":"dotfiles-req-target-raw-target-is-not-a-saf","errorCode":null,"errorMessage":"[dotfiles].\"{req.target_raw}\": target is not a safe OCI path","messagePattern":"\\[dotfiles\\]\\.\"(.+?)\": target is not a safe OCI path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/oci/builder.rs","lineNumber":1099,"sourceCode":"        (files, dirs)\n    }\n}\n\nfn oci_target_path(req: &FileRequest) -> Result<String> {\n    let raw = req.target_raw.as_str();\n    let path = if raw == \"~\" {\n        \"root\".to_string()\n    } else if let Some(rest) = raw.strip_prefix(\"~/\") {\n        format!(\"root/{rest}\")\n    } else {\n        req.target\n            .strip_prefix(\"/\")\n            .map_err(|_| eyre::eyre!(\"dotfile target must be absolute: {}\", req.target_raw))?\n            .to_string_lossy()\n            .replace('\\\\', \"/\")\n    };\n    if path.is_empty() || path.split('/').any(|p| p == \"..\") {\n        bail!(\n            \"[dotfiles].\\\"{}\\\": target is not a safe OCI path\",\n            req.target_raw\n        );\n    }\n    Ok(path)\n}\n\nfn source_mode(path: &std::path::Path) -> Result<u32> {\n    let md = path.metadata()?;\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        Ok(md.permissions().mode() & 0o7777)\n    }\n    #[cfg(not(unix))]\n    {\n        let _ = md;\n        Ok(0o644)","sourceCodeStart":1081,"sourceCodeEnd":1117,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/oci/builder.rs#L1081-L1117","documentation":"Before building a dotfiles OCI layer, each requested target path is normalized (absolute, forward slashes) and validated. This error is thrown when the resulting path is empty or any component is '..', because such paths would escape or be meaningless inside the image root and could enable path traversal in the OCI layer.","triggerScenarios":"Calling build_dotfiles_layer with a req.target_raw that either becomes empty after stripping the leading '/' (e.g. target \"/\"), or contains a '..' component in any path segment (e.g. \"~/../etc/passwd\" or \"/etc/../secret\").","commonSituations":"Mistyped dotfile targets like \"~/..\", templated targets that expand to empty, copy-pasted relative paths such as \"../dotfiles/zshrc\", or targets using '..' to redirect outside the home directory.","solutions":["Replace '..' components in the dotfile target with an absolute path rooted at '/' (or the correct home-relative location).","Ensure the target is a non-empty absolute path, e.g. target = \"/home/user/.zshrc\" not \"/\" or \"../x\".","If you intended a path outside the image via traversal, restructure the image so the file lives under a real prefix instead."],"exampleFix":"// before\n[[dotfiles]]\nsource = \"zshrc\"\ntarget = \"../.zshrc\"\n// after\n[[dotfiles]]\nsource = \"zshrc\"\ntarget = \"/home/user/.zshrc\"","handlingStrategy":"validation","validationCode":"fn is_safe_oci_path(target: &str) -> bool {\n    let path = target.strip_prefix('/').unwrap_or(target);\n    !path.is_empty() && !path.split('/').any(|p| p == \"..\")\n}\nassert!(is_safe_oci_path(\"/home/user/.zshrc\"));","typeGuard":null,"tryCatchPattern":"match oci_target_path(&req) {\n    Err(e) if e.to_string().contains(\"not a safe OCI path\") => {\n        eprintln!(\"rewrite target without '..' and as absolute path: {}\", req.target_raw);\n    }\n    p => p,\n}","preventionTips":["Always write dotfile targets as absolute paths with no '..' components.","Validate targets in CI before building images.","Avoid templated targets that can expand to empty strings."],"tags":["oci","dotfiles","path-validation"],"backgroundTag":"path-traversal-blocked","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"}