diff --git a/changelog.md b/changelog.md index 5f5c3c6..b364d46 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.12.1 + +### Jun 29, 2026 + +- Snyk fix + ## v1.12.0 ### Jun 15, 2026 diff --git a/pom.xml b/pom.xml index 1f203be..f81fe82 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.12.0 + 1.12.1 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 69295d0..933bb71 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -747,10 +747,35 @@ public Builder setRetry(@NotNull Boolean retry) { * @return Client host */ public Builder setHost(@NotNull String hostname) { - this.hostname = hostname; + this.hostname = validateHostname(hostname); return this; } + /** + * Validates that the supplied hostname is a bare host (optionally with a + * port) and does not smuggle a scheme, credentials, path, query, or other + * characters that could redirect outbound requests to an unintended + * destination. This guards against Server-Side Request Forgery (SSRF) + * when the host is sourced from untrusted input. + * + * @param hostname the candidate host + * @return the validated host, unchanged + * @throws IllegalArgumentException if the host is null, blank, or malformed + */ + private static String validateHostname(String hostname) { + if (hostname == null || hostname.trim().isEmpty()) { + throw new IllegalArgumentException("Hostname must not be null or empty"); + } + String host = hostname.trim(); + // Reject embedded scheme, credentials, path/query/fragment, whitespace, + // and other characters that would change the request target. + if (!host.matches("^[A-Za-z0-9.-]+(:\\d{1,5})?$")) { + throw new IllegalArgumentException( + "Invalid hostname: '" + hostname + "'. Expected a bare host name with an optional port."); + } + return host; + } + /** * Configures the client to target a specific Contentstack region by resolving * the correct Content Management API host from the bundled regions registry. @@ -974,7 +999,10 @@ public Contentstack build() { } private void validateClient(Contentstack contentstack) { - String baseUrl = Util.PROTOCOL + "://" + this.hostname + "/" + version + "/"; + // Re-validate the host at build time so SSRF protection applies regardless + // of how the hostname was set (setHost, region resolution, or default). + String validatedHost = validateHostname(this.hostname); + String baseUrl = Util.PROTOCOL + "://" + validatedHost + "/" + version + "/"; this.instance = new Retrofit.Builder().baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(httpClient(contentstack, this.retry)).build();