Skip to content

fix(security): restrict datasource access to the instance's own network address#41985

Open
wyattwalter wants to merge 3 commits into
releasefrom
fm/ssrf-ownip-s1
Open

fix(security): restrict datasource access to the instance's own network address#41985
wyattwalter wants to merge 3 commits into
releasefrom
fm/ssrf-ownip-s1

Conversation

@wyattwalter

@wyattwalter wyattwalter commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What

Adds a defense-in-depth SSRF protection so a user-defined datasource cannot connect
to the Appsmith instance's own network address.

Why

The SSRF host filter (RestrictedHostFilter) intentionally allows private-network
(RFC 1918 / site-local) addresses so that legitimate customer datasources on private
networks keep working. In a typical Docker/Kubernetes deployment the instance's own
routable address is itself a private/site-local address, so it falls inside that
allowed range. As a hardening measure, this change blocks that one target — the
instance's own address — without restricting the rest of the private network.

How

  • Registers the instance's own hostname(s) at startup (from the local hostname and the
    HOSTNAME environment variable) and resolves them to their current address(es),
    mirroring the existing internal-Redis host registration. Resolution is live per check,
    so an instance whose address changes stays covered with no cached-address bookkeeping.
  • Blocks a datasource whose target is a registered own hostname, or resolves to one of
    the instance's own addresses. Enforced consistently across the datasource egress paths
    — WebClient (REST/GraphQL/SaaS), Elasticsearch, Redis, and datasource validation —
    evaluating the address already resolved by each path, so no additional lookup of the
    user-supplied host is introduced.
  • Only the instance's own address(es) are affected; other private-network datasources
    continue to work unchanged.
  • Coverage is best-effort for the address(es) the registered hostname(s) resolve to
    (the primary, hostname-mapped address), not a full network-interface enumeration.

Testing

  • Unit tests cover rejection of the own address through each enforcement entry point
    (raw IPv4 literal, IPv4-mapped IPv6, and hostname), confirm other private-network
    addresses stay allowed, and confirm the existing SSRF kill-switch still applies.
    Own-address resolution is injected in tests, so the suite has no external DNS
    dependency.
  • appsmith-interfaces module tests pass.

Summary by CodeRabbit

  • Security Enhancements
    • Strengthened SSRF protection by blocking outbound requests that target the application’s own hostname or any resolved IP addresses associated with it.
    • Expanded detection to cover multiple address representations (including IPv4 literals and IPv4-mapped IPv6).
    • Added startup registration of the instance’s own hostname(s) and environment hostname with graceful fallback if resolution fails.
    • Preserved the existing allow/deny behavior for other private network targets and the SSRF bypass setting.
  • Tests
    • Added deterministic test coverage for own-host blocking, caching behavior, and SSRF bypass across all relevant enforcement paths.

Warning

Tests have not run on the HEAD 1d3d64d yet


Mon, 13 Jul 2026 19:13:41 UTC

wyattwalter and others added 2 commits July 13, 2026 15:42
…address (SSRF defense-in-depth)

The SSRF host filter (RestrictedHostFilter) intentionally allows RFC 1918 /
site-local addresses so that legitimate customer datasources on private
networks keep working. The Appsmith instance's own routable address is always
in that range (Docker bridge 172.17.x, k8s pod 10.x, etc.), so a user-defined
datasource could point at the instance itself.

Add a defense-in-depth own-host block that mirrors the existing internal-Redis
mechanism: register the instance's own hostname(s) at startup (from
InetAddress.getLocalHost() and the HOSTNAME env var), resolve them live on
every check, and block any datasource whose host is the instance's own
hostname or resolves to its own IP. This blocks the instance's own address
whether reached by the container hostname, the raw own IP, or the IPv4-mapped
IPv6 form, while leaving the rest of the private network reachable.

