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

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new fe000acced READY-389/390/392/396: Release notes + docs for the 
parser-depth and client SSRF hardening batch
fe000acced is described below

commit fe000acced472444852633a399bfc77ef309f4ab
Author: James Bognar <[email protected]>
AuthorDate: Sun Aug 16 21:23:21 2026 -0400

    READY-389/390/392/396: Release notes + docs for the parser-depth and client 
SSRF hardening batch
    
    Adds a release-notes entry for the READY-392 @Remote SSRF guardrail 
hardening
    (deny-private, pin-on-connect, redirect-revalidation, fail-closed, and the
    allowPrivateUrls opt-out), replacing the prior scheme-only description, and
    updates the NextGenRestClient topic's SSRF guardrail warning box to match.
---
 pages/release-notes/10.0.0.md           |  5 +++--
 pages/topics/13.14.NextGenRestClient.md | 40 +++++++++++++++++++++++++++------
 2 files changed, 36 insertions(+), 9 deletions(-)

diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index cb15a46f68..3e914e056d 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -569,13 +569,14 @@ model and the operator opt-in danger callout.
 
 ### Next-generation remote-proxy declarative features
 
-Juneau 10.0 significantly expands what the [REST 
proxy](/docs/topics/RestProxies) annotation family (`@Remote`, `@RemoteOp`, and 
the verb annotations `@RemoteGet` / `@RemotePost` / `@RemotePut` / 
`@RemoteDelete` / `@RemotePatch`) can express declaratively. Every addition 
below is honored by the **next-generation** proxy engine only 
(`RestClient.remote(...)` → `RemoteClient`); the classic `getRemote(...)` 
engine is unchanged and ignores these members.
+Juneau 10.0 significantly expands what the [REST 
proxy](/docs/topics/RestProxies) annotation family (`@Remote`, `@RemoteOp`, and 
the verb annotations `@RemoteGet` / `@RemotePost` / `@RemotePut` / 
`@RemoteDelete` / `@RemotePatch`) can express declaratively. Most additions 
below are honored by the **next-generation** proxy engine only 
(`RestClient.remote(...)` → `RemoteClient`); the classic `getRemote(...)` 
engine ignores those members. The one exception is dynamic URL & base override 
(`@U [...]
 
 - **Constant part values** — `@Remote` (interface) and `@RemoteOp` / verb 
annotations (method) gain `headers` / `queryData` / `formData` members that 
emit always-applied constant headers, query parameters, and form-data fields on 
every call, with no dummy parameter. Method-level constants take precedence 
over interface-level ones, caller-supplied values still compose, and all values 
resolve through `VarResolver.DEFAULT`. (Constant path values are out of scope; 
`@Remote(headerList=…)` rem [...]
 - **`@Content(def=…)` honored** — the next-gen engine now applies both the 
parameter-level body default (when the body argument is `null`) and a 
param-less, method-level constant body, matching the classic engine.
 - **Per-part serializers** — `@HttpPartMarshalling(serializer=…)` is now wired 
for outgoing query / header / path / form-data parts, with precedence parameter 
› method › interface and a fallback to the default `OpenApiSerializer` when 
absent. (Serializer side only; the parser member is not consumed by the 
next-gen engine.)
 - **Declarative call policy** — `@Remote` and `@RemoteOp` / verb annotations 
gain `interceptors()`, `timeout()`, `retries()`, `retryNonIdempotent()`, and 
`throwOnError()`. Interceptors apply as a union (builder → interface → method); 
timeout/retries/throwOnError scalars follow method › interface › 
builder-default precedence. Auto-retries are conservatively safe: triggered 
only by connection failures or `429`/`5xx`, with exponential backoff, and gated 
to idempotent verbs (POST/PATCH requi [...]
-- **Dynamic URL & base override** — a new `@Url` parameter annotation 
(`org.apache.juneau.http.Url`) supplies the whole request URL at call time 
(absolute replaces and bypasses the client root URL; relative resolves against 
it), plus a declarative `baseUrl()` attribute on `@Remote` / `@RemoteOp` / verb 
annotations that substitutes the authority+root while preserving the path and 
`{var}` templating. Precedence: `@Url` › method `baseUrl` › interface `baseUrl` 
› client `rootUrl`. URL/base o [...]
+- **Dynamic URL & base override** — a new `@Url` parameter annotation 
(`org.apache.juneau.http.Url`) supplies the whole request URL at call time 
(absolute replaces and bypasses the client root URL; relative resolves against 
it), plus a declarative `baseUrl()` attribute on `@Remote` / `@RemoteOp` / verb 
annotations that substitutes the authority+root while preserving the path and 
`{var}` templating. Precedence: `@Url` › method `baseUrl` › interface `baseUrl` 
› client `rootUrl`. **Unlike t [...]
+- **`@Remote` SSRF guardrail hardened: deny-private, pin-on-connect, 
redirect-revalidation** — every absolute effective `@Remote` URL (`@Url`, 
`baseUrl()`, or an already-absolute default path) is now checked against a 
deny-private policy by default — loopback, RFC1918/unique-local, link-local 
(including the `169.254.169.254`/`169.254.170.2` cloud-metadata addresses), 
`localhost`, and `*.metadata.google.internal` are all rejected — and the check 
runs both as a pre-check on the literal hos [...]
 - **Declarative multipart** — a new method-level `@Multipart` marker 
(`org.apache.juneau.http.remote.Multipart`) plus a `@Part` parameter annotation 
(`org.apache.juneau.http.Part`, with `name`/`value`/`fileName`/`contentType`) 
build a `multipart/form-data` request from annotated parameters. Text, 
`byte[]`, `File`, `InputStream`, `Reader`, `HttpBody`, and bean part sources 
are accepted; file/stream/reader/bean parts stream rather than buffer. A method 
is either multipart or single-`@Conte [...]
 - **Per-method format selection** — `contentType()` and `accept()` attributes 
on `@Remote` / `@RemoteOp` / verb annotations drive marshaller **selection** 
(not just header values). `contentType` selects the matching request serializer 
and emits a single clean `Content-Type`; `accept` sets the `Accept` header and 
acts as a fallback parser (the response `Content-Type` stays authoritative). A 
no-match media type falls back to the default marshaller but still sends the 
overridden label (vend [...]
 - **Streaming efficiency** — POJO and `Reader` request bodies now stream 
straight to the wire instead of being buffered, and `Reader` / `InputStream` 
return types hand the caller a lazy stream over the live response whose 
connection is released on close.
diff --git a/pages/topics/13.14.NextGenRestClient.md 
b/pages/topics/13.14.NextGenRestClient.md
index 81697f9bab..95ab2be278 100644
--- a/pages/topics/13.14.NextGenRestClient.md
+++ b/pages/topics/13.14.NextGenRestClient.md
@@ -210,18 +210,21 @@ The next-generation engine adds a family of declarative 
capabilities to the
 [REST proxy](/docs/topics/RestProxies) annotations (`@Remote`, `@RemoteOp`, 
and the verb
 annotations `@RemoteGet` / `@RemotePost` / `@RemotePut` / `@RemoteDelete` / 
`@RemotePatch`).
 
-:::warning Next-gen engine only
-Every feature in this section is honored **only** by the next-generation proxy 
engine obtained via
+:::warning Mostly next-gen engine only
+Most features in this section are honored **only** by the next-generation 
proxy engine obtained via
 `RestClient.remote(MyProxy.class)` → `RemoteClient`. The classic engine 
obtained via
-`RestClient.getRemote(...)` is unchanged and ignores these annotation members. 
Where an attribute is
-visible on a shared annotation, its Javadoc states that the classic engine 
currently ignores it.
+`RestClient.getRemote(...)` ignores those annotation members; where an 
attribute is visible on a
+shared annotation, its Javadoc states that the classic engine currently 
ignores it. The one
+exception is [dynamic URLs and base 
overrides](#dynamic-urls-and-base-overrides) (`@Url` /
+`baseUrl()`), which the classic engine also honors, with the same SSRF 
guardrail.
 :::
 
 ```java
 // Next-gen proxy — honors everything in this section.
 MyProxy proxy = client.remote(MyProxy.class);
 
-// Classic proxy — unchanged; ignores the next-gen-only members below.
+// Classic proxy — also honors @Url / baseUrl() (see below); ignores the other 
next-gen-only
+// members in this section.
 MyProxy classic = client.getRemote(MyProxy.class, "https://api.example.com";);
 ```
 
@@ -368,6 +371,13 @@ declared type matches and the status is `>=400` does the 
generic exception fire.
 
 ### Dynamic URLs and Base Overrides
 
+:::note Honored by both engines
+Unlike the other declarative features on this page, `@Url` and `baseUrl()` are 
honored by **both**
+the next-gen engine (`RestClient.remote(...)`) and the classic engine 
(`RestClient.getRemote(...)`).
+The classic engine resolves the same precedence order and enforces the same 
SSRF guardrail
+described below.
+:::
+
 Two complementary ways to point a call at a runtime- or annotation-chosen 
endpoint:
 
 - **`@Url` parameter** (`org.apache.juneau.http.Url`) — the Retrofit `@Url` 
pattern. The argument's
@@ -398,8 +408,24 @@ public interface MyApi {
 ```
 
 :::warning SSRF guardrail
-Only `http` / `https` schemes are permitted on a URL/base override. Other 
schemes (`file:`,
-`gopher:`, `jar:`, …) are rejected with a clear error.
+Only `http` / `https` schemes are permitted on a URL/base override — other 
schemes (`file:`,
+`gopher:`, `jar:`, …) are rejected with a clear error, `allowPrivateUrls` or 
not.
+
+Beyond the scheme check, every **absolute** effective URL (an absolute `@Url` 
value, a `baseUrl()`
+override, or a default-path URL that already carries a scheme) is checked 
against a **deny-private**
+policy by default: loopback (`127.0.0.1`, `::1`), RFC1918/unique-local private 
ranges, link-local
+(including the `169.254.169.254` / `169.254.170.2` cloud-metadata addresses), 
`localhost`, and
+`*.metadata.google.internal` are all rejected. This is enforced **at connect 
time** against the
+actually-resolved address (pin-on-connect) — not just lexically against the 
hostname string — so a
+DNS name that resolves to a blocked address is rejected even if the hostname 
itself looks public, and
+a rebinding attack (blocked address swapped in after the initial check) cannot 
bypass it. Every hop of
+a redirect chain on a guarded call is re-validated the same way before it is 
followed; scheme-less
+relative `@Url` values (resolved against the client root) are not re-checked.
+
+Opt out for an intentional local-dev or intranet target via 
`allowPrivateUrls(true)` on the client
+builder, `@Remote(allowPrivateUrls=true)` on the interface, or the 
`RestClient.allowPrivateUrls`
+system property — any one of the three is sufficient (there is no per-method 
override), and `http`/
+`https` is still required either way.
 :::
 
 ### Declarative Multipart

Reply via email to