jdx/mise · error
repository is outside the approved relay scope
Error message
repository is outside the approved relay scope
What it means
After extracting OWNER/REPO from the path, `authorize` checks it against the relay's approved scope via `scope.permits(&name)` (lowercased). If the requested repository is not in the allowlist configured for this relay session, the request is refused, even if the path shape and operation would otherwise be allowed. The relay is deliberately scoped so credentials can only reach specific repositories.
Solutions
- Add the requested repository to the relay's approved scope (restart the relay with the additional OWNER/REPO in the scope flag)
- Correct the client to use the scoped repository's exact owner/repo spelling
- If a tool is following redirects to other repos, pin it to the original repository or disable the redirecting behavior
Example fix
// before mise github-relay --scope "octocat/hello-world" git clone https://relay/git/octocat/hello-world-2.git // after mise github-relay --scope "octocat/hello-world" git clone https://relay/git/octocat/hello-world.git
Defensive patterns
Strategy: validation
Validate before calling
fn scope_permits(scope_repos: &[String], url_repo: &str) -> bool {
scope_repos.iter().any(|r| r.to_ascii_lowercase() == url_repo.to_ascii_lowercase())
}
// call before issuing the request
assert!(scope_permits(&["octocat/hello-world".into()], "octocat/hello-world")); Try / catch
match authorize(&scope, method, path, query) {
Err(e) if e.to_string().contains("outside the approved relay scope") => {
eprintln!("repo not in scope; restart relay with it added to --scope");
}
Ok(t) => forward(t),
Err(e) => return Err(e),
} Prevention
- List every repository (including forks/submodules) the client may touch in the relay scope
- Normalize owner/repo to lowercase before comparing
- Audit tools for redirect-following that could hop to other repositories
- Double-check scope flag spelling against the URLs clients will use
When it happens
Trigger: A client requests `api/repos/other/repo`, `git/other/repo`, or `web/other/repo/...` where `other/repo` was not included in the scope passed at relay startup (e.g. scope was `octocat/hello-world` but the clone URL points at a fork under another owner).
Common situations: Cloning a fork whose owner differs from the scoped repo; a tool following a redirect or submodule URL to a different repository; a typo in either the scope flag or the client URL; case differences are fine (name is lowercased) but owner/repo spelling must match the scope.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- GitHub relay permits read-only repository operations only
- unsupported archive format
- unsupported GitHub operation
- unsupported query parameter
- expected a GitHub repository in OWNER/REPO form
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/e3c6ccfdda809dc5.
Report an issue: GitHub.
Appendix: source
Thrown at src/github_relay.rs:206
{
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");
}
let git = p[0] == "git";
let allowed = match p.as_slice() {
["git", _, _, "info", "refs"] => {
method == "GET" && query == Some("service=git-upload-pack")
}
["git", _, _, "git-upload-pack"] => method == "POST" && query.is_none(),
["api", "repos", _, _] => method == "GET" || method == "HEAD",
["api", "repos", _, _, "git", kind, ..] => {
matches!(*kind, "refs" | "matching-refs") && matches!(method, "GET" | "HEAD")
}
["api", "repos", _, _, kind, ..] => {
matches!(
*kind,
"contents" | "releases" | "tags" | "branches" | "tarball" | "zipball"
) && matches!(method, "GET" | "HEAD")
}
["web", _, _, "releases", "download", _, ..] => matches!(method, "GET" | "HEAD"),View on GitHub (pinned to 533346cc37)