Honors the APPSMITH_DISABLE_SSRF_FILTER kill-switch and the test allowlist like
the existing paths. Wired into RedisConfig at startup alongside the
internal-Redis registration. Adds RestrictedHostFilterTest coverage for the
own-IP block (raw IPv4, IPv4-mapped IPv6, hostname), the no-over-block
guarantee for other RFC 1918 addresses, and the kill-switch.

Co-Authored-By: Claude <noreply@anthropic.com>
…search resolver paths

Follow-up hardening for the own-host/own-IP defense-in-depth block. The initial
change enforced it only on the datasource-save (isHostBlocked) and Redis
(firstAllowedRedisAddress) paths; the REST/GraphQL/SaaS (WebClient) and
Elasticsearch datasource requests resolve through isLiteralBlocked and
isDisallowedAndFail, which did not consult the own-host set, so those datasources
could still reach the instance's own routable address.

Changes:
- isLiteralBlocked now blocks a URL that literally names the instance's own host
  (pre-resolver fast path, no DNS), matching how it already handles internal Redis.
- isDisallowedAndFail — the shared address-level policy for both the WebClient
  (Netty) and Elasticsearch resolver hooks — now applies the own-host literal and
  own-IP overlap via a shared matchesOwnHost helper, working on the already-resolved
  address the hook hands it (no user-host re-resolution, so no DNS-rebinding TOCTOU).
- Comments corrected to state coverage is best-effort for the address(es) the
  registered own hostname(s) resolve to, not full network-interface enumeration;
  a multi-homed container's secondary-interface IPs are out of scope by design.
- Tests: added a test-only seam (setOwnResolvedIpsForTesting) so own-IP overlap is
  exercised deterministically without live DNS, removing the previous public-DNS
  dependency. Added coverage proving a resolved own IP is rejected via isLiteralBlocked,
  isDisallowedAndFail, and firstAllowedRedisAddress — not only isHostBlocked.

Co-Authored-By: Claude <noreply@anthropic.com>
@wyattwalter

Copy link
Copy Markdown
Contributor Author

/build-deploy-preview

@github-actions

Copy link
Copy Markdown

Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29275340682.
Workflow: On demand build Docker image and deploy preview.
skip-tests: . env: .
PR: 41985.
recreate: .
base-image-tag: .

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

RestrictedHostFilter registers the application’s own hostnames and resolved IPs, blocks SSRF targets that resolve back to the instance, wires registration into startup, and adds deterministic coverage across filtering entry points.

Changes

Own-host SSRF protection

Layer / File(s) Summary
Own-host state and resolution
app/server/appsmith-interfaces/src/main/java/com/appsmith/util/RestrictedHostFilter.java
Adds registered own-host state, local hostname discovery, runtime registration, cached IP resolution, and test-only overrides.
Startup hostname registration
app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/RedisConfig.java
Registers the local hostname and HOSTNAME environment value during post-construction startup.
Own-host enforcement and validation
app/server/appsmith-interfaces/src/main/java/com/appsmith/util/RestrictedHostFilter.java, app/server/appsmith-interfaces/src/test/java/com/appsmith/util/RestrictedHostFilterTest.java
Blocks own-hostname and resolved-own-IP matches across host, literal, resolver, and Redis-address paths, with cache, isolation, and kill-switch coverage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Datasource
  participant RestrictedHostFilter
  participant DNS
  Datasource->>RestrictedHostFilter: submit target host
  RestrictedHostFilter->>DNS: resolve target
  DNS-->>RestrictedHostFilter: canonical host and addresses
  RestrictedHostFilter->>RestrictedHostFilter: compare own hostname and IP overlaps
  RestrictedHostFilter-->>Datasource: allow or block target
Loading

Possibly related PRs

  • appsmithorg/appsmith#41921: Modifies shared restricted-host and Redis denial paths that this change extends with own-instance hostname and IP blocking.

Suggested reviewers: subrata71, sharat87, salevine

Poem

