diff --git a/CHANGELOG.md b/CHANGELOG.md index 14508021..0453e4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,42 @@ # Changelog +## 9.10.0 (2026-10-06) + +### Features + +#### Reject request paths that would leave the configured base URL + +The low-level request path was joined onto the configured base URL with a resolving +join, which follows a path the way a browser follows a link. An absolute URL +(`https://host/x`) or a scheme-relative one (`//host/x`) replaced the configured +origin outright while the Authorization header was still attached, so an application +that passed untrusted input as a path could send its API token to a host of someone +else's choosing. + +A path that is not a relative reference is now rejected before the join. Paths +containing dot segments remain valid: they resolve against the base URL and cannot +leave its origin. + +#### Reject URL parameters that could change which endpoint is addressed + +A URL parameter is a single path segment — a resource identity — but the escaping +applied to one varied by language, and in Go, Node, PHP and .NET there was none at +all. A value carrying path syntax could move a request to an endpoint the caller +never asked for: `find("../mandates")` reached the mandates collection, and +`find("?limit=500")` injected a query parameter. + +Escaping alone cannot fix this, because `.` and `..` are dot segments that a path +resolver strips whether or not they are encoded, and an empty value addresses the +collection rather than one resource. Values that could change which endpoint is +addressed are therefore rejected rather than escaped: `/`, `?`, `#`, control +characters, `.`, `..` and the empty string now raise an error instead of producing a +request that quietly 404s. Everything else is escaped as before. + +No valid GoCardless resource identity contains any of these characters, so correct +code is unaffected. Ruby and Java previously encoded `/` as `%2F` and sent the +request; they now raise. + ## 9.9.0 (2026-10-05) ### Features diff --git a/README.md b/README.md index 144bc44f..15f7890d 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ With Maven: com.gocardless gocardless-pro - 9.9.0 + 9.10.0 ``` With Gradle: ``` -implementation 'com.gocardless:gocardless-pro:9.9.0' +implementation 'com.gocardless:gocardless-pro:9.10.0' ``` ## Initializing the client diff --git a/build.gradle b/build.gradle index 4344533b..f8a1c431 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ plugins { sourceCompatibility = 1.8 targetCompatibility = 1.8 group = 'com.gocardless' -version = '9.9.0' +version = '9.10.0' apply plugin: 'ch.raffael.pegdown-doclet' diff --git a/src/main/java/com/gocardless/http/HttpClient.java b/src/main/java/com/gocardless/http/HttpClient.java index c2bcb4c7..de1080d3 100644 --- a/src/main/java/com/gocardless/http/HttpClient.java +++ b/src/main/java/com/gocardless/http/HttpClient.java @@ -35,7 +35,7 @@ public class HttpClient { private static final String DISALLOWED_USER_AGENT_CHARACTERS = "[^\\w!#$%&'\\*\\+\\-\\.\\^`\\|~]"; private static final String USER_AGENT = - String.format("gocardless-pro-java/9.9.0 java/%s %s/%s %s/%s", + String.format("gocardless-pro-java/9.10.0 java/%s %s/%s %s/%s", cleanUserAgentToken(System.getProperty("java.vm.specification.version")), cleanUserAgentToken(System.getProperty("java.vm.name")), cleanUserAgentToken(System.getProperty("java.version")), @@ -49,7 +49,7 @@ public class HttpClient { builder.put("GoCardless-Version", "2015-07-06"); builder.put("Accept", "application/json"); builder.put("GoCardless-Client-Library", "gocardless-pro-java"); - builder.put("GoCardless-Client-Version", "9.9.0"); + builder.put("GoCardless-Client-Version", "9.10.0"); HEADERS = builder.build(); } private final OkHttpClient rawClient;