diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml
index 6a59bde6c..2586cf3c6 100644
--- a/.github/workflows/builds.yml
+++ b/.github/workflows/builds.yml
@@ -5,34 +5,50 @@ on:
     - cron: '0 12 * * *'
 
 jobs:
+  Verify:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Grant Permission
+        run: chmod +x ./mvnw
+      - uses: actions/setup-java@v4
+        with:
+          distribution: 'corretto'
+          java-version: '11'
+      - name: Verify
+        run: ./mvnw -B -ntp clean verify -DskipTests -Dgpg.skip=true
+
   RunOnLinux:
     runs-on: ubuntu-latest
+    needs: Verify
     steps:
       - uses: actions/checkout@v4
       - name: Grant Permission
-        run: sudo chmod +x ./mvnw
+        run: chmod +x ./mvnw
       - uses: actions/setup-java@v4
         with:
           distribution: 'corretto'
           java-version: '11'
       - name: Run Tests
-        run: ./mvnw -B -ntp clean test
+        run: ./mvnw -B -ntp test
 
   RunOnMacOs:
     runs-on: macos-latest
+    needs: Verify
     steps:
       - uses: actions/checkout@v4
       - name: Grant Permission
-        run: sudo chmod +x ./mvnw
+        run: chmod +x ./mvnw
       - uses: actions/setup-java@v4
         with:
           distribution: 'corretto'
           java-version: '11'
       - name: Run Tests
-        run: ./mvnw -B -ntp clean test
+        run: ./mvnw -B -ntp test
 
   RunOnWindows:
     runs-on: windows-latest
+    needs: Verify
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-java@v4
@@ -40,4 +56,4 @@ jobs:
           distribution: 'corretto'
           java-version: '11'
       - name: Run Tests
-        run: ./mvnw.cmd -B -ntp clean test
+        run: ./mvnw.cmd -B -ntp test
diff --git a/client/pom.xml b/client/pom.xml
index 749a98ddb..6e56bf9fc 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -31,7 +31,7 @@
         <javaModuleName>org.asynchttpclient.client</javaModuleName>
 
         <jetty.version>11.0.24</jetty.version>
-        <tomcat.version>10.1.39</tomcat.version>
+        <tomcat.version>10.1.42</tomcat.version>
         <commons-io.version>2.18.0</commons-io.version>
         <mockito.version>4.11.0</mockito.version>
         <hamcrest.version>3.0</hamcrest.version>
@@ -51,7 +51,7 @@
         <dependency>
             <groupId>commons-fileupload</groupId>
             <artifactId>commons-fileupload</artifactId>
