{"record":{"id":"4a3eb55eb678ec7f","repo":"jdx/mise","slug":"unsupported-github-operation","errorCode":null,"errorMessage":"unsupported GitHub operation","messagePattern":"unsupported GitHub operation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/github_relay.rs","lineNumber":202,"sourceCode":"        if decoded.is_empty()\n            || matches!(decoded.as_ref(), \".\" | \"..\")\n            || decoded.contains(['/', '\\\\', '%'])\n            || decoded.chars().any(char::is_control)\n        {\n            bail!(\"invalid relay path\");\n        }\n    }\n    Ok(())\n}\n\n#[cfg(any(unix, test))]\nfn authorize(scope: &Scope, method: &str, path: &str, query: Option<&str>) -> Result<Target> {\n    validate_path(path)?;\n    let p: Vec<_> = path.split('/').collect();\n    let (owner, repo) = match p.as_slice() {\n        [\"api\", \"repos\", owner, repo, ..] => (*owner, *repo),\n        [\"git\" | \"web\", owner, repo, ..] => (*owner, repo.strip_suffix(\".git\").unwrap_or(repo)),\n        _ => bail!(\"unsupported GitHub operation\"),\n    };\n    let name = repository(&format!(\"{owner}/{repo}\"))?;\n    if !scope.permits(&name) {\n        bail!(\"repository is outside the approved relay scope\");\n    }\n    let git = p[0] == \"git\";\n    let allowed = match p.as_slice() {\n        [\"git\", _, _, \"info\", \"refs\"] => {\n            method == \"GET\" && query == Some(\"service=git-upload-pack\")\n        }\n        [\"git\", _, _, \"git-upload-pack\"] => method == \"POST\" && query.is_none(),\n        [\"api\", \"repos\", _, _] => method == \"GET\" || method == \"HEAD\",\n        [\"api\", \"repos\", _, _, \"git\", kind, ..] => {\n            matches!(*kind, \"refs\" | \"matching-refs\") && matches!(method, \"GET\" | \"HEAD\")\n        }\n        [\"api\", \"repos\", _, _, kind, ..] => {\n            matches!(\n                *kind,","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/github_relay.rs#L184-L220","documentation":"`authorize` matches the relayed request path against a fixed set of patterns: `api/repos/{owner}/{repo}/...`, or `git/{owner}/{repo}/...` / `web/{owner}/{repo}/...`. Any other path prefix is rejected because the relay is only designed to proxy GitHub repository API, git smart-HTTP, and release/archive download traffic. This is the first structural check after path validation.","triggerScenarios":"A request whose path does not start with `api/repos/`, `git/`, or `web/` — e.g. `/api/user`, `/api/orgs/x`, `/gists/1`, `/git/` with fewer than 3 segments, or a typo like `/API/repos/o/r`.","commonSituations":"A tool is configured to proxy all GitHub API traffic through the relay but calls a non-repository endpoint (user, org, search, gists); a client uses the wrong base path (e.g. omitting `repos` for the repo API); a git client uses a non-standard path layout.","solutions":["Point the client only at repository endpoints: `api/repos/OWNER/REPO/...`, `git/OWNER/REPO(.git)/...`, or `web/OWNER/REPO/...`","If you need a non-repository GitHub API endpoint, use direct authenticated access instead of the scoped relay","Fix the client's base URL so it includes the `repos` segment for repo API calls"],"exampleFix":"// before\nGET https://relay/api/user\n// after\nGET https://relay/api/repos/octocat/hello-world","handlingStrategy":"validation","validationCode":"const ALLOWED_PREFIXES: [&str; 3] = [\"api/repos/\", \"git/\", \"web/\"];\nfn relayable(path: &str) -> bool {\n    ALLOWED_PREFIXES.iter().any(|p| path.starts_with(p))\n        && path.split('/').filter(|s| !s.is_empty()).count() >= 3\n}","typeGuard":null,"tryCatchPattern":"match authorize(&scope, method, path, query) {\n    Err(e) if e.to_string().contains(\"unsupported GitHub operation\") => {\n        eprintln!(\"{path} is not a relay-supported endpoint; use direct GitHub access\");\n    }\n    Ok(t) => forward(t),\n    Err(e) => return Err(e),\n}","preventionTips":["Only route repository-scoped endpoints through the relay","Use the correct API base path (include `repos/` for repo endpoints)","Do not point generic GitHub API clients at the relay base URL","Keep path casing exactly as the allowlist expects (lowercase prefixes)"],"tags":["github","relay","allowlist","unsupported-operation"],"backgroundTag":"unsupported-operation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}