jdx/mise · error

GitHub relay permits read-only repository operations only

Error message

GitHub relay permits read-only repository operations only

What it means

The GitHub relay is strictly read-only: `authorize` matches the method + path against a small allow-list (git info/refs upload-pack GET, API repo GETs, release/archive downloads with GET|HEAD, etc.) and fails with this error for anything else. Write operations (POST/PUT/PATCH/DELETE) and non-allow-listed read endpoints are rejected before any network call.

Source

Thrown at src/github_relay.rs:229

            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"),
        ["web", _, _, "archive", _, ..] => matches!(method, "GET" | "HEAD"),
        _ => false,
    };
    if !allowed {
        bail!("GitHub relay permits read-only repository operations only");
    }
    if !git && let Some(query) = query {
        for (key, _) in url::form_urlencoded::parse(query.as_bytes()) {
            if !matches!(key.as_ref(), "ref" | "page" | "per_page") {
                bail!("unsupported query parameter");
            }
        }
    }
    let host = if p[0] == "api" {
        "api.github.com"
    } else {
        "github.com"
    };
    let suffix = path.split_once('/').expect("validated path").1;
    let mut url = format!("https://{host}/{suffix}");
    let archive_repo = match p.as_slice() {
        ["api", "repos", _, _, "tarball" | "zipball", ..] => Some(name.clone()),
        ["web", _, _, "archive", rest @ ..] => {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Use only read-only operations: GET (and HEAD for downloads) against allowed paths — clone/fetch via git upload-pack, tarball/zipball downloads, repo metadata reads.
  2. Remove write calls (releases, issues, push) from code that runs through the relay; perform them outside the relay with proper credentials if truly needed.
  3. Check the method constant passed to `forward`/`operation` — a wrong default (e.g. POST for a metadata fetch) will be rejected.

Example fix

// before
relay::forward(&scope, "POST", "/api/repos/o/r/releases", Some(body))?;
// after
relay::forward(&scope, "GET", "/api/repos/o/r/releases/latest", None)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_read_only(method: &str) -> bool {
    matches!(method, "GET" | "HEAD")
}
// guard before calling forward/operation
assert!(is_read_only("GET"));

Try / catch

match relay::forward(&scope, method, path, None) {
    Err(e) if e.to_string().contains("read-only") => eprintln!("{method} {path} is not permitted by the relay"),
    Err(e) => return Err(e),
    Ok(resp) => resp,
}

Prevention

When it happens

Trigger: Calling `forward`/`operation` with a mutating method (e.g. POST to create a release, DELETE a file), or a method/path combo not in the allow-list such as `POST /api/repos/o/r/issues` or `GET /git/o/r/git-receive-pack`.

Common situations: Pointing a generic GitHub API client (which may POST or PATCH) through the relay; attempting `git push` (receive-pack) instead of clone/fetch; tooling that creates comments, releases, or checks while installing dependencies.

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/332a4b41aa6b3a55. Report an issue: GitHub.