JuliusBrussee/caveman · critical
githubapp: request path escaped configured host
Error message
githubapp: request path escaped configured host
What it means
This is the final SSRF guard: do() joins baseURL with the path, re-parses the combined target URL, and verifies the scheme matches the configured scheme and the host matches case-insensitively with no userinfo. This error means the path, when appended, redirected the request to a different host or scheme than the configured base — a classic open-redirect/SSRF vector.
Solutions
- Escape every dynamic segment with url.PathEscape before interpolating into the path.
- Remove or escape '@', '\\', and '/' characters originating from user input in path segments.
- Audit the code that builds the path so no concatenated fragment can introduce an authority component.
- Log the offending path when this fires — it usually indicates malicious or malformed input worth rejecting upstream.
Example fix
// before path := "/repos/" + userInput + "/contents" // after path := "/repos/" + url.PathEscape(userInput) + "/contents"
Defensive patterns
Strategy: validation
Validate before calling
func safeSegment(s string) (string, error) {
esc := url.PathEscape(s)
if strings.ContainsAny(esc, "@\\") || strings.Contains(esc, "//") {
return "", fmt.Errorf("unsafe path segment %q", s)
}
return esc, nil
} Try / catch
if _, _, err := app.DoToken(ctx, tok, m, path, nil); err != nil {
if strings.Contains(err.Error(), "escaped configured host") {
// treat as malicious/malformed input: reject and alert, never retry
}
} Prevention
- PathEscape every segment derived from user or repo input.
- Reject '@', backslashes, and dot-segments in dynamic path parts.
- Log-and-alert on this error; it usually signals hostile input.
When it happens
Trigger: A path containing encoded trickery that changes the effective host after concatenation, e.g. baseURL "https://api.github.com" with path "/@evil.com/" or "/..\\@evil.com/" or "///evil.com/x" variants that slip past earlier checks; also a baseURL with a trailing component that the path hijacks.
Common situations: Interpolating untrusted user input (repo names, branch refs) into the path without escaping, letting '@' or '.' sequences form a new authority; using a base URL whose trailing slash interacts badly with paths built with unescaped 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
- githubapp: invalid request path
- githubapp: request path must be a single-host absolute path
- ssrf: credentials embedded in URL are forbidden
- ssrf: host is blocked (loopback)
- ssrf: scheme not permitted (managed mode requires https)
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/8652d6275d5e36cb.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/githubapp/githubapp.go:362
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)
}
reader = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, target.String(), reader)
if err != nil {
return 0, nil, fmt.Errorf("githubapp: build request: %w", err)
}
req.Header.Set("Authorization", authorization)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
if body != nil {
req.Header.Set("Content-Type", "application/json")View on GitHub (pinned to 3ee70a1026)