jdx/mise · error
[dotfiles]." ": target is not a safe OCI path
Error message
[dotfiles]."{}": target is not a safe OCI path What it means
oci_target_path normalizes a [dotfiles] target into an in-image path: it must be absolute (checked earlier), have the leading '/' stripped, use forward slashes, and every path component must be non-empty and not '..'. If the resulting path is empty or contains a '..' component, the builder rejects it because '..' could escape the intended home directory inside the image (path traversal in the layer).
Solutions
- Rewrite the target as an absolute path without '..' components, e.g. "/etc/passwd" instead of "~/../etc/passwd".
- If a variable is templated into the target, validate/sanitize it (no '..') before rendering the config.
- Fix empty targets — give each [dotfiles] entry a concrete absolute target path.
Example fix
// before [dotfiles."~/../etc/motd"] mode = "content" contents = "welcome" // after [dotfiles."/etc/motd"] mode = "content" contents = "welcome"
Defensive patterns
Strategy: validation
Validate before calling
// reject targets containing '..' or empty before config load
let t = target.trim_start_matches('~');
if t.split('/').any(|c| c == "..") || t.is_empty() {
return Err(format!("unsafe dotfiles target: {target}"));
} Type guard
fn is_safe_oci_target(target: &str) -> bool {
let p = target.strip_prefix('/').unwrap_or(target);
!p.is_empty()
&& !p.split('/').any(|c| c.is_empty() || c == "." || c == "..")
} Prevention
- Write dotfiles targets as absolute paths with no '..' components.
- Sanitize variables interpolated into targets; never allow user input with '..'.
- Keep all dotfiles inside the image home directory; use other mechanisms for system paths.
- Add a config-lint rule rejecting '..' in [dotfiles] targets.
When it happens
Trigger: build_dotfiles_layer with a [dotfiles] target like "~/../etc/passwd", "/a/../b", "..", or an empty target after stripping '/', producing a path with a '..' component or an empty string.
Common situations: Hand-written targets using relative escapes to reach system paths; templated targets where a variable resolves to '..' or empty; typos like a trailing '..' when attempting to install a file outside the home directory.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- --changed requires trusted configuration
- copy image path ends with `/` but is not a directory…
- [dotfiles]: duplicate OCI file path
- [dotfiles]: duplicate OCI path
- [dotfiles]." ": mode symlink-each requires a directory…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/5718d76a30f6f007.
Report an issue: GitHub.
Appendix: source
Thrown at src/oci/builder.rs:1155
(files, dirs)
}
}
fn oci_target_path(req: &FileRequest) -> Result<String> {
let raw = req.target_raw.as_str();
let path = if raw == "~" {
"root".to_string()
} else if let Some(rest) = raw.strip_prefix("~/") {
format!("root/{rest}")
} else {
req.target
.strip_prefix("/")
.map_err(|_| eyre::eyre!("dotfile target must be absolute: {}", req.target_raw))?
.to_string_lossy()
.replace('\\', "/")
};
if path.is_empty() || path.split('/').any(|p| p == "..") {
bail!(
"[dotfiles].\"{}\": target is not a safe OCI path",
req.target_raw
);
}
Ok(path)
}
fn source_mode(path: &std::path::Path) -> Result<u32> {
let md = path.metadata()?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
Ok(md.permissions().mode() & 0o7777)
}
#[cfg(not(unix))]
{
let _ = md;
Ok(0o644)View on GitHub (pinned to 533346cc37)