jdx/mise · error

an implied source entry key must not contain '..'

Error message

an implied source entry key must not contain '..'

What it means

`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.

Solutions

  1. Remove `..` from the entry key and point it directly at the file inside the dotfiles root
  2. Set an explicit `source` field with the actual path instead of relying on the key
  3. Reorganize files so the source lives under the dotfiles directory

Example fix

// before
"../shared/gitconfig" = { variants = [ { target = "~/.gitconfig" } ] }
// after
"~/.gitconfig" = { source = "shared/gitconfig", variants = [ { target = "~/.gitconfig" } ] }
Defensive patterns

Strategy: validation

Validate before calling

function keyHasParentDir(key) { return key.split('/').includes('..'); }
// reject: if (keyHasParentDir(entryKey)) throw new Error('entry key must not contain ..');

Type guard

const isSafeRelativeKey = (key: string) => !key.split(/[\\/]/).includes('..') && !path.isAbsolute(key);

Prevention

When it happens

Trigger: An entry key like `"dotfiles/../secrets/rc"` used with destination `variants` and no explicit `source`, so the key is interpreted as the source path.

Common situations: 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.

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


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/dad815a812985ba5. Report an issue: GitHub.

Appendix: source

Thrown at src/system/files.rs:257

        }
    }
    Ok(implied_source)
}

fn logical_source_path(key: &str) -> Result<PathBuf> {
    let path = file::replace_path(key);
    if !path.is_relative() {
        bail!(
            "destination variants require an explicit source unless the entry key is a relative source path"
        );
    }
    let mut relative = PathBuf::new();
    for component in path.components() {
        match component {
            std::path::Component::Normal(component) => relative.push(component),
            std::path::Component::CurDir => {}
            std::path::Component::ParentDir => {
                bail!("an implied source entry key must not contain '..'");
            }
            std::path::Component::RootDir | std::path::Component::Prefix(_) => {
                bail!("an implied source entry key must be relative");
            }
        }
    }
    if relative.as_os_str().is_empty() {
        bail!("an implied source entry key must not be empty");
    }
    Ok(relative)
}

/// Inactive variants can contain another platform's absolute path syntax.
/// The selected destination is still checked with native path rules before use.
fn variant_target_is_absolute(target: &str) -> bool {
    let bytes = target.as_bytes();
    resolve_target_arg(target).is_absolute()
        || target.starts_with('/')

View on GitHub (pinned to 533346cc37)