jdx/mise · error

invalid relay path

Error message

invalid relay path

What it means

After percent-decoding each path segment, `validate_path` rejects segments that are empty, `.` or `..` (path traversal), still contain `/`, `\`, or `%` after decoding, or contain control characters. Any such segment triggers this error, keeping relayed requests pinned to safe repository paths.

Source

Thrown at src/github_relay.rs:189

    // encoded separators and percent signs so a second decoder cannot change scope.
    for segment in path.split('/') {
        for (index, byte) in segment.bytes().enumerate() {
            if byte == b'%'
                && !segment
                    .as_bytes()
                    .get(index + 1..index + 3)
                    .is_some_and(|digits| digits.iter().all(u8::is_ascii_hexdigit))
            {
                bail!("invalid relay path encoding");
            }
        }
        let decoded = urlencoding::decode(segment)?;
        if decoded.is_empty()
            || matches!(decoded.as_ref(), "." | "..")
            || decoded.contains(['/', '\\', '%'])
            || decoded.chars().any(char::is_control)
        {
            bail!("invalid relay path");
        }
    }
    Ok(())
}

#[cfg(any(unix, test))]
fn authorize(scope: &Scope, method: &str, path: &str, query: Option<&str>) -> Result<Target> {
    validate_path(path)?;
    let p: Vec<_> = path.split('/').collect();
    let (owner, repo) = match p.as_slice() {
        ["api", "repos", owner, repo, ..] => (*owner, *repo),
        ["git" | "web", owner, repo, ..] => (*owner, repo.strip_suffix(".git").unwrap_or(repo)),
        _ => bail!("unsupported GitHub operation"),
    };
    let name = repository(&format!("{owner}/{repo}"))?;
    if !scope.permits(&name) {
        bail!("repository is outside the approved relay scope");
    }

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Sanitize the path: remove empty, `.`, and `..` segments and reject refs containing `/`, `\`, `%`, or control characters.
  2. Encode special characters correctly (a branch like `feature/x` cannot be relayed via a single segment; use the ref query parameter instead).
  3. Trim trailing slashes from the path before calling.

Example fix

// before
let path = "/api/repos/o/r/tarball/../../etc";
// after
let path = "/api/repos/o/r/tarball/v1.2.0";
Defensive patterns

Strategy: validation

Validate before calling

fn safe_path(p: &str) -> bool {
    p.split('/').skip(1).all(|seg| !seg.is_empty() && !matches!(seg, "." | "..")
        && !seg.contains(['/', '\\', '%'])
        && !seg.chars().any(char::is_control))
}
assert!(safe_path("/api/repos/o/r/tarball/v1.0"));

Try / catch

match relay::authorize(&scope, "GET", path, None) {
    Err(e) if e.to_string().contains("invalid relay path") => eprintln!("sanitize path segments: {e}"),
    Err(e) => return Err(e),
    Ok(t) => t,
}

Prevention

When it happens

Trigger: Calling `authorize` or `archive_redirect` with a path containing an empty segment (`//`), a `.`/`..` segment, an encoded traversal (`..%2F`), an encoded slash/backslash inside a segment, or control chars (e.g. `%00`, `%0a`) in a ref or file path.

Common situations: Refs built from untrusted input containing `../`; tags or branch names with embedded slashes that were encoded; NUL or newline characters introduced by shell interpolation; trailing slashes producing empty segments.

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@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/7c9ace90e56ea4ce. Report an issue: GitHub.