Skip to content

test(core): Add reproducer for chain-pattern literal static-receiver mismatch#150

Draft
misonijnik wants to merge 3 commits into
mainfrom
misonijnik/repro-chain-pattern
Draft

test(core): Add reproducer for chain-pattern literal static-receiver mismatch#150
misonijnik wants to merge 3 commits into
mainfrom
misonijnik/repro-chain-pattern

Conversation

@misonijnik
Copy link
Copy Markdown
Member

A multi-statement sink pattern that uses
HttpRequest.newBuilder().uri($URL) as a literal sub-expression in the let-binding $BUILDER = ...uri(...) does NOT match a chained source like

HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(t))
        .GET()
        .build();
client.send(req);

even though HttpRequest.newBuilder() is literally the receiver of .uri(...) in the source. Replacing the literal static-method receiver with $_ ($BUILDER = $_.uri($URL)) matches the same shape correctly — that's the workaround currently used in the production ssrf-sinks.yaml java-ssrf-sink block (see the Builder-chain inline forms comment). The literal form ought to match the same source but doesn't.

Stubs (issues/iChain/) mirror the JDK java.net.http.HttpRequest

  • java.net.URI + java.net.http.HttpClient API surface needed for the chain; the rule and test reference only the stub classes so the reproducer doesn't depend on JDK classpath presence.

The test fails with:
Expected [PositiveCase(className=issues.issueChain$PositiveTaint)]
to be positive, but no vulnerability was found.

(constructor call new okhttp3.Request.Builder().url(x) works as a literal in the same SSRF rule, so the mismatch is specific to a static-method call in the receiver slot of the next chained call.)

A multi-statement sink pattern that uses
`HttpRequest.newBuilder().uri($URL)` as a literal sub-expression in
the let-binding `$BUILDER = ...uri(...)` does NOT match a chained
source like

    HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create(t))
            .GET()
            .build();
    client.send(req);

even though `HttpRequest.newBuilder()` is literally the receiver of
`.uri(...)` in the source. Replacing the literal static-method
receiver with `$_` (`$BUILDER = $_.uri($URL)`) matches the same
shape correctly — that's the workaround currently used in the
production `ssrf-sinks.yaml` `java-ssrf-sink` block (see the
`Builder-chain inline forms` comment). The literal form ought to
match the same source but doesn't.

Stubs (`issues/iChain/`) mirror the JDK `java.net.http.HttpRequest`
+ `java.net.URI` + `java.net.http.HttpClient` API surface needed
for the chain; the rule and test reference only the stub classes so
the reproducer doesn't depend on JDK classpath presence.

The test fails with:
  Expected [PositiveCase(className=issues.issueChain$PositiveTaint)]
  to be positive, but no vulnerability was found.

(constructor call `new okhttp3.Request.Builder().url(x)` works as a
literal in the same SSRF rule, so the mismatch is specific to a
static-method call in the receiver slot of the next chained call.)
@misonijnik misonijnik added the bug Something isn't working label May 18, 2026
…erpart

Reframe the issueChain reproducer: the literal nested-receiver sink
(`$BUILDER = newBuilder().uri($URL)` then `$BUILDER.build()`) not
matching
the fluent chain `newBuilder().uri(...).GET().build()` is INTENDED
behaviour, not an analyzer false-negative. The calls are matched
one at a time, so the order/structure of calls in the rule has to line
up with the source; the intervening `.GET()` means
`newBuilder().uri(...)`
is never the direct receiver of `.build()`, so the nested literal
receiver never binds.

Drop the inaccurate docs that claimed a `$_.uri($URL)` form is "used in
production ssrf-sinks.yaml (Builder-chain inline forms)" — that block
does not exist in ssrf-sinks.yaml.

Add issueChainSplitBuilder: same source, but bind `newBuilder()` in its
own pattern-inside (`$NEW_BUILDER = ...newBuilder();`) and then match
`$BUILDER = $NEW_BUILDER.uri($URL)`, matching the chain call-by-call.
This fires (test passes), showing the correct way to express the intent.

The issueChain test stays enabled/red as a documented reproducer.
The issueChain sample's tainted fluent-chain case was named
PositiveTaint,
so the sample harness (SampleData/SampleBasedTest) expected a finding.
The
chain-pattern rule legitimately cannot match that shape — the nested
static
receiver never binds (see the class Javadoc) — so the `issue
chain-pattern
literal static receiver` test was deliberately failing ("red").

Reclassify it as an expected non-detection by renaming the class to
NegativeTaintIntendedNonMatch (distinct from the existing literal-only
NegativeTaint case), which the harness treats as no-finding-expected.
The
test now passes. Update the now-inaccurate "expected to be red" comments
in
issueChain.java and IssuesTest.kt to match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants