{"record":{"id":"10e0589a6dbd693d","repo":"JuliusBrussee/caveman","slug":"githubapp-base-url-rejected-by-ssrf-guard-w","errorCode":null,"errorMessage":"githubapp: base_url rejected by SSRF guard: %w","messagePattern":"githubapp: base_url rejected by SSRF guard: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/githubapp/githubapp.go","lineNumber":89,"sourceCode":"\tif len(cfg.PrivateKeyPEM) == 0 {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key PEM is required\")\n\t}\n\tkey, err := parseRSAPrivateKey(cfg.PrivateKeyPEM)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbase := strings.TrimRight(strings.TrimSpace(cfg.BaseURL), \"/\")\n\tif base == \"\" {\n\t\tbase = defaultBaseURL\n\t}\n\tclient := cfg.HTTPClient\n\tif client == nil {\n\t\t// Production path: SSRF-guarded client + pre-flight host check on a custom\n\t\t// (GHE) base. When a caller injects a client (tests), it owns the host policy,\n\t\t// so we skip the pre-flight — but production never injects one.\n\t\tif base != defaultBaseURL {\n\t\t\tif err := ssrf.ValidateURL(context.Background(), base, ssrf.ManagedConfig()); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"githubapp: base_url rejected by SSRF guard: %w\", err)\n\t\t\t}\n\t\t}\n\t\tclient = ssrf.NewHTTPClient(ssrf.ManagedConfig())\n\t\tclient.Timeout = 20 * time.Second\n\t}\n\treturn &App{\n\t\tappID:         strings.TrimSpace(cfg.AppID),\n\t\tslug:          strings.TrimSpace(cfg.AppSlug),\n\t\tprivateKey:    key,\n\t\twebhookSecret: cfg.WebhookSecret,\n\t\tbaseURL:       base,\n\t\thttpClient:    client,\n\t}, nil\n}\n\n// Slug returns the App slug used to build the install URL.\nfunc (a *App) Slug() string { return a.slug }\n","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/githubapp/githubapp.go#L71-L107","documentation":"githubapp.New performs a pre-flight SSRF check (ssrf.ValidateURL) on a custom base URL (GitHub Enterprise) before constructing its HTTP client, and this error wraps the guard's rejection. It protects the production path from pointing the App at internal/private network addresses. Callers that inject their own client (tests) bypass the check because they own host policy.","triggerScenarios":"Calling New with a non-nil default client and cfg.BaseURL set to something other than the default api.github.com base, where ssrf.ValidateURL rejects the URL — private/loopback/link-local IPs, non-HTTPS schemes, disallowed hosts.","commonSituations":"Pointing the base URL at a local GHE dev box or mock server via production code path; a config value like http://localhost:8080/api/v3; DNS that resolves a hostname to a private address; typo'd internal URLs.","solutions":["Use an HTTPS, publicly resolvable URL for the GHE instance (the SSRF guard blocks private/loopback ranges by design)","For local testing, inject your own *http.Client into Config (the pre-flight is skipped when a client is provided)","If GHE sits behind a proxy/allowlist, configure the ssrf ManagedConfig allowlist appropriately instead of weakening the guard","Verify the base URL has no scheme typos (e.g. http instead of https) and no internal-only hostnames"],"exampleFix":"// before\nbase := \"http://ghe.internal.corp/api/v3\"\napp, err := githubapp.New(githubapp.Config{AppID: id, PrivateKeyPEM: pem, BaseURL: base})\n// after\nbase := \"https://ghe.mycompany.example/api/v3\"\napp, err := githubapp.New(githubapp.Config{AppID: id, PrivateKeyPEM: pem, BaseURL: base})","handlingStrategy":"try-catch","validationCode":"u, err := url.Parse(base)\nif err != nil || u.Scheme != \"https\" {\n    return fmt.Errorf(\"base URL must be https: %q\", base)\n}","typeGuard":null,"tryCatchPattern":"app, err := githubapp.New(cfg)\nif err != nil && strings.Contains(err.Error(), \"SSRF guard\") {\n    // base URL points at a disallowed (private/non-https) host;\n    // fix the URL or, for tests, inject a custom client\n    return ErrBaseURLRejected\n}","preventionTips":["Always use public HTTPS URLs for GHE in production","Use client injection for local/mock testing instead of private base URLs","Keep the SSRF allowlist updated when adding new GHE endpoints"],"tags":["go","github-app","ssrf","security"],"backgroundTag":"invalid-url","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"}