-            <version>1.5</version>
+            <version>1.6.0</version>
             <scope>test</scope>
         </dependency>
 
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
index 12dc93d7d..954628b3d 100644
--- a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
@@ -375,6 +375,13 @@ public interface AsyncHttpClientConfig {
 
     int getIoThreadsCount();
 
+    /**
+     * Indicates whether the Authorization header should be stripped during redirects to a different domain.
+     *
+     * @return true if the Authorization header should be stripped, false otherwise.
+     */
+    boolean isStripAuthorizationOnRedirect();
+
     enum ResponseBodyPartFactory {
 
         EAGER {
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
index e72235c17..1c7dbf37f 100644
--- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
+++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
@@ -127,6 +127,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     private final boolean keepEncodingHeader;
     private final ProxyServerSelector proxyServerSelector;
     private final boolean validateResponseHeaders;
+    private final boolean stripAuthorizationOnRedirect;
 
     // websockets
     private final boolean aggregateWebSocketFrameFragments;
@@ -219,6 +220,7 @@ private DefaultAsyncHttpClientConfig(// http
                                          boolean validateResponseHeaders,
                                          boolean aggregateWebSocketFrameFragments,
                                          boolean enablewebSocketCompression,
+                                         boolean stripAuthorizationOnRedirect,
 
                                          // timeouts
                                          Duration connectTimeout,
@@ -307,6 +309,7 @@ private DefaultAsyncHttpClientConfig(// http
         this.keepEncodingHeader = keepEncodingHeader;
         this.proxyServerSelector = proxyServerSelector;
         this.validateResponseHeaders = validateResponseHeaders;
+        this.stripAuthorizationOnRedirect = stripAuthorizationOnRedirect;
 
         // websocket
         this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments;
@@ -564,6 +567,11 @@ public boolean isValidateResponseHeaders() {
         return validateResponseHeaders;
     }
 
+    @Override
+    public boolean isStripAuthorizationOnRedirect() {
+        return stripAuthorizationOnRedirect;
+    }
+
     // ssl
     @Override
     public boolean isUseOpenSsl() {
@@ -800,6 +808,7 @@ public static class Builder {
         private boolean useProxySelector = defaultUseProxySelector();
         private boolean useProxyProperties = defaultUseProxyProperties();
         private boolean validateResponseHeaders = defaultValidateResponseHeaders();
+        private boolean stripAuthorizationOnRedirect = false; // default value
 
         // websocket
         private boolean aggregateWebSocketFrameFragments = defaultAggregateWebSocketFrameFragments();
@@ -891,6 +900,7 @@ public Builder(AsyncHttpClientConfig config) {
             keepEncodingHeader = config.isKeepEncodingHeader();
             proxyServerSelector = config.getProxyServerSelector();
             validateResponseHeaders = config.isValidateResponseHeaders();
+            stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect();
 
             // websocket
             aggregateWebSocketFrameFragments = config.isAggregateWebSocketFrameFragments();
@@ -1079,6 +1089,11 @@ public Builder setUseProxyProperties(boolean useProxyProperties) {
             return this;
         }
 
+        public Builder setStripAuthorizationOnRedirect(boolean value) {
+            stripAuthorizationOnRedirect = value;
+            return this;
+        }
+
         // websocket
         public Builder setAggregateWebSocketFrameFragments(boolean aggregateWebSocketFrameFragments) {
             this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments;
@@ -1444,6 +1459,7 @@ public DefaultAsyncHttpClientConfig build() {
                     validateResponseHeaders,
                     aggregateWebSocketFrameFragments,
                     enablewebSocketCompression,
+                    stripAuthorizationOnRedirect,
                     connectTimeout,
                     requestTimeout,
                     readTimeout,
diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
index c5e4a97d0..c29c0f33d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
+++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
@@ -187,10 +187,10 @@ public boolean cancel(boolean force) {
             return false;
         }
 
-        // cancel could happen before channel was attached
-        if (channel != null) {
-            Channels.setDiscard(channel);
-            Channels.silentlyCloseChannel(channel);
+        final Channel ch = channel; //atomic read, so that it won't end up in TOCTOU
+        if (ch != null) {
+            Channels.setDiscard(ch);
+            Channels.silentlyCloseChannel(ch);
         }
 
         if (ON_THROWABLE_CALLED_FIELD.getAndSet(this, 1) == 0) {
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java
index e60495f80..40628a7e5 100644
--- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java
@@ -35,7 +35,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION;
@@ -73,11 +72,13 @@ public class Redirect30xInterceptor {
     private final AsyncHttpClientConfig config;
     private final NettyRequestSender requestSender;
     private final MaxRedirectException maxRedirectException;
+    private final boolean stripAuthorizationOnRedirect;
 
     Redirect30xInterceptor(ChannelManager channelManager, AsyncHttpClientConfig config, NettyRequestSender requestSender) {
         this.channelManager = channelManager;
         this.config = config;
         this.requestSender = requestSender;
+        stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect(); // New flag
         maxRedirectException = unknownStackTrace(new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()),
                 Redirect30xInterceptor.class, "exitAfterHandlingRedirect");
     }
@@ -127,7 +128,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
                     }
                 }
 
-                requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody));
+                requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuthorizationOnRedirect));
 
                 // in case of a redirect from HTTP to HTTPS, future
                 // attributes might change
@@ -180,7 +181,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
         return false;
     }
 
-    private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody) {
+    private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) {
         HttpHeaders headers = request.getHeaders()
                 .remove(HOST)
                 .remove(CONTENT_LENGTH);
@@ -189,7 +190,7 @@ private static HttpHeaders propagatedHeaders(Request request, Realm realm, boole
             headers.remove(CONTENT_TYPE);
         }
 
-        if (realm != null && realm.getScheme() == AuthScheme.NTLM) {
+        if (stripAuthorization || (realm != null && realm.getScheme() == AuthScheme.NTLM)) {
             headers.remove(AUTHORIZATION)
                     .remove(PROXY_AUTHORIZATION);
         }
diff --git a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java
new file mode 100644
index 000000000..1548d6812
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java
@@ -0,0 +1,30 @@
+package org.asynchttpclient;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DefaultAsyncHttpClientConfigTest {
+    @Test
+    void testStripAuthorizationOnRedirect_DefaultIsFalse() {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build();
+        assertFalse(config.isStripAuthorizationOnRedirect(), "Default should be false");
+    }
+
+    @Test
+    void testStripAuthorizationOnRedirect_SetTrue() {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setStripAuthorizationOnRedirect(true)
+                .build();
+        assertTrue(config.isStripAuthorizationOnRedirect(), "Should be true when set");
+    }
+
+    @Test
+    void testStripAuthorizationOnRedirect_SetFalse() {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setStripAuthorizationOnRedirect(false)
+                .build();
+        assertFalse(config.isStripAuthorizationOnRedirect(), "Should be false when set to false");
+    }
+}
diff --git a/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java b/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java
new file mode 100644
index 000000000..08c150c08
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java
@@ -0,0 +1,95 @@
+package org.asynchttpclient;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class StripAuthorizationOnRedirectHttpTest {
+    private static HttpServer server;
+    private static int port;
+    private static volatile String lastAuthHeader;
+
+    @BeforeAll
+    public static void startServer() throws Exception {
+        server = HttpServer.create(new InetSocketAddress(0), 0);
+        port = server.getAddress().getPort();
+        server.createContext("/redirect", new RedirectHandler());
+        server.createContext("/final", new FinalHandler());
+        server.start();
+    }
+
+    @AfterAll
+    public static void stopServer() {
+        server.stop(0);
+    }
+
+    static class RedirectHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange exchange) {
+            String auth = exchange.getRequestHeaders().getFirst("Authorization");
+            lastAuthHeader = auth;
+            exchange.getResponseHeaders().add("Location", "http://localhost:" + port + "/final");
+            try {
+                exchange.sendResponseHeaders(302, -1);
+            } catch (Exception ignored) {
+            }
+            exchange.close();
+        }
+    }
+
+    static class FinalHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange exchange) {
+            String auth = exchange.getRequestHeaders().getFirst("Authorization");
+            lastAuthHeader = auth;
+            try {
+                exchange.sendResponseHeaders(200, 0);
+                exchange.getResponseBody().close();
+            } catch (Exception ignored) {
+            }
+            exchange.close();
+        }
+    }
+
+    @Test
+    void testAuthHeaderPropagatedByDefault() throws Exception {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setFollowRedirect(true)
+                .build();
+        try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) {
+            lastAuthHeader = null;
+            client.prepareGet("http://localhost:" + port + "/redirect")
+                    .setHeader("Authorization", "Bearer testtoken")
+                    .execute()
+                    .get(5, TimeUnit.SECONDS);
+            // By default, Authorization header is propagated to /final
+            assertEquals("Bearer testtoken", lastAuthHeader, "Authorization header should be present on redirect by default");
+        }
+    }
+
+    @Test
+    void testAuthHeaderStrippedWhenEnabled() throws Exception {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setFollowRedirect(true)
+                .setStripAuthorizationOnRedirect(true)
+                .build();
+        try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) {
+            lastAuthHeader = null;
+            client.prepareGet("http://localhost:" + port + "/redirect")
+                    .setHeader("Authorization", "Bearer testtoken")
+                    .execute()
+                    .get(5, TimeUnit.SECONDS);
+            // When enabled, Authorization header should be stripped on /final
+            assertNull(lastAuthHeader, "Authorization header should be stripped on redirect when enabled");
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 70d09ac53..9d64fc54b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,14 +105,14 @@
             <dependency>
                 <groupId>org.junit</groupId>
                 <artifactId>junit-bom</artifactId>
-                <version>5.11.4</version>
+                <version>5.13.0</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
             <dependency>
                 <groupId>io.github.nettyplus</groupId>
                 <artifactId>netty-leak-detector-junit-extension</artifactId>
-                <version>0.0.6</version>
+                <version>0.2.0</version>
             </dependency>
         </dependencies>
     </dependencyManagement>
@@ -422,10 +422,38 @@
                                 <arg>--pinentry-mode</arg>
                                 <arg>loopback</arg>
                             </gpgArguments>
+                            <gpg.skip>false</gpg.skip>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>
+
+            <plugin>
+                <groupId>com.github.siom79.japicmp</groupId>
+                <artifactId>japicmp-maven-plugin</artifactId>
+                <version>0.23.1</version>
+                <configuration>
+                    <version>
+                        <old>RELEASE</old>
+                        <new>${project.version}</new>
+                    </version>
+                    <parameter>
+                        <onlyModified>true</onlyModified>
+                        <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications>
+                        <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications>
+                        <includeSynthetic>false</includeSynthetic>
+                        <accessModifier>public</accessModifier>
+                    </parameter>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>cmp</goal>
+                        </goals>
+                        <phase>verify</phase>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 </project>
