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
- Sanitize the path: remove empty, `.`, and `..` segments and reject refs containing `/`, `\`, `%`, or control characters.
- Encode special characters correctly (a branch like `feature/x` cannot be relayed via a single segment; use the ref query parameter instead).
- 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
- Treat all path segments derived from user input as untrusted; reject `.`/`..` and control chars early.
- Trim trailing slashes and collapse duplicate slashes before building relay paths.
- Use branch refs via the allowed `ref` query param instead of embedding slashes in path segments.
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
- brew-cask: invalid {kind} '{value}'
- {option}: '{name}' must be a plain file name (no path separa
- {option}: '{path}' must be a safe relative path (no absolute
- invalid relay path encoding
- unsupported relay destination
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/7c9ace90e56ea4ce.
Report an issue: GitHub.