{"record":{"id":"6ee3f6d0e22d544b","repo":"elsa-workflows/elsa-core","slug":"destinationrejected","errorCode":"DestinationRejected","errorMessage":"ProviderHttpException(ProviderHttpFailure.DestinationRejected)","messagePattern":"ProviderHttpException\\(ProviderHttpFailure\\.DestinationRejected\\)","errorType":"error_code","errorClass":"ProviderHttpException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/ProviderHttpClientFactory.cs","lineNumber":114,"sourceCode":"\n                if (IsRedirect(response.StatusCode))\n                {\n                    if (kind is ProviderResponseKind.Token or ProviderResponseKind.UserInfo || response.Headers.Location is null || redirects++ >= options.Value.ProviderEgress.MaximumRedirects)\n                        throw new ProviderHttpException(ProviderHttpFailure.RedirectRejected);\n\n                    current = new(current, response.Headers.Location);\n                    continue;\n                }\n\n                if (!response.IsSuccessStatusCode)\n                    return new(response.StatusCode, []);\n\n                return new(response.StatusCode, await ReadResponseBodyAsync(response, kind, timeout.Token));\n            }\n        }\n        catch (OutboundDestinationException)\n        {\n            throw new ProviderHttpException(ProviderHttpFailure.DestinationRejected);\n        }\n        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)\n        {\n            throw new ProviderHttpException(ProviderHttpFailure.Timeout);\n        }\n        catch (ProviderHttpException)\n        {\n            throw;\n        }\n        catch (Exception) when (!cancellationToken.IsCancellationRequested)\n        {\n            throw new ProviderHttpException(ProviderHttpFailure.TransportFailure);\n        }\n    }\n\n    private async Task<byte[]> ReadResponseBodyAsync(HttpResponseMessage response, ProviderResponseKind kind, CancellationToken cancellationToken)\n    {\n        var limit = GetResponseLimit(kind);","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/ProviderHttpClientFactory.cs#L96-L132","documentation":"SendAsync validates every request destination (including each redirect hop) through OutboundDestinationValidator. If the validator rejects the host, scheme, port, or proxy destination it throws OutboundDestinationException, which SendAsync translates into ProviderHttpException(ProviderHttpFailure.DestinationRejected). This is the library's SSRF/egress-control boundary: the configured provider URL points somewhere the deployment has not approved.","triggerScenarios":"Calling GetAsync/PostFormAsync with a URI whose scheme/host/port is not allowed by OutboundDestinationValidator policy (e.g. non-HTTPS, private/loopback IP, host not on the allowlist), or a redirect hop landing on a disallowed destination; also thrown when a configured proxy URI fails ValidateApprovedProxy checks during a request.","commonSituations":"Typing an internal/dev provider URL (http://localhost:8080, http://10.x.x.x) into production config where private egress is blocked; OIDC discovery document advertising endpoints on hosts outside the approved egress list; redirect from the provider to a different domain that is not allowlisted; DNS rebinding or a host resolving to a private IP caught by the validator.","solutions":["Update the configured provider/authority URL to an approved destination (public HTTPS host on the allowlist) and redeploy.","Add the provider's host to the outbound-destination allowlist in your ExternalAuthentication egress configuration if the destination is legitimate.","Inspect where the redirect chain lands (curl -I each hop) and either allowlist the final host or fix the provider's redirect target.","Check the DNS resolution: if a public hostname resolves to a private IP, fix DNS or update validator policy deliberately — do not disable validation."],"exampleFix":"// before — internal endpoint rejected by destination validation\nawait client.GetAsync(new Uri(\"http://localhost:8080/.well-known/openid-configuration\"), ProviderResponseKind.Discovery);\n\n// after — approved HTTPS provider host\nawait client.GetAsync(new Uri(\"https://idp.example.com/.well-known/openid-configuration\"), ProviderResponseKind.Discovery);","handlingStrategy":"validation","validationCode":"// validate provider URLs against your egress policy before configuring\nvar uri = new Uri(candidateUrl);\nbool isAllowed = uri.Scheme == Uri.UriSchemeHttps\n    && allowedHosts.Contains(uri.Host)\n    && !IPAddress.TryParse(uri.Host, out var ip) || !IsPrivateIp(ip!);\nif (!isAllowed) throw new InvalidOperationException($\"Provider URL not on approved egress list: {candidateUrl}\");","typeGuard":"static bool IsApprovedDestination(Uri uri, IReadOnlySet<string> allowedHosts) =>\n    uri.Scheme == Uri.UriSchemeHttps && allowedHosts.Contains(uri.Host);","tryCatchPattern":"try\n{\n    var response = await client.GetAsync(discoveryUri, ProviderResponseKind.Discovery, ct);\n}\ncatch (ProviderHttpException ex) when (ex.Failure == ProviderHttpFailure.DestinationRejected)\n{\n    logger.LogError(\"Destination rejected by outbound validation: {Uri}\", discoveryUri);\n    throw; // do not retry — egress policy rejection is deterministic, not transient\n}","preventionTips":["Never point External Authentication at http:// or private/loopback addresses in production.","Keep the egress allowlist in configuration-as-code and update it through review whenever a provider host changes.","After an OIDC provider change, verify all advertised endpoints (issuer, token, userinfo, JWKS) stay within the allowlist.","Treat DestinationRejected as config error, not transient: do not build automatic retries around it."],"tags":["http","security","ssrf","egress-validation"],"backgroundTag":"http-request-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}