Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ public class WebClientUtils {

private static final Set<String> DISALLOWED_HOSTS = computeDisallowedHosts();

// Opt-in strict egress mode (GHSA-4fjr-w826-cpwm). When enabled, outbound datasource/HTTP
// requests are additionally blocked from reaching loopback and RFC 1918 private ranges on ALL
// deployment types. Default is off so existing self-hosted deployments that legitimately connect
// datasources to internal/private hosts are not broken on upgrade.
private static final boolean BLOCK_PRIVATE_ADDRESSES =
"true".equalsIgnoreCase(System.getenv("APPSMITH_SSRF_BLOCK_PRIVATE_ADDRESS"));

public static final String HOST_NOT_ALLOWED = "Host not allowed.";

private static final int MAX_IN_MEMORY_SIZE_IN_BYTES = 16 * 1024 * 1024;
Expand Down Expand Up @@ -239,9 +246,50 @@ public static Optional<InetAddress> resolveIfAllowed(String host) {
return Optional.of(resolved[0]);
}

/**
* Datasource-facing SSRF resolver for plugins that connect outside the WebClient pipeline (e.g.
* the SMTP plugin via JavaMail — GHSA-72m2-f9xp-wg9h). Mirrors the WebClient HTTP egress policy:
* the cloud-metadata denylist and the always-blocked address classes (link-local/IMDS,
* any-local, multicast, IPv6 ULA) are rejected on every deployment, while loopback is rejected
* only inside Docker or under strict egress, and RFC 1918 site-local only under strict egress.
*
* <p>Unlike {@link #resolveIfAllowed}, loopback and RFC 1918 are allowed by default so that
* self-hosted mail servers on private networks continue to work. Returns the resolved
* {@link InetAddress} so callers can pin the connection to it and defeat DNS-rebinding.
*
* @return the resolved {@link InetAddress} if the host is allowed, or empty if blocked
*/
public static Optional<InetAddress> resolveForDatasource(String host) {
if (!StringUtils.hasText(host)) {
return Optional.empty();
}

final String canonicalHost = normalizeHostForComparisonQuietly(host);
if (DISALLOWED_HOSTS.contains(canonicalHost)) {
return Optional.empty();
}

final InetAddress[] resolved;
try {
resolved = InetAddress.getAllByName(host);
} catch (UnknownHostException e) {
return Optional.empty();
}

final boolean inDocker = "1".equals(System.getenv("IN_DOCKER"));
for (InetAddress addr : resolved) {
if (DISALLOWED_HOSTS.contains(normalizeHostForComparisonQuietly(addr.getHostAddress()))
|| isBlockedResolvedAddress(addr, inDocker, BLOCK_PRIVATE_ADDRESSES)) {
return Optional.empty();
}
}

return Optional.of(resolved[0]);
}
Comment on lines +262 to +288

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# How does SmtpPlugin build the JavaMail session after resolveForDatasource? Does it pin the IP?
fd 'SmtpPlugin.java' --exec sed -n '190,320p' {}

Repository: appsmithorg/appsmith

Length of output: 6707


🌐 Web query:

JavaMail Transport.connect host DNS resolution re-resolves on connect mail.smtp.host ip pinning

💡 Result:

In Jakarta Mail (formerly JavaMail), the Transport.connect method does not explicitly pin IP addresses, nor does it inherently cache DNS lookups in a way that prevents re-resolution [1][2]. The resolution of the mail.smtp.host property is handled by the underlying Java networking stack (specifically java.net.InetAddress) each time a new connection is established [1][3]. Key technical points regarding DNS resolution and IP pinning in this context include: 1. DNS Resolution Behavior: When you call transport.connect, Jakarta Mail triggers a socket connection attempt to the host specified in the mail.smtp.host property (or passed as an argument) [4][5]. This relies on the standard Java networking library to resolve the hostname to an IP address [1][2]. If the hostname resolves to multiple IP addresses, the behavior depends on the JVM's underlying implementation of InetAddress.getByName or getAllByName, which typically follows the OS-level DNS resolution rules and potential local JVM-level caching [1]. 2. Lack of Native IP Pinning: Jakarta Mail does not implement its own internal DNS caching layer or IP pinning mechanism [1]. Because the library relies on the Java runtime's networking facilities, it does not "pin" to a specific IP address across multiple sessions unless explicitly configured to do so by the network environment or JVM system properties [1][3]. 3. DNS Timeouts and Performance: DNS resolution is performed synchronously. If the DNS server is unresponsive or slow, the connect call can appear to hang, as JavaMail timeouts generally cover only the socket connection, read, and write operations, not the initial DNS lookup [3]. 4. Workarounds for Resilience: Because Jakarta Mail does not natively handle DNS-based load balancing (such as iterating through multiple IP addresses from an MX record or a round-robin DNS entry upon connection failure), developers often implement custom retry logic [1][2]. This involves resolving the hostname manually via InetAddress.getAllByName, shuffling the resulting array of IP addresses, and attempting to connect to each address sequentially if the previous attempt fails [1]. In summary, JavaMail performs a fresh DNS lookup through the standard Java networking stack at the time of connection [1][3]. If you require behavior like "IP pinning" (sticking to one IP) or conversely "DNS failover" (trying all IPs), these must be managed at the application layer or via JVM configuration [1][2].

Citations:


SMTP still needs IP pinning or post-connect validation
SmtpPlugin.datasourceCreate validates endpoint.getHost() up front, but JavaMail still connects using the hostname, so the SMTP transport can re-resolve it at connect time and bypass the DNS-rebinding guard. Pin the session to the resolved InetAddress, or validate the remote peer after connect().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java`
around lines 262 - 288, SMTP hostname validation is still bypassable because
`SmtpPlugin.datasourceCreate` only checks `endpoint.getHost()` before JavaMail
connects. Update the SMTP connection flow to either pin the session/transport to
the resolved `InetAddress` returned by `WebClientUtils.resolveForDatasource` or
add post-connect verification of the remote peer after `connect()`, so the
actual connected address is checked against the DNS-rebinding guard.

Source: Learnings


public static boolean isDisallowedAndFail(String host, Promise<?> promise) {
final String canonicalHost = normalizeHostForComparisonQuietly(host);
if (DISALLOWED_HOSTS.contains(canonicalHost) || isBlockedAddressClassInDocker(canonicalHost)) {
if (DISALLOWED_HOSTS.contains(canonicalHost) || isBlockedForEgress(canonicalHost)) {
log.warn("Host {} is disallowed. Failing the request.", host);
if (promise != null) {
promise.setFailure(new UnknownHostException(HOST_NOT_ALLOWED));
Expand Down Expand Up @@ -269,7 +317,7 @@ private static Mono<ClientRequest> requestFilterFn(ClientRequest request) {
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, "IP Address resolution is invalid"));
}

return (DISALLOWED_HOSTS.contains(canonicalHost) || isBlockedAddressClassInDocker(canonicalHost))
return (DISALLOWED_HOSTS.contains(canonicalHost) || isBlockedForEgress(canonicalHost))
? Mono.error(new UnknownHostException(HOST_NOT_ALLOWED))
: Mono.just(request);
}
Expand All @@ -285,11 +333,30 @@ static boolean isBlockedIpAddressClass(String canonicalHost) {
}
}

private static boolean matchesBlockedAddressClass(InetAddress address) {
if (address.isLoopbackAddress()
|| address.isAnyLocalAddress()
|| address.isLinkLocalAddress()
|| address.isMulticastAddress()) {
/**
* Address classes that have no legitimate outbound-datasource use and are therefore blocked on
* EVERY deployment, independent of {@code IN_DOCKER} (GHSA-7wfp-6c63-99gq, GHSA-66gg-xpjf-p92v):
* link-local (covers the whole 169.254.0.0/16 IMDS range and IPv6 fe80::/10), any-local,
* multicast, and IPv6 Unique Local Addresses (fc00::/7).
*
* <p>Note this deliberately EXCLUDES loopback and RFC 1918 site-local ranges — those are gated
* separately (loopback under {@code IN_DOCKER} or strict mode; RFC 1918 under strict mode only)
* so that self-hosted deployments and local development that legitimately reach private/loopback
* services are not broken by default.
*/
static boolean isAlwaysBlockedAddressClass(String canonicalHost) {
if (!isValidIpAddress(canonicalHost)) {
return false;
}
try {
return matchesAlwaysBlockedAddressClass(InetAddress.getByName(canonicalHost));
} catch (UnknownHostException e) {
return false;
}
}

private static boolean matchesAlwaysBlockedAddressClass(InetAddress address) {
if (address.isAnyLocalAddress() || address.isLinkLocalAddress() || address.isMulticastAddress()) {
return true;
}
if (address instanceof Inet6Address) {
Expand All @@ -300,8 +367,47 @@ private static boolean matchesBlockedAddressClass(InetAddress address) {
return false;
}

private static boolean isBlockedAddressClassInDocker(String canonicalHost) {
return "1".equals(System.getenv("IN_DOCKER")) && isBlockedIpAddressClass(canonicalHost);
private static boolean matchesBlockedAddressClass(InetAddress address) {
return address.isLoopbackAddress() || matchesAlwaysBlockedAddressClass(address);
}

/**
* Whether an IP-literal host must be blocked for outbound egress under the current deployment
* configuration. Used by both the WebClient HTTP path ({@link #requestFilterFn} /
* {@link #isDisallowedAndFail}) and the datasource resolver ({@link #resolveForDatasource}).
*/
private static boolean isBlockedForEgress(String canonicalHost) {
if (!isValidIpAddress(canonicalHost)) {
return false;
}
final InetAddress address;
try {
address = InetAddress.getByName(canonicalHost);
} catch (UnknownHostException e) {
return false;
}
return isBlockedResolvedAddress(address, "1".equals(System.getenv("IN_DOCKER")), BLOCK_PRIVATE_ADDRESSES);
}

/**
* Pure address-class decision, extracted so it can be unit-tested deterministically without
* mutating process env.
*
* @param inDocker whether the process runs inside the Appsmith Docker image ({@code IN_DOCKER=1})
* @param blockPrivate whether strict egress mode ({@code APPSMITH_SSRF_BLOCK_PRIVATE_ADDRESS=true}) is on
*/
static boolean isBlockedResolvedAddress(InetAddress address, boolean inDocker, boolean blockPrivate) {
// Always blocked, every deployment: link-local (incl. IMDS), any-local, multicast, IPv6 ULA.
if (matchesAlwaysBlockedAddressClass(address)) {
return true;
}
// Loopback: blocked inside Docker (existing behaviour) or when strict egress is enabled.
if ((inDocker || blockPrivate) && address.isLoopbackAddress()) {
return true;
}
// RFC 1918 site-local: blocked only under strict egress (never gated on IN_DOCKER, because
// Docker deployments routinely reach private-range services such as sibling containers).
return blockPrivate && address.isSiteLocalAddress();
}

private static boolean isValidIpAddress(String host) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,149 @@ public void isBlockedIpAddressClass_doesNotMatchOtherHosts(String host) {
"Did not expect " + host + " to be recognized as a blocked address class");
}

// GHSA-7wfp-6c63-99gq / GHSA-66gg-xpjf-p92v / GHSA-4fjr-w826-cpwm:
// Link-local (incl. non-listed IMDS ranges), any-local, multicast, and IPv6 ULA addresses have
// no legitimate datasource use, so they must be blocked on every deployment — not only under
// IN_DOCKER as before. isAlwaysBlockedAddressClass is deployment-independent.
@ParameterizedTest
@ValueSource(
strings = {
// Link-local IPv4 (169.254.0.0/16) beyond the explicitly listed metadata IPs
"169.254.1.1",
"169.254.0.1",
// Link-local IPv6
"fe80::1",
// Any-local
"0.0.0.0",
"::",
// Multicast
"224.0.0.1",
"239.255.255.250",
"ff02::1",
// IPv6 ULA (fc00::/7)
"fc00::1",
"fd00::1",
})
public void isAlwaysBlockedAddressClass_recognizesNonRoutableClasses(String host) {
assertTrue(
WebClientUtils.isAlwaysBlockedAddressClass(host),
"Expected " + host + " to be an always-blocked address class");
}

// Loopback and RFC1918 are NOT in the always-blocked set: loopback stays reachable on non-Docker
// deployments (and localhost-backed datasources / MockWebServer tests keep working), and RFC1918
// stays reachable so self-hosted internal datasources are unaffected. No regression by default.
@ParameterizedTest
@ValueSource(strings = {"127.0.0.1", "::1", "10.0.0.1", "192.168.1.1", "172.16.0.1", "1.1.1.1", "8.8.8.8"})
public void isAlwaysBlockedAddressClass_doesNotMatchLoopbackPrivateOrPublic(String host) {
assertFalse(
WebClientUtils.isAlwaysBlockedAddressClass(host),
"Did not expect " + host + " to be an always-blocked address class");
}

// The HTTP egress enforcement points (isDisallowedAndFail / requestFilterFn) must reject the
// always-blocked classes even when IN_DOCKER is unset (the historical gap this batch closes).
@ParameterizedTest
@ValueSource(strings = {"169.254.1.1", "fe80::1", "0.0.0.0", "224.0.0.1", "fc00::1"})
public void isDisallowedAndFail_blocksAlwaysBlockedClassesRegardlessOfDocker(String host) {
assertTrue(
WebClientUtils.isDisallowedAndFail(host, null),
"Expected " + host + " to be blocked on all deployments");
}

@ParameterizedTest
@ValueSource(
strings = {
"http://169.254.1.1/latest/meta-data",
"http://[fe80::1]/",
"http://0.0.0.0/",
"http://224.0.0.1/",
})
public void requestFilterFn_blocksAlwaysBlockedClassesRegardlessOfDocker(String url) throws Exception {
StepVerifier.create(invokeRequestFilterFn(url))
.expectErrorSatisfies(throwable -> {
assertTrue(throwable instanceof UnknownHostException);
assertEquals(WebClientUtils.HOST_NOT_ALLOWED, throwable.getMessage());
})
.verify();
}

// Default posture (no IN_DOCKER, strict-egress flag off): loopback and RFC1918 remain reachable
// so existing self-hosted internal datasources and local dev keep working (no regression).
@ParameterizedTest
@ValueSource(strings = {"127.0.0.1", "10.0.0.1", "192.168.1.1", "172.16.0.1"})
public void isDisallowedAndFail_allowsLoopbackAndPrivateByDefault(String host) {
assertFalse(
WebClientUtils.isDisallowedAndFail(host, null),
"Expected " + host + " to be allowed by default (no IN_DOCKER, strict egress off)");
}
Comment on lines +218 to +224

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Does the CI/build env set IN_DOCKER for server test runs?
rg -n 'IN_DOCKER' --iglob '*.yml' --iglob '*.yaml' --iglob 'Dockerfile*' --iglob '*.sh'

Repository: appsmithorg/appsmith

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate IN_DOCKER usage ---'
rg -n 'IN_DOCKER' app/server || true

echo
echo '--- inspect WebClientUtilsTest around the flagged lines ---'
sed -n '180,260p' app/server/appsmith-interfaces/src/test/java/com/appsmith/util/WebClientUtilsTest.java

echo
echo '--- inspect WebClientUtils implementation ---'
fd -a 'WebClientUtils.java' app/server

Repository: appsmithorg/appsmith

Length of output: 6630


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="$(fd -a 'WebClientUtils.java' app/server | head -n 1)"
echo "FILE=$file"
echo
ast-grep outline "$file" --view expanded || true
echo
sed -n '1,260p' "$file"

Repository: appsmithorg/appsmith

Length of output: 13740


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java"

echo '--- isDisallowedAndFail / resolveForDatasource / egress checks ---'
sed -n '250,420p' "$file"

Repository: appsmithorg/appsmith

Length of output: 7944


Drop the loopback rows from the live-env tests
isDisallowedAndFail and resolveForDatasource both read IN_DOCKER at runtime, so the 127.0.0.1 cases flip under IN_DOCKER=1 while the RFC1918 rows stay deterministic. Keep the pure overload coverage below and leave only the non-Docker-gated cases here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-interfaces/src/test/java/com/appsmith/util/WebClientUtilsTest.java`
around lines 218 - 224, The parameterized test in WebClientUtilsTest mixes
loopback and RFC1918 hosts in a case that depends on the runtime IN_DOCKER flag,
so remove the 127.0.0.1 row from
isDisallowedAndFail_allowsLoopbackAndPrivateByDefault and keep only the
deterministic non-Docker-gated private ranges. Leave the separate pure-overload
coverage for isDisallowedAndFail and resolveForDatasource intact so the test
stays stable regardless of environment.


// resolveForDatasource is the datasource-facing resolver used by non-HTTP plugins that connect
// outside the WebClient pipeline (e.g. SMTP via JavaMail). It blocks the cloud-metadata denylist
// and the always-blocked address classes on every deployment, but allows loopback and RFC1918 by
// default so self-hosted mail servers on private networks (and Testcontainers hosts that resolve
// to loopback on some Docker setups) keep working. It returns the resolved address so callers can
// pin the connection and defeat DNS-rebinding.
@ParameterizedTest
@ValueSource(strings = {"169.254.169.254", "169.254.1.1", "0.0.0.0", "224.0.0.1", "metadata.google.internal"})
public void resolveForDatasource_blocksMetadataAndAlwaysBlockedClasses(String host) {
assertTrue(
WebClientUtils.resolveForDatasource(host).isEmpty(),
"Expected datasource host " + host + " to be blocked");
}

@ParameterizedTest
@ValueSource(strings = {"127.0.0.1", "10.0.0.1", "192.168.1.1", "172.16.0.1"})
public void resolveForDatasource_allowsLoopbackAndPrivateByDefault(String host) {
assertTrue(
WebClientUtils.resolveForDatasource(host).isPresent(),
"Expected datasource host " + host + " to be allowed by default");
}

@Test
public void resolveForDatasource_blocksNullAndEmpty() {
assertTrue(WebClientUtils.resolveForDatasource(null).isEmpty());
assertTrue(WebClientUtils.resolveForDatasource(" ").isEmpty());
}

@Test
public void resolveForDatasource_returnsResolvedAddressForPublicHost() {
Optional<InetAddress> result = WebClientUtils.resolveForDatasource("smtp.gmail.com");
assertTrue(result.isPresent());
}

// Strict egress mode (APPSMITH_SSRF_BLOCK_PRIVATE_ADDRESS=true) additionally blocks loopback and
// RFC1918 on all deployments. Exercised through the pure overload so no process env is mutated.
@Test
public void isBlockedResolvedAddress_strictMode_blocksLoopbackAndPrivate() throws Exception {
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("127.0.0.1"), false, true));
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("10.0.0.1"), false, true));
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("192.168.1.1"), false, true));
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("172.16.0.1"), false, true));
}

