This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 1b6856b4d7903780786c79327bed1b53ea051a15
Author: Robert Lazarski <[email protected]>
AuthorDate: Wed Aug 5 07:08:56 2026 -1000

    Refuse decoupled WS-Addressing responses by default
    
    Follows Apache CXF, whose implementation of the same decision is now visible
    in full: org.apache.cxf.ws.addressing.decoupled.enabled defaults to false 
and
    gates non-anonymous wsa:ReplyTo/FaultTo outright, with a scheme allowlist
    enforced even for pre-approved exchanges. Notably CXF implements no IP-range
    check at all -- the on/off gate plus the scheme list is their whole answer.
    
    That is the better primary control and it is now the Axis2 default too. The
    range check alone was a partial defence: refusing RFC-1918 and loopback 
still
    left the server able to deliver a response body to any public host the 
caller
    named, which is the exfiltration half of a non-blind SSRF. Refusing 
decoupled
    responses outright closes both halves, is a single boolean to document, and
    fails loudly so a deployment that needs the feature finds out immediately.
    blockPrivateNetworkResponseEndpoints stays as a secondary control for
    deployments that turn decoupled responses back on.
    
    CXF carved out WS-RM via a pre-approval property. Axis2 needs no equivalent:
    Sandesha is dead and the only in-tree references to it are commented out.
    
    The feature is exercised by 17 integration tests -- the async, two-channel,
    third-party-callback and separate-listener MTOM cases -- which now opt in
    through their test repositories. That opt-in is the migration a real
    deployment makes, so the tests double as its documentation. Such deployments
    should also set httpFrontendHostUrl, since the generated reply address
    otherwise defaults to the client machine's local IP.
    
    Co-Authored-By: Claude Fable 5 <[email protected]>
---
 .../addressing/ResponseEndpointPolicy.java         | 14 ++++----
 .../addressing/AddressingFinalInHandlerTest.java   |  6 ++++
 .../ResponseEndpointPolicyHandlerTest.java         | 26 ++++++++++++++
 .../addressing/ResponseEndpointPolicyTest.java     | 40 +++++++++++++++------
 modules/integration/pom.xml                        | 42 ++++++++++++++++++++++
 .../test-resources/mtom/MTOM-enabled-axis2.xml     |  4 +++
 .../mtom/MTOM-fileCache-enabled-axis2.xml          |  4 +++
 modules/kernel/conf/axis2.xml                      | 17 ++++++---
 modules/webapp/conf/axis2.xml                      | 17 ++++++---
 9 files changed, 145 insertions(+), 25 deletions(-)

diff --git 
a/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
 
b/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
index f9d7a3fe28..83fcfa058d 100644
--- 
a/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
+++ 
b/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
@@ -63,11 +63,13 @@ import java.util.concurrent.TimeoutException;
  * (service, service group, then {@code axis2.xml}):
  *
  * <dl>
- * <dt>{@code allowNonAnonymousResponseEndpoints} (default {@code true})</dt>
- * <dd>Set to {@code false} to refuse every non-anonymous response endpoint, so
- * replies and faults only ever travel back down the inbound connection. This 
is
- * the strictest posture and the right one for a deployment that does not use
- * decoupled or dual-channel responses.</dd>
+ * <dt>{@code allowNonAnonymousResponseEndpoints} (default {@code false})</dt>
+ * <dd>Off by default, so replies and faults only ever travel back down the
+ * inbound connection and an inbound header cannot name an outbound destination
+ * at all. Apache CXF made the same call for the same reason in its
+ * {@code org.apache.cxf.ws.addressing.decoupled.enabled} property. Set it to
+ * {@code true} for a deployment that genuinely uses decoupled responses — the
+ * separate-listener ("Dual") clients, or a third-party callback endpoint.</dd>
  *
  * <dt>{@code blockPrivateNetworkResponseEndpoints} (default {@code 
false})</dt>
  * <dd>Additionally rejects response endpoints that resolve to loopback,
@@ -143,7 +145,7 @@ final class ResponseEndpointPolicy {
             return true;
         }
 