Own hosts gather, names align,
Private roads now draw a line.
DNS whispers, filters know,
Self-bound targets cannot go.
Safe paths bloom in every flow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the security change to block access to the instance's own network address.
Description check ✅ Passed It covers the change, motivation, implementation, and testing, though it omits the issue link and template sections like Automation and Communication.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fm/ssrf-ownip-s1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hacktron-app hacktron-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Severity Count
MEDIUM 1

View full scan results

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/server/appsmith-interfaces/src/main/java/com/appsmith/util/RestrictedHostFilter.java (1)

663-682: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Avoid live DNS on the resolver path here. matchesOwnHost(canonicalHost) calls resolveOwnIps(), which does InetAddress.getAllByName(...) on every resolver check (pre-DNS and again for each resolved address in doResolveAll). Cache the own-IP set outside this path, or resolve it once per request and reuse it; a slow or unresolvable HOSTNAME can block outbound requests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-interfaces/src/main/java/com/appsmith/util/RestrictedHostFilter.java`
around lines 663 - 682, The resolver path in isDisallowedAndFail must not
perform live DNS through matchesOwnHost on every check. Cache the own-IP set
outside this method, or resolve it once per request and reuse it across pre-DNS
and resolved-address checks, while preserving the existing disallowed-host and
blocked-IP checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@app/server/appsmith-interfaces/src/main/java/com/appsmith/util/RestrictedHostFilter.java`:
- Around line 663-682: The resolver path in isDisallowedAndFail must not perform
live DNS through matchesOwnHost on every check. Cache the own-IP set outside
this method, or resolve it once per request and reuse it across pre-DNS and
resolved-address checks, while preserving the existing disallowed-host and
blocked-IP checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f53cd626-2a38-4142-8d3e-3cde6b6c1300

📥 Commits

Reviewing files that changed from the base of the PR and between 315b36c and f5ecb5b.

📒 Files selected for processing (3)
  • app/server/appsmith-interfaces/src/main/java/com/appsmith/util/RestrictedHostFilter.java
  • app/server/appsmith-interfaces/src/test/java/com/appsmith/util/RestrictedHostFilterTest.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/RedisConfig.java

…er check does no DNS

The own-IP overlap check runs inside isDisallowedAndFail, which the WebClient
(Netty) and Elasticsearch resolver hooks invoke on their reactive EventLoop
threads. The previous implementation resolved the instance's own hostname(s)
live on every check (matchesOwnHost -> resolveOwnIps -> InetAddress.getAllByName),
putting a synchronous DNS lookup on that hot path.

Resolve the own hostname(s) to IPs once, off the hot path, and cache them in a
volatile ownResolvedIps set: at static init and whenever registerOwnHost runs
(the server calls that at startup, before any datasource is served). The
enforcement checks — matchesOwnHost on the resolver EventLoop and
isAnyResolvedAddressBlocked on the datasource-save / Redis paths — are now pure
set-membership with no DNS. The own IP is effectively static for the process
lifetime (a re-IP means a restart, which re-seeds the cache), so a startup
resolve is sufficient and no periodic refresh is needed.

Internal-Redis resolution is unchanged — it runs only on blocking-tolerant
paths, never the EventLoop. The setOwnResolvedIpsForTesting seam now seeds the
cache directly. Added tests proving the hot path reads the cache and performs no
resolution, and that registerOwnHost recomputes the cache off the hot path.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Deploy-Preview-URL: https://ce-41985.dp.appsmith.com

@wyattwalter

Copy link
Copy Markdown
Contributor Author

/build-deploy-preview

@github-actions

Copy link
Copy Markdown

Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29277637331.
Workflow: On demand build Docker image and deploy preview.
skip-tests: . env: .
PR: 41985.
recreate: .
base-image-tag: .

@github-actions

Copy link
Copy Markdown

Deploy-Preview-URL: https://ce-41985.dp.appsmith.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant