JuliusBrussee/caveman · error
githubapp: request path must be a single-host absolute path
Error message
githubapp: request path must be a single-host absolute path
What it means
The internal do() method SSRF-guards every request path before combining it with the configured base URL. A valid path must start with a single '/', must not start with '//', and must not contain backslashes. This error is thrown for any path argument passed to DoToken (or higher-level wrappers) that violates those rules.
Solutions
- Ensure the path argument always starts with exactly one '/', e.g. "/repos/" + owner + "/" + name.
- Never embed a host in the path — configure a.baseURL for the host and pass only the path portion.
- Sanitize interpolated segments with url.PathEscape and strip backslashes before building the path.
- Add a pre-call assertion in tests that rejects paths not matching ^/[^/].
Example fix
// before client.DoToken(ctx, tok, "GET", "repos/" + owner + "/" + name, nil) // after path := "/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(name) client.DoToken(ctx, tok, "GET", path, nil)
Defensive patterns
Strategy: validation
Validate before calling
func validAPIPath(p string) bool {
return strings.HasPrefix(p, "/") && !strings.HasPrefix(p, "//") && !strings.Contains(p, "\\")
}
if !validAPIPath(path) { return fmt.Errorf("bad path %q", path) } Try / catch
if _, _, err := app.DoToken(ctx, tok, m, path, body); err != nil {
if strings.Contains(err.Error(), "single-host absolute path") {
// fix path construction; do not retry
}
} Prevention
- Always build paths with a leading '/' via a helper, never raw concatenation.
- Escape segments with url.PathEscape.
- Never put hosts into the path argument.
When it happens
Trigger: Calling DoToken(ctx, token, method, path, body) with path = "repos/octocat/hello" (missing leading '/'), "//evil.com/api" (protocol-relative), or any path containing '\\' such as "repos\\octocat".
Common situations: Building endpoint strings with string concatenation and forgetting the leading slash; copying Windows-style paths; accidentally interpolating a full URL into the path argument instead of passing it via baseURL.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- githubapp: invalid request path
- githubapp: request path escaped configured host
- awscreds: AWS_EC2_METADATA_SERVICE_ENDPOINT is not a valid…
- githubapp: invalid base URL
- ssrf: credentials embedded in URL are forbidden
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/71101d88666da324.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/githubapp/githubapp.go:350
return nil, fmt.Errorf("githubapp: decode repository proof content: %w", err)
}
if len(decoded) > 64<<10 || int64(len(decoded)) != payload.Size {
return nil, fmt.Errorf("githubapp: repository proof size mismatch")
}
return decoded, nil
}
// DoToken issues an authenticated GitHub REST call with an installation token and
// returns the status + raw body for the caller to parse. It is the reusable
// primitive the worker's PR opener builds the Git Data API flow on, so every
// GitHub egress goes through the one SSRF-guarded client + fixed base host.
func (a *App) DoToken(ctx context.Context, token, method, path string, body any) (int, []byte, error) {
return a.do(ctx, "Bearer "+token, method, path, body)
}
func (a *App) do(ctx context.Context, authorization, method, path string, body any) (int, []byte, error) {
if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") || strings.Contains(path, "\\") {
return 0, nil, fmt.Errorf("githubapp: request path must be a single-host absolute path")
}
base, err := url.Parse(a.baseURL)
if err != nil || base.Scheme == "" || base.Host == "" || base.User != nil {
return 0, nil, fmt.Errorf("githubapp: invalid base URL")
}
relative, err := url.ParseRequestURI(path)
if err != nil || relative.IsAbs() || relative.Host != "" || relative.User != nil {
return 0, nil, fmt.Errorf("githubapp: invalid request path")
}
target, err := url.Parse(a.baseURL + path)
if err != nil || target.Scheme != base.Scheme || !strings.EqualFold(target.Host, base.Host) || target.User != nil {
return 0, nil, fmt.Errorf("githubapp: request path escaped configured host")
}
var reader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return 0, nil, fmt.Errorf("githubapp: marshal request: %w", err)View on GitHub (pinned to 3ee70a1026)