-        if (!booleanParameter(messageContext, ALLOW_NON_ANONYMOUS, true)) {
+        if (!booleanParameter(messageContext, ALLOW_NON_ANONYMOUS, false)) {
             log.warn("Rejecting non-anonymous WS-Addressing response endpoint: 
"
                     + ALLOW_NON_ANONYMOUS + " is false");
             return false;
diff --git 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java
 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java
index 1918f246f2..0015569bc9 100644
--- 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java
+++ 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java
@@ -217,6 +217,12 @@ public class AddressingFinalInHandlerTest extends 
AddressingInHandlerTestBase {
         MessageContext mc = new MessageContext();
         
mc.setConfigurationContext(ConfigurationContextFactory.createEmptyConfigurationContext());
         mc.setServerSide(true);
+        // This fixture carries a non-anonymous wsa:ReplyTo and the test is 
about
+        // SOAPAction matching, so opt in to decoupled responses rather than 
have
+        // the egress policy fault the message first.
+        mc.getConfigurationContext().getAxisConfiguration().addParameter(
+                new org.apache.axis2.description.Parameter(
+                        "allowNonAnonymousResponseEndpoints", "true"));
         try {
             mc.setSoapAction("http://ws.apache.org/tests/action";);
             basicExtractAddressingInformationFromHeaders(testfile, mc);
diff --git 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
index 82273ccaaf..970294cf6b 100644
--- 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
+++ 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
@@ -51,6 +51,32 @@ public class ResponseEndpointPolicyHandlerTest extends 
TestCase {
         super.setUp();
         handler = new AddressingInHandler();
         configurationContext = 
ConfigurationContextFactory.createEmptyConfigurationContext();
+        // Decoupled responses are off by default; opt in so these tests reach 
the
+        // address checks. testDecoupledReplyToRefusedByDefault covers the 
default.
+        configurationContext.getAxisConfiguration().addParameter(
+                new Parameter(ResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, 
"true"));
+    }
+
+    /**
+     * Out of the box a non-anonymous ReplyTo is refused outright, whatever it
+     * points at, so an inbound header cannot name an outbound destination.
+     */
+    public void testDecoupledReplyToRefusedByDefault() throws Exception {
+        configurationContext = 
ConfigurationContextFactory.createEmptyConfigurationContext();
+        try {
+            invokeWith("ReplyTo", "http://192.0.2.25/replies";);
+            fail("A non-anonymous ReplyTo should fault under the shipped 
default");
+        } catch (AxisFault expected) {
+            // expected
+        }
+    }
+
+    /** Anonymous replies are unaffected, or ordinary in-out messaging breaks. 
*/
+    public void testAnonymousReplyToStillWorksByDefault() throws Exception {
+        configurationContext = 
ConfigurationContextFactory.createEmptyConfigurationContext();
+        MessageContext mc = invokeWith("ReplyTo",
+                
org.apache.axis2.addressing.AddressingConstants.Final.WSA_ANONYMOUS_URL);
+        assertTrue(mc.getReplyTo().hasAnonymousAddress());
     }
 
     /**
diff --git 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
index 0d1af45aa8..478d0acdf8 100644
--- 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
+++ 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
@@ -42,6 +42,18 @@ public class ResponseEndpointPolicyTest extends TestCase {
         ConfigurationContext configurationContext = new 
ConfigurationContext(axisConfiguration);
         messageContext = configurationContext.createMessageContext();
         messageContext.setServerSide(true);
+        // Decoupled responses are off by default. Most tests here cover what 
the
+        // policy does to an endpoint it is actually willing to consider, so 
they
+        // opt in; testDecoupledResponsesAreOffByDefault covers the default.
+        setParameter(ResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "true");
+    }
+
+    /** A context without the opt-in, for testing the shipped default. */
+    private MessageContext defaultConfigured() throws Exception {
+        AxisConfiguration config = new AxisConfiguration();
+        MessageContext mc = new 
ConfigurationContext(config).createMessageContext();
+        mc.setServerSide(true);
+        return mc;
     }
 
     private void setParameter(String name, String value) throws Exception {
@@ -75,7 +87,7 @@ public class ResponseEndpointPolicyTest extends TestCase {
      * ThirdPartyResponseRawXMLTest replies to 127.0.0.1 — so the default 
policy
      * must not refuse them.
      */
-    public void testLoopbackAndPrivateAddressesAreAllowedByDefault() {
+    public void testLoopbackAndPrivateAddressesAllowedWhenDecoupledEnabled() {
         assertTrue(ResponseEndpointPolicy.isAllowed(
                 new EndpointReference("http://127.0.0.1:8080/sink";), 
messageContext));
         assertTrue(ResponseEndpointPolicy.isAllowed(
@@ -161,23 +173,29 @@ public class ResponseEndpointPolicyTest extends TestCase {
      * A routable public address is still permitted by default, so decoupled
      * responses to a genuine external endpoint keep working.
      */
-    public void testPublicAddressIsAllowedByDefault() {
+    public void testPublicAddressAllowedWhenDecoupledEnabled() {
         EndpointReference publicEpr = new 
EndpointReference("http://192.0.2.25/replies";);
         assertTrue(ResponseEndpointPolicy.isAllowed(publicEpr, 
messageContext));
     }
 
     /**
-     * The strict posture — the equivalent of what CXF made its default — 
refuses
-     * every non-anonymous response endpoint.
+     * The shipped default refuses every non-anonymous response endpoint, so no
+     * inbound header can name an outbound destination at all — the same call
+     * Apache CXF made in org.apache.cxf.ws.addressing.decoupled.enabled.
      */
-    public void testNonAnonymousCanBeDisabledEntirely() throws Exception {
-        setParameter(ResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "false");
-        assertFalse(ResponseEndpointPolicy.isAllowed(
-                new EndpointReference("http://192.0.2.25/replies";), 
messageContext));
-        // The anonymous case must still work, or in-out messaging breaks.
+    public void testDecoupledResponsesAreOffByDefault() throws Exception {
+        MessageContext defaults = defaultConfigured();
+        assertFalse("A public reply endpoint must be refused by default",
+                ResponseEndpointPolicy.isAllowed(
+                        new EndpointReference("http://192.0.2.25/replies";), 
defaults));
+        assertFalse("A private reply endpoint must be refused by default",
+                ResponseEndpointPolicy.isAllowed(
+                        new EndpointReference("http://10.1.2.3/internal";), 
defaults));
+
+        // Anonymous must still work by default, or ordinary in-out messaging 
breaks.
         assertTrue(ResponseEndpointPolicy.isAllowed(
-                new 
EndpointReference(AddressingConstants.Final.WSA_ANONYMOUS_URL),
-                messageContext));
+                new 
EndpointReference(AddressingConstants.Final.WSA_ANONYMOUS_URL), defaults));
+        assertTrue(ResponseEndpointPolicy.isAllowed(null, defaults));
     }
 
     /**
diff --git a/modules/integration/pom.xml b/modules/integration/pom.xml
index 6887febc14..2ad66dba7b 100644
--- a/modules/integration/pom.xml
+++ b/modules/integration/pom.xml
@@ -254,6 +254,20 @@
                                 <modules>
                                     <module>addressing</module>
                                 </modules>
+                                <!--
+                                  Decoupled WS-Addressing responses are off by 
default
+                                  (allowNonAnonymousResponseEndpoints in 
axis2.xml). The
+                                  async, two-channel and third-party-callback 
tests here
+                                  exercise exactly that feature, so they opt 
in - which
+                                  also demonstrates the migration a deployment 
using
+                                  those clients has to make.
+                                -->
+                                <parameters>
+                                    <parameter>
+                                        
<name>allowNonAnonymousResponseEndpoints</name>
+                                        <value>true</value>
+                                    </parameter>
+                                </parameters>
                                 <handlers>
                                     <handler>
                                         <flow>InFlow</flow>
@@ -278,6 +292,20 @@
                                 <modules>
                                     <module>addressing</module>
                                 </modules>
+                                <!--
+                                  Decoupled WS-Addressing responses are off by 
default
+                                  (allowNonAnonymousResponseEndpoints in 
axis2.xml). The
+                                  async, two-channel and third-party-callback 
tests here
+                                  exercise exactly that feature, so they opt 
in - which
+                                  also demonstrates the migration a deployment 
using
+                                  those clients has to make.
+                                -->
+                                <parameters>
+                                    <parameter>
+                                        
<name>allowNonAnonymousResponseEndpoints</name>
+                                        <value>true</value>
+                                    </parameter>
+                                </parameters>
                             </generatedAxis2xml>
                             <modules>addressing</modules>
                         </configuration>
@@ -329,6 +357,20 @@
                                 <modules>
                                     <module>addressing</module>
                                 </modules>
+                                <!--
+                                  Decoupled WS-Addressing responses are off by 
default
+                                  (see allowNonAnonymousResponseEndpoints in 
axis2.xml).
+                                  The async, two-channel and 
third-party-callback tests
+                                  in this module exercise exactly that 
feature, so they
+                                  opt in here - which also demonstrates the 
migration a
+                                  deployment using those clients has to make.
+                                -->
+                                <parameters>
+                                    <parameter>
+                                        
<name>allowNonAnonymousResponseEndpoints</name>
+                                        <value>true</value>
+                                    </parameter>
+                                </parameters>
                             </generatedAxis2xml>
                             <modules>addressing</modules>
                         </configuration>
diff --git a/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml 
b/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml
index 78c25ab096..2c55ca73d5 100644
--- a/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml
+++ b/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml
@@ -18,6 +18,10 @@
   -->
 
 <axisconfig name="AxisJava2.0">
+    <!-- The separate-listener MTOM tests use a decoupled wsa:ReplyTo, which is
+         off by default; opt in so they exercise that path. -->
+    <parameter name="allowNonAnonymousResponseEndpoints">true</parameter>
+
     <!-- ================================================= -->
     <!-- Parameters -->
     <!-- ================================================= -->
diff --git 
a/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml 
b/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml
index 45c701294f..0a86f27802 100644
--- a/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml
+++ b/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml
@@ -18,6 +18,10 @@
   -->
 
 <axisconfig name="AxisJava2.0">
+    <!-- The separate-listener MTOM tests use a decoupled wsa:ReplyTo, which is
+         off by default; opt in so they exercise that path. -->
+    <parameter name="allowNonAnonymousResponseEndpoints">true</parameter>
+
     <!-- ================================================= -->
     <!-- Parameters -->
     <!-- ================================================= -->
diff --git a/modules/kernel/conf/axis2.xml b/modules/kernel/conf/axis2.xml
index 1c474c016e..8bc34b9091 100644
--- a/modules/kernel/conf/axis2.xml
+++ b/modules/kernel/conf/axis2.xml
@@ -72,9 +72,18 @@
     are wired. Turn it on wherever the caller is not already inside the trusted
     network.
 
-    Set allowNonAnonymousResponseEndpoints to false to refuse decoupled
-    responses entirely - the right setting unless this deployment actually uses
-    dual-channel or decoupled WS-Addressing responses.
+    allowNonAnonymousResponseEndpoints is FALSE by default as of 2.0.2: a
+    reply or fault only ever travels back down the inbound connection, so an
+    inbound header cannot name an outbound destination at all. Apache CXF made
+    the same call for the same reason in its
+    org.apache.cxf.ws.addressing.decoupled.enabled property.
+
+    Set it to true only if this deployment genuinely uses decoupled responses -
+    the separate-listener "Dual" clients (Options.setUseSeparateListener(true),
+    see samples/userguide EchoBlockingDualClient) or a third-party callback
+    endpoint. Those deployments should also set httpFrontendHostUrl so the
+    generated reply address is the real external URL rather than the client
+    machine's local IP.
 
     allowedResponseEndpointHosts, when set to a comma-separated host list,
     restricts response endpoints to exactly those hosts.
@@ -88,7 +97,7 @@
     list is not restricted to HTTP. Schemes outside it, such as file or gopher,
     are refused. Add to this list if a custom transport is in use.
     -->
-    <parameter name="allowNonAnonymousResponseEndpoints">true</parameter>
+    <parameter name="allowNonAnonymousResponseEndpoints">false</parameter>
     <parameter name="blockPrivateNetworkResponseEndpoints">false</parameter>
     <!--<parameter 
name="allowedResponseEndpointHosts">replies.example.com</parameter>-->
     <!--<parameter 
name="allowedResponseEndpointSchemes">http,https</parameter>-->
diff --git a/modules/webapp/conf/axis2.xml b/modules/webapp/conf/axis2.xml
index a26c7723fe..2e8aecbe9b 100644
--- a/modules/webapp/conf/axis2.xml
+++ b/modules/webapp/conf/axis2.xml
@@ -72,9 +72,18 @@
     are wired. Turn it on wherever the caller is not already inside the trusted
     network.
 
-    Set allowNonAnonymousResponseEndpoints to false to refuse decoupled
-    responses entirely - the right setting unless this deployment actually uses
-    dual-channel or decoupled WS-Addressing responses.
+    allowNonAnonymousResponseEndpoints is FALSE by default as of 2.0.2: a
+    reply or fault only ever travels back down the inbound connection, so an
+    inbound header cannot name an outbound destination at all. Apache CXF made
+    the same call for the same reason in its
+    org.apache.cxf.ws.addressing.decoupled.enabled property.
+
+    Set it to true only if this deployment genuinely uses decoupled responses -
+    the separate-listener "Dual" clients (Options.setUseSeparateListener(true),
+    see samples/userguide EchoBlockingDualClient) or a third-party callback
+    endpoint. Those deployments should also set httpFrontendHostUrl so the
+    generated reply address is the real external URL rather than the client
+    machine's local IP.
 
     allowedResponseEndpointHosts, when set to a comma-separated host list,
     restricts response endpoints to exactly those hosts.
@@ -88,7 +97,7 @@
     list is not restricted to HTTP. Schemes outside it, such as file or gopher,
     are refused. Add to this list if a custom transport is in use.
     -->
-    <parameter name="allowNonAnonymousResponseEndpoints">true</parameter>
+    <parameter name="allowNonAnonymousResponseEndpoints">false</parameter>
     <parameter name="blockPrivateNetworkResponseEndpoints">false</parameter>
     <!--<parameter 
name="allowedResponseEndpointHosts">replies.example.com</parameter>-->
     <!--<parameter 
name="allowedResponseEndpointSchemes">http,https</parameter>-->

Reply via email to