Skip to content

fix: use hostname for SMTP TLS verification while pinning the connection to the SSRF-validated address - #42060

Merged
sebastianiv21 merged 3 commits into
releasefrom
feature/app-15713
Jul 31, 2026
Merged

fix: use hostname for SMTP TLS verification while pinning the connection to the SSRF-validated address#42060
sebastianiv21 merged 3 commits into
releasefrom
feature/app-15713

Conversation

@sebastianiv21

@sebastianiv21 sebastianiv21 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Problem (APP-15713 / #42024): SMTP test emails from Admin Settings fail whenever TLS is enabled — STARTTLS on 587/2525 and implicit SSL on 465 — with:

jakarta.mail.MessagingException: Could not convert socket to TLS
javax.net.ssl.SSLHandshakeException: (certificate_unknown) No subject alternative names matching IP address

Plaintext SMTP (e.g. 2525 with TLS off) works, which matches the reporter's port matrix exactly.

Root cause: the SSRF fix for GHSA-vvxf-f8q9-86gh (#41666) made EnvManagerCEImpl.sendTestEmail connect to the DNS-resolved IP (setHost(resolvedAddress.getHostAddress())) to close a DNS-rebinding TOCTOU. But JavaMail performs SNI and certificate identity checking against the connect host, and server certificates carry hostnames, not IPs — so every TLS handshake fails.

Fix: buildMailSender now sets the sender host to the user-entered hostname (restoring correct SNI + certificate identity checking) while pinning the TCP connection to the RestrictedHostFilter.resolveIfAllowed-validated address via a custom mail.smtp.socketFactory. Angus Mail asks the factory for an unconnected socket and connects it to (host, port) itself; the pinned socket ignores the re-resolved endpoint address and connects to the validated one. Both security properties of the original fix are preserved:

  • DNS-rebinding TOCTOU stays closed on the plain/STARTTLS paths (25/587/2525): the validated address is the one connected to; the hostname is never re-resolved into something the filter didn't check.
  • Port 465 (implicit SSL) is pinned too: with no mail.smtp.ssl.socketFactory configured, Angus falls back to the plain mail.smtp.socketFactory for the TCP connect and layers TLS over the already-connected socket (verified against Angus Mail 2.0.5 SocketFetcher bytecode). If that fallback ever stops applying, the path still fails closed — it is handshake-first with mail.smtp.ssl.checkserveridentity on by default, so a rebound DNS answer dies at certificate verification before any SMTP dialogue.
  • mail.smtp.socketFactory.fallback=false is set explicitly so a factory failure can never silently fall back to an unpinned socket.

The superuser-only authorization, port allowlist, and RestrictedHostFilter validation are unchanged and still run before any socket is built.

Tests: 6 new unit tests in EnvManagerTest — hostname-based host for TLS (fails on the old IP-connecting code), 465 implicit-SSL props, STARTTLS-disabled props, auth on/off props, and socket-factory pinning (a factory socket pointed at an unroutable TEST-NET endpoint must land on the validated listener, for both the unconnected-then-connect path JavaMail uses and the pre-connected overloads). mvn test -Dtest=EnvManagerTest: 29/29 pass.

Maintainer note: the pinning relies on Angus Mail's socket-factory calling convention (documented in the buildMailSender javadoc). When upgrading the mail library, re-verify that SocketFetcher still consults mail.smtp.socketFactory and connects the factory's unconnected socket itself.

Manual verification

Verified on a live deploy preview of this PR via Admin Settings → Email → "Send test email", against real providers:

Case Provider(s) Result
587 + TLS on (STARTTLS) — the reported repro Mailtrap sandbox, Brevo ✅ succeeds, email delivered
465 + TLS on (implicit SSL) Brevo ✅ succeeds, email delivered
2525 + TLS off (plaintext, no-regression check) Mailtrap sandbox ✅ succeeds
Host 169.254.169.254 (SSRF guard) ✅ still rejected with the generic "Invalid SMTP configuration" (filter fires before any socket is opened)

Additionally verified off-DP by driving the real Angus Mail stack through buildMailSender against live smtp.gmail.com: the 587-STARTTLS and 465-implicit-SSL handshakes succeed with this fix, and rebuilding the sender the pre-fix way (host = resolved IP) reproduces the exact reported failure (Could not convert socket to TLS) — same machine, same server, hostname-vs-IP being the only variable.

Note for anyone re-testing: Mailtrap's sandbox does not speak implicit TLS on port 465 (it greets in plaintext on that port), so 465 must be verified with a provider that does, e.g. Brevo or Gmail. That failure mode is a provider quirk, not this code path.

Fixes #42024
Linear: https://linear.app/appsmith/issue/APP-15713

Automation

/ok-to-test tags="@tag.All"

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved SMTP test-email behavior to use the SSRF-validated destination while keeping the original SMTP hostname for TLS/SNI and certificate hostname checks.
    • Centralized mail-sender configuration (TLS/SSL, StartTLS, auth, and timeouts) for consistent, correct settings.
    • Disabled socket-factory fallback to reduce risk of bypassing address validation.
  • Tests

    • Added unit tests covering hostname vs resolved IP handling, port 465 SSL behavior, StartTLS-disabled plaintext mode, conditional auth properties, and pinned socket connections to the validated address.

Tip

🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/30640681435
Commit: d5301eb
Cypress dashboard.
Tags: @tag.All
Spec:


Fri, 31 Jul 2026 16:05:29 UTC

…ted address

The SSRF fix for GHSA-vvxf-f8q9-86gh made sendTestEmail connect to the
DNS-resolved IP. TLS certificate identity checks run against the connect
host, and server certificates carry hostnames, so every TLS mode failed
with "SSLHandshakeException: No subject alternative names matching IP
address" while plaintext SMTP kept working (APP-15713).

buildMailSender now sets the sender host to the user-entered hostname —
restoring correct SNI and certificate identity checking — while pinning
the TCP connection to the RestrictedHostFilter-validated address through
a custom mail.smtp.socketFactory. Angus Mail asks the factory for an
unconnected socket and connects it to (host, port) itself; the pinned
socket ignores the re-resolved endpoint address, so the DNS-rebinding
TOCTOU the security fix closed stays closed. The implicit-SSL path on
port 465 is pinned via Angus's plain-socketFactory fallback, and fails
closed (handshake-first identity check against the hostname) should that
fallback ever stop applying. mail.smtp.socketFactory.fallback is set to
false explicitly so a factory failure can never silently unpin.

Fixes #42024
Linear: https://linear.app/appsmith/issue/APP-15713

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 28, 2026

Copy link
Copy Markdown

APP-15713

@sebastianiv21 sebastianiv21 added the ok-to-test Required label for CI label Jul 28, 2026
@github-actions github-actions Bot added Bug Something isn't working Needs Triaging Needs attention from maintainers to triage labels Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

SMTP test-email sender construction now preserves the configured hostname for TLS while pinning connections to the SSRF-validated address. sendTestEmail uses the shared builder, with tests covering TLS, StartTLS, authentication, plaintext, hostname handling, and socket routing.

Changes

SMTP connection pinning

Layer / File(s) Summary
Pinned SMTP sender construction
app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java
buildMailSender centralizes SMTP configuration, preserves the configured hostname, and pins socket connections to the validated address without fallback.
Test-mail integration and validation
app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java, app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/EnvManagerTest.java
sendTestEmail uses the shared builder, with tests covering TLS, StartTLS, authentication, hostname handling, and socket pinning.

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

Sequence Diagram(s)

sequenceDiagram
  participant sendTestEmail
  participant RestrictedHostFilter
  participant buildMailSender
  participant JavaMailSenderImpl
  sendTestEmail->>RestrictedHostFilter: resolveIfAllowed SMTP hostname
  RestrictedHostFilter-->>sendTestEmail: return validated address
  sendTestEmail->>buildMailSender: provide request and resolved address
  buildMailSender->>JavaMailSenderImpl: configure hostname and pinned socket factory
Loading

Possibly related PRs

Suggested reviewers: subrata71

Poem

Hostnames stay for TLS’s call,
Pinned sockets guard the wall.
StartTLS and auth align,
Validated routes hold the line.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 primary SMTP TLS hostname and SSRF connection-pinning fix.
Description check ✅ Passed The description covers the problem, root cause, fix, testing, manual verification, issue links, and automation details.
✨ 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 feature/app-15713

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.

@sebastianiv21

Copy link
Copy Markdown
Contributor Author

/build-deploy-preview skip-tests=true

@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

@sebastianiv21
sebastianiv21 requested a review from subrata71 July 28, 2026 13:32
@sebastianiv21
sebastianiv21 marked this pull request as ready for review July 28, 2026 20:31
@sebastianiv21
sebastianiv21 requested a review from a team as a code owner July 28, 2026 20:31

@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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java (2)

844-859: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Only a read timeout is set — no connect timeout.

mail.smtp.timeout bounds the read timeout, but mail.smtp.connectiontimeout (and mail.smtp.writetimeout) aren't set. If the pinned address is reachable but silently drops packets (firewalled host, blackholed route), the connect phase can block far longer than 7s, tying up the calling thread on this synchronous testConnection()/send() path.

♻️ Proposed fix
         props.put("mail.smtp.timeout", 7000); // 7 seconds
+        props.put("mail.smtp.connectiontimeout", 7000); // 7 seconds
+        props.put("mail.smtp.writetimeout", 7000); // 7 seconds
🤖 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-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java`
around lines 844 - 859, Update the SMTP properties setup in the EnvManagerCEImpl
mail configuration to set connection and write timeouts alongside the existing
mail.smtp.timeout, using the same 7-second bound. Preserve the current protocol,
TLS, socket-factory, and fallback settings.

846-849: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Security Misconfiguration (CWE-297)

Reachability: Internal

Set mail.smtp.ssl.checkserveridentity explicitly.

This path relies on TLS server-ID validation, but the fallback comment still describes the library default as false. Explicitly setting it to "true" removes reliance on a default that changed between JavaMail/Jakarta Mail and Angus Mail.

🤖 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-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java`
around lines 846 - 849, Update the SMTP properties setup near
pinnedSocketFactory to explicitly set mail.smtp.ssl.checkserveridentity to
"true", alongside the existing socketFactory and fallback properties, ensuring
TLS server identity validation does not depend on library defaults.
🤖 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.

Inline comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java`:
- Around line 861-868: Remove the unconditional `props.put("mail.debug",
"true")` from the mail configuration in `EnvManagerCEImpl`; keep SMTP debug
logging disabled by default so `sendTestEmail` cannot emit authentication
credentials, while preserving the existing authentication property setup.

---

Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java`:
- Around line 844-859: Update the SMTP properties setup in the EnvManagerCEImpl
mail configuration to set connection and write timeouts alongside the existing
mail.smtp.timeout, using the same 7-second bound. Preserve the current protocol,
TLS, socket-factory, and fallback settings.
- Around line 846-849: Update the SMTP properties setup near pinnedSocketFactory
to explicitly set mail.smtp.ssl.checkserveridentity to "true", alongside the
existing socketFactory and fallback properties, ensuring TLS server identity
validation does not depend on library defaults.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 97d394f9-cde8-4a06-935f-127eced54dd8

📥 Commits

Reviewing files that changed from the base of the PR and between 3bf3f67 and 299021a.

📒 Files selected for processing (2)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/EnvManagerTest.java

…d mail.debug

Per CodeRabbit review:
- Set mail.smtp.connectiontimeout and mail.smtp.writetimeout (7s) so a
  blackholed connect can't hold the calling thread indefinitely (the
  existing mail.smtp.timeout only bounds reads).
- Set mail.smtp.ssl.checkserveridentity=true explicitly instead of
  relying on the library default, which differed across JavaMail/Angus
  Mail generations.
- Gate mail.debug on the logger's DEBUG level instead of hardcoding
  true. Note: Angus already suppresses the AUTH exchange in the debug
  trace by default (mail.debug.auth=false), so this gates transcript
  noise (banners, recipient, username), not the password.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sebastianiv21

Copy link
Copy Markdown
Contributor Author

/build-deploy-preview skip-tests=true

@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

@sebastianiv21
sebastianiv21 merged commit da2e280 into release Jul 31, 2026
84 checks passed
@sebastianiv21
sebastianiv21 deleted the feature/app-15713 branch July 31, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working Needs Triaging Needs attention from maintainers to triage ok-to-test Required label for CI Ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: SMTP works only when TLS is disabled.

2 participants