@Test
public void isBlockedResolvedAddress_defaultMode_allowsLoopbackAndPrivate() throws Exception {
assertFalse(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("127.0.0.1"), false, false));
assertFalse(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("10.0.0.1"), false, false));
assertFalse(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("192.168.1.1"), false, false));
}

@Test
public void isBlockedResolvedAddress_dockerMode_blocksLoopbackButNotPrivate() throws Exception {
// In Docker, loopback is blocked but RFC1918 stays reachable (sibling containers/services).
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("127.0.0.1"), true, false));
assertFalse(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("10.0.0.1"), true, false));
}

@Test
public void isBlockedResolvedAddress_alwaysBlocksNonRoutableClassesInEveryMode() throws Exception {
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("169.254.1.1"), false, false));
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("fe80::1"), false, false));
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("224.0.0.1"), false, false));
assertTrue(WebClientUtils.isBlockedResolvedAddress(InetAddress.getByName("fc00::1"), false, false));
}

@SuppressWarnings("unchecked")
private Mono<ClientRequest> invokeRequestFilterFn(String url) throws Exception {
final Method method = WebClientUtils.class.getDeclaredMethod("requestFilterFn", ClientRequest.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.models.ApiKeyAuth;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.util.WebClientUtils;
import com.external.plugins.constants.AnthropicConstants;
import com.external.plugins.constants.AnthropicErrorMessages;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.client.WebClient;
Expand Down Expand Up @@ -69,10 +69,11 @@ public static Mono<ResponseEntity<byte[]>> makeRequest(

private static WebClient createWebClient() {
// Initializing webClient to be used for http call
WebClient.Builder webClientBuilder = WebClient.builder();
// Route outbound calls through WebClientUtils so the centralized SSRF address filter is
// applied (GHSA-h4wh-59p8-vqff).
WebClient.Builder webClientBuilder = WebClientUtils.builder(HttpClient.create(connectionProvider()));
return webClientBuilder
.exchangeStrategies(AnthropicConstants.EXCHANGE_STRATEGIES)
.clientConnector(new ReactorClientHttpConnector(HttpClient.create(connectionProvider())))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.util.WebClientUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jakarta.validation.constraints.NotNull;
Expand All @@ -10,7 +11,6 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
Expand Down Expand Up @@ -132,11 +132,10 @@ public static Mono<ResponseEntity<byte[]>> makeRequest(

private static WebClient createWebClient() {
// Initializing webClient to be used for http call
WebClient.Builder webClientBuilder = WebClient.builder();
return webClientBuilder
.exchangeStrategies(EXCHANGE_STRATEGIES)
.clientConnector(new ReactorClientHttpConnector(HttpClient.create(connectionProvider())))
.build();
// Route outbound calls through WebClientUtils so the centralized SSRF address filter is
// applied (GHSA-h4wh-59p8-vqff).
WebClient.Builder webClientBuilder = WebClientUtils.builder(HttpClient.create(connectionProvider()));
return webClientBuilder.exchangeStrategies(EXCHANGE_STRATEGIES).build();
}

private static ConnectionProvider connectionProvider() {
Expand Down
Loading
Loading