{"record":{"id":"8652d6275d5e36cb","repo":"JuliusBrussee/caveman","slug":"githubapp-request-path-escaped-configured-host","errorCode":null,"errorMessage":"githubapp: request path escaped configured host","messagePattern":"githubapp: request path escaped configured host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"shared/platform/githubapp/githubapp.go","lineNumber":362,"sourceCode":"func (a *App) DoToken(ctx context.Context, token, method, path string, body any) (int, []byte, error) {\n\treturn a.do(ctx, \"Bearer \"+token, method, path, body)\n}\n\nfunc (a *App) do(ctx context.Context, authorization, method, path string, body any) (int, []byte, error) {\n\tif !strings.HasPrefix(path, \"/\") || strings.HasPrefix(path, \"//\") || strings.Contains(path, \"\\\\\") {\n\t\treturn 0, nil, fmt.Errorf(\"githubapp: request path must be a single-host absolute path\")\n\t}\n\tbase, err := url.Parse(a.baseURL)\n\tif err != nil || base.Scheme == \"\" || base.Host == \"\" || base.User != nil {\n\t\treturn 0, nil, fmt.Errorf(\"githubapp: invalid base URL\")\n\t}\n\trelative, err := url.ParseRequestURI(path)\n\tif err != nil || relative.IsAbs() || relative.Host != \"\" || relative.User != nil {\n\t\treturn 0, nil, fmt.Errorf(\"githubapp: invalid request path\")\n\t}\n\ttarget, err := url.Parse(a.baseURL + path)\n\tif err != nil || target.Scheme != base.Scheme || !strings.EqualFold(target.Host, base.Host) || target.User != nil {\n\t\treturn 0, nil, fmt.Errorf(\"githubapp: request path escaped configured host\")\n\t}\n\tvar reader io.Reader\n\tif body != nil {\n\t\tb, err := json.Marshal(body)\n\t\tif err != nil {\n\t\t\treturn 0, nil, fmt.Errorf(\"githubapp: marshal request: %w\", err)\n\t\t}\n\t\treader = bytes.NewReader(b)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, method, target.String(), reader)\n\tif err != nil {\n\t\treturn 0, nil, fmt.Errorf(\"githubapp: build request: %w\", err)\n\t}\n\treq.Header.Set(\"Authorization\", authorization)\n\treq.Header.Set(\"Accept\", \"application/vnd.github+json\")\n\treq.Header.Set(\"X-GitHub-Api-Version\", \"2022-11-28\")\n\tif body != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/githubapp/githubapp.go#L344-L380","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npath := \"/repos/\" + userInput + \"/contents\"\n// after\npath := \"/repos/\" + url.PathEscape(userInput) + \"/contents\"","handlingStrategy":"validation","validationCode":"func safeSegment(s string) (string, error) {\n    esc := url.PathEscape(s)\n    if strings.ContainsAny(esc, \"@\\\\\") || strings.Contains(esc, \"//\") {\n        return \"\", fmt.Errorf(\"unsafe path segment %q\", s)\n    }\n    return esc, nil\n}","typeGuard":null,"tryCatchPattern":"if _, _, err := app.DoToken(ctx, tok, m, path, nil); err != nil {\n    if strings.Contains(err.Error(), \"escaped configured host\") {\n        // treat as malicious/malformed input: reject and alert, never retry\n    }\n}","preventionTips":["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."],"tags":["github","ssrf","security","url-validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}