Skip to content

fix(security): fail closed when APPSMITH_BASE_URL unset for token-bearing emails (GHSA-j9gf-vw2f-9hrw)#41766

Closed
subrata71 wants to merge 7 commits intoreleasefrom
fix/origin-validation-email-links-ghsa-j9gf
Closed

fix(security): fail closed when APPSMITH_BASE_URL unset for token-bearing emails (GHSA-j9gf-vw2f-9hrw)#41766
subrata71 wants to merge 7 commits intoreleasefrom
fix/origin-validation-email-links-ghsa-j9gf

Conversation

@subrata71
Copy link
Copy Markdown
Collaborator

@subrata71 subrata71 commented Apr 28, 2026

Description

fix(security): fail closed when APPSMITH_BASE_URL unset for token-bearing emails (GHSA-j9gf-vw2f-9hrw)

  • Primary fix: Extract a single canonical SecureBaseUrlResolver helper that gates the host portion of every emailed absolute link (forgot-password, email verification, workspace invite, instance-admin invite). The resolver no longer trusts the request Origin header as the canonical host when APPSMITH_BASE_URL is unset — it returns Mono.empty() instead, which propagates through the existing controller wiring (defaultIfEmpty(true) / thenReturn) so unauthenticated flows return the same generic 200 success without dispatching email (anti-enumeration preserved). Auth-gated invite flows surface the new MISCONFIGURED_INSTANCE_BASE_URL error.
  • Migration path: New opt-in env var APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS (defaults to false). When set to true, the legacy behaviour is restored. Documented as deprecated; intended only as a transition window for self-hosted deployments that have not yet configured APPSMITH_BASE_URL. The helper logs a clear WARN on every call so operators are aware they are running in an insecure mode.
  • Defense-in-depth: Re-routes the workspace-invite and instance-admin-invite flows in EmailServiceCEImpl through the same helper. Previously these flows derived the invite URL host directly from originHeader, which was an auth-gated but identical attack surface — and was a latent bug in EE multi-org mode (where forgot-password used a server-controlled URL but invite emails still used Origin).
  • Test coverage: New SecureBaseUrlResolverCEImplTest (7 tests, pure unit tests, no Spring context) pinning all four state permutations of the resolver. Existing EmailServiceCEImplTest updated with @TestPropertySource(properties = "APPSMITH_BASE_URL=http://example.com") so its invite/admin-invite tests exercise the strict-match path.
  • Strict-mode unchanged: the protection added in PR fix(auth): validate Origin header against APPSMITH_BASE_URL #41426 (Origin must equal APPSMITH_BASE_URL when configured) is preserved exactly. The new compat flag does NOT weaken it — it only governs the unconfigured case.

Vulnerability

Field Value
GHSA GHSA-j9gf-vw2f-9hrw
CVE Not yet assigned
CVSS 8.1 (High) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CWE CWE-346 (Origin Validation Error), CWE-807 (Reliance on Untrusted Inputs in a Security Decision)
Affected component Token-bearing email link generation (forgot-password, email verification, invite, instance-admin invite)

Exposure Analysis

Who can exploit this? Any unauthenticated network attacker who can reach POST /api/v1/users/forgotPassword or POST /api/v1/users/resendEmailVerification. No prior authentication, no recon.

What can an attacker achieve? When APPSMITH_BASE_URL is unset (the default state for self-hosted installs that haven't been hardened), an attacker can forge the Origin header to make Appsmith send password-reset and email-verification emails whose clickable host is attacker-controlled. The emails are otherwise legitimate (sent from the real Appsmith server, signed with SPF/DKIM, rendered with the real Appsmith branding). A victim who clicks the link delivers a valid token to the attacker's host — the attacker can then replay it against the legitimate Appsmith instance via PUT /api/v1/users/resetPassword to take over the account.

Blast radius: Per-user. Each successful exploit takes over one targeted account. There is no scope change to the wider workspace from this primitive alone, but a successful takeover of a workspace admin or instance admin escalates from there through normal application flows.

Affected configurations:

  • Self-hosted CE/EE single-org with APPSMITH_BASE_URL unset and outbound email enabled — vulnerable.
  • EE multi-org — already safe via the previous OrganizationService.getOrganizationBaseUrl() override that derived the host from slug + deploymentDomain. This PR relocates that override into the new helper so it now also covers invite flows (which were a latent bug under multi-org).
  • Cloud-hosted Appsmith — has APPSMITH_BASE_URL configured, so the strict-mode check from PR fix(auth): validate Origin header against APPSMITH_BASE_URL #41426 applies. Not affected.

Evidence of exploitation in the wild: None known. Reported responsibly via GHSA by an external researcher.

Fix

Root cause. UserServiceCEImpl#resolveSecureBaseUrl failed open when APPSMITH_BASE_URL was empty: it returned the caller-supplied value verbatim. That caller-supplied value originated from @RequestHeader("Origin") and was used to interpolate the host of token-bearing email links. The underlying coding pattern is using request metadata (a value the client claims) to populate an outbound, server-issued security artifact. Token-bearing artifacts must be bound to the server's configured identity, not the requester's claimed identity.

Fix strategy. Extract SecureBaseUrlResolver as a Spring @Component with a single resolver method. Make this the only path through which any emailed absolute link gets its host. The helper has three resolution branches:

  1. Configured base URL set (existing strict-mode from PR fix(auth): validate Origin header against APPSMITH_BASE_URL #41426): require exact match with the supplied value, error on mismatch.
  2. Unconfigured + new compat flag off (default): log WARN, return Mono.empty(). Unauthenticated callers (forgot-password, resend-verification) translate this into a generic 200 success without dispatching email — anti-enumeration preserved by the existing defaultIfEmpty(true).thenReturn(...) controller wiring. Authenticated callers (invite, admin-invite) translate it into MISCONFIGURED_INSTANCE_BASE_URL so the admin caller sees an actionable configuration error.
  3. Unconfigured + new compat flag on (APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true): log WARN, return the caller-supplied value. Migration-only, deprecated.

What was intentionally NOT changed:

  • All HTTP endpoint paths and request/response shapes are unchanged.
  • The strict-mode check from PR fix(auth): validate Origin header against APPSMITH_BASE_URL #41426 is preserved exactly. The new compat flag does NOT weaken it.
  • SecurityConfig permit-all matchers stay as-is — the endpoints must remain reachable without auth; the fix is in URL generation, not auth gating.
  • DTO shapes (ResetUserPasswordDTO, ResendEmailVerificationDTO) unchanged.

Defense-in-depth.

  • Single canonical helper across all four token-bearing email flows, including the auth-gated invite flows that previously bypassed resolveSecureBaseUrl entirely.
  • Helper unit tests pin every branch including a regression that the compat flag does not weaken strict-mode.
  • WARN log on every fail-closed and fail-back call gives operators an immediate signal when emails aren't being dispatched and why.

Reporter coordination. This fix matches the design the reporter recommended in the GHSA discussion thread, point-for-point: single server-side helper, fail-closed by default, anti-enumeration preserved for unauth flows, opt-in insecure compatibility flag for migration. Reporter is @0xmrma.

CE/EE sync

Mostly CE-only safe with a small shadow EE PR.

File Diff status (CE vs EE pre-sync) Strategy
UserServiceCEImpl.java Identical CE edit syncs cleanly to EE
EmailServiceCEImpl.java Identical CE edit syncs cleanly to EE
EmailServiceCEImplTest.java Identical CE edit syncs cleanly to EE
UserServiceCECompatibleImpl.java Identical CE edit syncs cleanly to EE
SecureBaseUrlResolver.java, SecureBaseUrlResolverCE.java, SecureBaseUrlResolverCEImpl.java (new) n/a New files sync to EE
SecureBaseUrlResolverImpl.java (new) CE no-op variant; EE replaces with multi-org-aware variant Shadow EE PR layered on top
UserServiceImpl.java EE-specific class with multi-org override Shadow EE PR strips the now-redundant resolveSecureBaseUrl override and updates the constructor signature
EmailServiceImpl.java EE-specific class Shadow EE PR updates the constructor to inject SecureBaseUrlResolver
AppsmithError.java / AppsmithErrorCode.java Differs (EE has additional codes) Shadow EE PR adds the same MISCONFIGURED_INSTANCE_BASE_URL entry

Shadow EE branch (fix/origin-validation-email-links-ghsa-j9gf on appsmithorg/appsmith-ee) is prepared and pushed; will be linked in a follow-up comment.

Disclosure

Do not merge until advisory is ready for disclosure coordination.

After merge:

  1. Confirm fix is in release branch
  2. Coordinate with security team on disclosure timeline
  3. Update advisory with patched version and publish
  4. Notify reporter (@0xmrma)

Automation

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

🔍 Cypress test results

Tip

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


Wed, 29 Apr 2026 20:02:20 UTC

Communication

Should the DevRel and Marketing teams inform users about this change?

  • Yes — operators of self-hosted instances that have not configured APPSMITH_BASE_URL will see password-reset / email-verification / invite emails stop being delivered after upgrading. Migration path: set APPSMITH_BASE_URL, or as a temporary opt-in, set APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true (insecure, deprecated).
  • No

Follow-ups

  • Admin UI tooltip update in app/client/src/ce/pages/AdminSettings/config/configuration.tsx strengthening the wording around APPSMITH_BASE_URL being required (deferred — local lint-staged requires a fresh yarn install in app/client; will land as a separate small PR).
  • Plan to deprecate APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS env var in a future major release once telemetry confirms self-hosted operators have migrated.
  • Add Spring-context integration tests in UserServiceTest for forgotPasswordTokenGenerate / resendEmailVerification fail-closed paths (deferred — needs Mongo+Redis; helper unit tests already pin the security-critical semantics).

Summary by CodeRabbit

  • Bug Fixes

    • Enforced use of a trusted instance base URL for token-bearing email flows (password reset, verification, workspace invites). Added a clear internal error when the base URL is not configured; invite/email flows are disabled until set. Origin matching is stricter to prevent spoofing.
  • Tests

    • Added comprehensive unit tests covering secure base URL resolution and compatibility modes.
  • Documentation / CI

    • Updated CI, deploy and local scripts and added design docs to ensure the base URL is set during tests and previews.

…f-vw2f-9hrw)

These tests pin the fail-closed semantics required by the security advisory:

- When APPSMITH_BASE_URL is unset and the new APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS
  compatibility flag is OFF (the default), the resolver returns Mono.empty() — it does
  NOT trust the request-supplied Origin header as the host of token-bearing email links.
- When the compatibility flag is ON (opt-in migration window), legacy behaviour is
  restored.
- Once APPSMITH_BASE_URL is configured, strict-mode validation rejects mismatched Origin
  values; the compat flag does NOT weaken this branch.

Tests fail at compile time because the helper class is introduced in the next commit.

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
…ring emails (GHSA-j9gf-vw2f-9hrw)

Extract a single canonical SecureBaseUrlResolver that gates the host portion of
every emailed absolute link (forgot-password, email verification, workspace
invite, instance-admin invite). The resolver MUST NOT trust request-supplied
values such as the Origin header as the canonical host.

New default behaviour when APPSMITH_BASE_URL is unset:

- Forgot-password and resend-verification (unauthenticated): the resolver
  returns Mono.empty() which propagates through the existing controller wiring
  (defaultIfEmpty(true) / thenReturn) — clients receive the same generic 200
  success response and no email is dispatched. Anti-enumeration is preserved.
- Workspace invite and instance-admin invite (authenticated): the resolver
  returns Mono.empty() which the email-service callers translate into the new
  MISCONFIGURED_INSTANCE_BASE_URL error (HTTP 500, AE-APP-5045) so the admin
  caller sees an actionable configuration message.

Migration path for self-hosted deployments that have not yet set
APPSMITH_BASE_URL: opt into the new APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS
env var (defaults to false). The legacy behaviour is then restored, but the
helper logs a WARN on every call so operators see they are running in an
insecure mode. Documented as deprecated; intended only as a transition window.

The strict-mode protection from PR #41426 (Origin must equal APPSMITH_BASE_URL
when configured) is preserved — and the new compat flag does NOT weaken it.

CE/EE: SecureBaseUrlResolverImpl is annotated @component on the CE side and
extends SecureBaseUrlResolverCEImpl. EE replaces the Impl class with a
multi-org-aware variant in its parallel branch (shadow EE PR).

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
Linear: APP-15046
@linear
Copy link
Copy Markdown

linear Bot commented Apr 28, 2026

@subrata71 subrata71 added Security Issues related to information security within the product ok-to-test Required label for CI labels Apr 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a secure base-URL resolution subsystem and CE implementation, wires it into email and user services, updates constructors and flows to enforce fail‑closed origin checks, adds tests, and updates CI/deploy/local scripts and docs to provide APPSMITH_BASE_URL for test/runtime parity.

Changes

Cohort / File(s) Summary
Error Definitions
app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java, app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithErrorCode.java
Added MISCONFIGURED_INSTANCE_BASE_URL enum constant and new error code AE-APP-5046.
Resolver Interfaces
app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SecureBaseUrlResolver.java, app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCE.java
Introduced marker interface and CE contract resolveSecureBaseUrl(String) with documented fail‑closed semantics and Mono-based API.
Resolver Implementations
app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java, app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SecureBaseUrlResolverImpl.java
Added CE implementation that validates origins against APPSMITH_BASE_URL, honors APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS fallback, logs warnings, returns Mono.empty() when strict mode denies resolution; provided Spring bean wrapper replaceable by EE.
Email Service Changes
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCEImpl.java, app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailServiceImpl.java
Constructors updated to accept SecureBaseUrlResolver; invite flows call resolver to derive trusted base URL and map empty resolution to MISCONFIGURED_INSTANCE_BASE_URL where appropriate.
User Service Changes
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java, app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java, app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/UserServiceCECompatibleImpl.java
Wired SecureBaseUrlResolver into constructors; CE user flows use resolver and short‑circuit (Mono.empty) when no trusted base URL is available, preserving anti‑enumeration behavior.
Tests & Test Config
app/server/appsmith-server/src/test/java/.../SecureBaseUrlResolverCEImplTest.java, app/server/appsmith-server/src/test/resources/application-test.properties
Added comprehensive unit tests covering configured/unconfigured/mismatch/compatibility cases; test properties enable insecure compatibility mode for tests.
CI / Deploy / Local Scripts & Docs
.github/workflows/ci-test-*.yml (multiple), scripts/deploy_preview.sh, scripts/local_testing.sh, docs/superpowers/specs/2026-04-28-ci-base-url-config-design.md
Injected APPSMITH_BASE_URL=http://localhost into CI/test container runs; deploy preview script and docs set applicationConfig.APPSMITH_BASE_URL for previews; local testing script updated accordingly.
Cypress Tests
app/client/cypress/support/commands.js
Updated test intercepts to send real Origin from Cypress.config("baseUrl") instead of literal values to match stricter server origin checks.

Sequence Diagram

sequenceDiagram
    participant Client
    participant EmailService as EmailServiceCEImpl
    participant Resolver as SecureBaseUrlResolver
    participant Config as APPSMITH_BASE_URL
    participant Logger as Logger

    Client->>EmailService: start invite flow (originHeader)
    EmailService->>Resolver: resolveSecureBaseUrl(originHeader)
    alt APPSMITH_BASE_URL is set
        Resolver->>Config: read APPSMITH_BASE_URL
        alt originHeader == APPSMITH_BASE_URL
            Resolver-->>EmailService: trustedBaseUrl
            EmailService->>EmailService: build invite URL with trustedBaseUrl
            EmailService-->>Client: send email
        else originHeader != APPSMITH_BASE_URL
            Resolver-->>Logger: emit AppsmithException (GENERIC_BAD_REQUEST)
            Logger-->>Client: reject / fail
        end
    else APPSMITH_BASE_URL unset
        alt APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS = true
            Resolver-->>EmailService: emit originHeader (warn)
            EmailService->>EmailService: build invite URL with originHeader
            EmailService-->>Client: send email
        else
            Resolver-->>Logger: warn & return empty
            Logger-->>Client: no email sent (silent success)
        end
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🔐 A base URL stands on guard tonight,
Flags flicker, parsers trim the light,
Resolvers match or gently close the door,
Invites launch only when origins are sure,
CI and tests nod — secure flows soar. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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
Title check ✅ Passed The PR title accurately summarizes the main security fix: implementing fail-closed behavior when APPSMITH_BASE_URL is unset for token-bearing emails.
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.
Description check ✅ Passed PR description is comprehensive, well-structured, and covers all required sections: detailed motivation, vulnerability analysis, fix strategy, test coverage, CE/EE sync notes, and disclosure instructions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/origin-validation-email-links-ghsa-j9gf

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 and usage tips.

@subrata71
Copy link
Copy Markdown
Collaborator Author

Shadow EE PR: https://github.com/appsmithorg/appsmith-ee/pull/8997 (pre-staged for sync resolution)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java (1)

197-207: Add a boundary test for the empty-completion path.

These branches now rely on Mono.empty() as the security-critical “generic success, no email sent” outcome. I’d add a service/controller-level assertion for that path so future refactors don’t accidentally turn it into a different HTTP response or dispatch emails again.

Also applies to: 843-852

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java`
around lines 197 - 207, Add a boundary test that asserts the empty-completion
path from secureBaseUrlResolver.resolveSecureBaseUrl(...) produces the generic
success response and does not send an email: mock secureBaseUrlResolver to
return Mono.empty() for resetUserPasswordDTO.getBaseUrl(), call the public entry
point that uses processForgotPasswordTokenGeneration (the service or controller
method that consumes resetUserPasswordDTO), and assert the HTTP/Service response
equals the generic success value and that the email-sending component was never
invoked; repeat the same kind of test for the other occurrence around
processForgotPasswordTokenGeneration referenced in the second region so future
refactors cannot convert Mono.empty() into a different response or trigger email
dispatch.
app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java (1)

69-72: Consider rate-limiting this warning.

This branch is reachable from unauthenticated flows, so a misconfigured instance can be driven into unbounded WARN spam. A rate-limited log or startup health signal would be safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java`
around lines 69 - 72, In SecureBaseUrlResolverCEImpl, avoid unbounded warn spam
from the log.warn(...) branch that returns Mono.empty(); implement rate-limiting
so the warning is emitted at most once per configured interval (e.g., once per
hour). For example, add a static/instance time-based guard (AtomicLong
lastWarnAt or a Guava RateLimiter) checked before calling log.warn(...) and
update the timestamp when the warn is emitted; keep the method behavior
returning Mono.empty() unchanged. Ensure the guard is thread-safe and
configurable, and document the interval constant near the class-level fields so
future maintainers can adjust it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithErrorCode.java`:
- Line 67: In AppsmithErrorCode, MISCONFIGURED_INSTANCE_BASE_URL currently
reuses the code string "AE-APP-5045" which collides with
FEATURE_FLAG_MIGRATION_FAILURE; update the MISCONFIGURED_INSTANCE_BASE_URL enum
entry to use a unique, unused error code string (e.g., increment the numeric
suffix) so each enum constant has a distinct code and leave
FEATURE_FLAG_MIGRATION_FAILURE unchanged; ensure the change is made only in the
MISCONFIGURED_INSTANCE_BASE_URL declaration within the AppsmithErrorCode enum.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java`:
- Around line 61-66: In SecureBaseUrlResolverCEImpl, the compat fallback branch
returns Mono.just(providedBaseUrl) which will NPE when providedBaseUrl is null;
update the branch that checks allowInsecureOriginBasedLinks to first null-check
providedBaseUrl and if it's null log a warning and return Mono.empty() (or
otherwise an appropriate empty/error Mono) instead of Mono.just(null), otherwise
continue to return Mono.just(providedBaseUrl).

---

Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java`:
- Around line 69-72: In SecureBaseUrlResolverCEImpl, avoid unbounded warn spam
from the log.warn(...) branch that returns Mono.empty(); implement rate-limiting
so the warning is emitted at most once per configured interval (e.g., once per
hour). For example, add a static/instance time-based guard (AtomicLong
lastWarnAt or a Guava RateLimiter) checked before calling log.warn(...) and
update the timestamp when the warn is emitted; keep the method behavior
returning Mono.empty() unchanged. Ensure the guard is thread-safe and
configurable, and document the interval constant near the class-level fields so
future maintainers can adjust it.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java`:
- Around line 197-207: Add a boundary test that asserts the empty-completion
path from secureBaseUrlResolver.resolveSecureBaseUrl(...) produces the generic
success response and does not send an email: mock secureBaseUrlResolver to
return Mono.empty() for resetUserPasswordDTO.getBaseUrl(), call the public entry
point that uses processForgotPasswordTokenGeneration (the service or controller
method that consumes resetUserPasswordDTO), and assert the HTTP/Service response
equals the generic success value and that the email-sending component was never
invoked; repeat the same kind of test for the other occurrence around
processForgotPasswordTokenGeneration referenced in the second region so future
refactors cannot convert Mono.empty() into a different response or trigger email
dispatch.
🪄 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

Run ID: 0a4aac4e-d7ce-4853-bced-9028cca33403

📥 Commits

Reviewing files that changed from the base of the PR and between a92dee4 and 3b7c865.

📒 Files selected for processing (13)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithErrorCode.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SecureBaseUrlResolver.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SecureBaseUrlResolverImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailServiceImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/UserServiceCECompatibleImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImplTest.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/EmailServiceCEImplTest.java

Comment on lines +61 to +66
if (allowInsecureOriginBasedLinks) {
log.warn("APPSMITH_BASE_URL is not configured and APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true. "
+ "Token-bearing email links will be derived from the request Origin header. "
+ "This is INSECURE and intended only as a transition path. "
+ "Set APPSMITH_BASE_URL to your instance's canonical URL and remove the insecure flag.");
return Mono.just(providedBaseUrl);
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Project Reactor, does Mono.just(null) throw an exception, and what API is recommended when the value may be null?

💡 Result:

No, Mono.just(null) does not throw an exception at runtime; it fails to compile due to null-safety annotations and the Reactive Streams specification prohibiting null values (rule 2.13). When the value may be null, use Mono.justOrEmpty(T data), which emits the item if non-null or completes empty if null.

Citations:


🏁 Script executed:

# Check the actual file content around lines 61-66
cat -n app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java | sed -n '50,75p'

Repository: appsmithorg/appsmith

Length of output: 1561


🏁 Script executed:

# Check the method signature and parameter types
ast-grep --pattern $'public Mono<String> resolveSecureBaseUrl($_) {
  $$$
}'

Repository: appsmithorg/appsmith

Length of output: 46


🏁 Script executed:

# Check for null-safety annotations in the file and project configuration
grep -r "@NonNull\|@Nullable\|nonnull\|Nonnull" app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java || echo "No explicit null-safety annotations found in file"

Repository: appsmithorg/appsmith

Length of output: 114


🏁 Script executed:

# Check if the project uses lombok or spring-boot null-safety
head -50 app/server/appsmith-server/pom.xml | grep -A 5 -B 5 "lombok\|spring"

Repository: appsmithorg/appsmith

Length of output: 46


🏁 Script executed:

# Find all callers of resolveSecureBaseUrl
rg "resolveSecureBaseUrl" app/server/appsmith-server/src/ -A 2 -B 2 --type java

Repository: appsmithorg/appsmith

Length of output: 12891


🏁 Script executed:

# Check the request Origin header handling in the codebase
rg "Origin" app/server/appsmith-server/src/ --type java -l | head -10

Repository: appsmithorg/appsmith

Length of output: 1061


🏁 Script executed:

# Look for the interface definition to check nullability contracts
find app/server -name "*SecureBaseUrlResolver*.java" -type f

Repository: appsmithorg/appsmith

Length of output: 575


🏁 Script executed:

# Check for null-safety configuration in pom.xml
cat app/server/appsmith-server/pom.xml | grep -A 10 -B 5 "maven-compiler\|nullness\|<nullableAnnotations\|<nonnullAnnotations" || echo "No explicit config found"

Repository: appsmithorg/appsmith

Length of output: 712


🏁 Script executed:

# Check for Spring Boot or Lombok null-safety processor configuration
grep -r "processor\|@NonNull\|@Nullable" app/server/appsmith-server/pom.xml

Repository: appsmithorg/appsmith

Length of output: 46


🏁 Script executed:

# Look at the test file more carefully to see what happens with null in compat mode
cat -n app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImplTest.java | grep -A 10 -B 5 "CompatFlag"

Repository: appsmithorg/appsmith

Length of output: 2686


Add null check in the compat fallback path.

Line 66 will throw an exception if providedBaseUrl is null. The resolver accepts nullable input (HTTP Origin headers, DTO fields), and callers can reach this branch with null when the compat flag is enabled. The test suite covers null only with the flag disabled, not in this path.

Suggested fix
         if (allowInsecureOriginBasedLinks) {
+            if (!StringUtils.hasText(providedBaseUrl)) {
+                return Mono.empty();
+            }
             log.warn("APPSMITH_BASE_URL is not configured and APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true. "
                     + "Token-bearing email links will be derived from the request Origin header. "
                     + "This is INSECURE and intended only as a transition path. "
                     + "Set APPSMITH_BASE_URL to your instance's canonical URL and remove the insecure flag.");
             return Mono.just(providedBaseUrl);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (allowInsecureOriginBasedLinks) {
log.warn("APPSMITH_BASE_URL is not configured and APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true. "
+ "Token-bearing email links will be derived from the request Origin header. "
+ "This is INSECURE and intended only as a transition path. "
+ "Set APPSMITH_BASE_URL to your instance's canonical URL and remove the insecure flag.");
return Mono.just(providedBaseUrl);
if (allowInsecureOriginBasedLinks) {
if (!StringUtils.hasText(providedBaseUrl)) {
return Mono.empty();
}
log.warn("APPSMITH_BASE_URL is not configured and APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true. "
"Token-bearing email links will be derived from the request Origin header. "
"This is INSECURE and intended only as a transition path. "
"Set APPSMITH_BASE_URL to your instance's canonical URL and remove the insecure flag.");
return Mono.just(providedBaseUrl);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java`
around lines 61 - 66, In SecureBaseUrlResolverCEImpl, the compat fallback branch
returns Mono.just(providedBaseUrl) which will NPE when providedBaseUrl is null;
update the branch that checks allowInsecureOriginBasedLinks to first null-check
providedBaseUrl and if it's null log a warning and return Mono.empty() (or
otherwise an appropriate empty/error Mono) instead of Mono.just(null), otherwise
continue to return Mono.just(providedBaseUrl).

@github-actions
Copy link
Copy Markdown

Failed server tests

  • com.appsmith.server.fork.ApplicationForkingServiceTests#cloneApplicationForkWithConfigurationFalseWithActionsThrice
  • com.appsmith.server.fork.ApplicationForkingServiceTests#cloneApplicationForkWithConfigurationTrueWithActionsThrice
  • com.appsmith.server.fork.ApplicationForkingServiceTests#cloneApplicationMultipleTimes
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplicationToWorkspace_WhenAppHasCustomSavedTheme_NewCustomThemeCreated
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplicationToWorkspace_WhenAppHasSystemTheme_SystemThemeSet
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplicationToWorkspace_WhenAppHasUnsavedThemeCustomization_ForkedWithCustomizations
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplication_WhenContainsInternalFields_InternalFieldsNotForked
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplication_deletePageAfterBeingPublished_deletedPageIsNotCloned
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplication_withForkWithConfigurationFalseAndDatasourceNotUsed_IsPartialImportFalse
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplication_withForkWithConfigurationFalseAndDatasourceUsed_IsPartialImportTrue
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplication_withForkWithConfigurationTrue_IsPartialImportFalse
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkGitConnectedApplication_defaultBranchUpdated_forkDefaultBranchApplication
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkingEnabledPublicApp_noPermission_ForkApplicationSuccess
  • com.appsmith.server.fork.ApplicationForkingServiceTests#test1_cloneWorkspaceWithItsContents
  • com.appsmith.server.fork.ApplicationForkingServiceTests#test2_forkApplicationWithReadApplicationUserAccess
  • com.appsmith.server.fork.ApplicationForkingServiceTests#test3_failForkApplicationWithInvalidPermission
  • com.appsmith.server.fork.ApplicationForkingServiceTests#test4_validForkApplication_cancelledMidWay_createValidApplication
  • com.appsmith.server.fork.ApplicationForkingServiceTests#test5_failForkApplication_noPageEditPermission
  • com.appsmith.server.fork.ApplicationForkingServiceTests#test6_failForkApplication_noDatasourceCreatePermission
  • com.appsmith.server.helpers.AppsmithErrorTest#verifyUniquenessOfAppsmithErrorCode
  • com.appsmith.server.services.ThemeServiceTest#changeCurrentTheme_WhenUserHasNoPermission_ThrowsException
  • com.appsmith.server.services.ThemeServiceTest#getApplicationTheme_WhenUserHasNoPermission_ExceptionThrows
  • com.appsmith.server.services.UserServiceTest#emailVerificationTokenGenerate_WhenInstanceEmailVerificationIsNotEnabled_ThrowsException
  • com.appsmith.server.services.UserServiceTest#emailVerificationTokenGenerate_WhenUserEmailAlreadyVerified_ThrowsException
  • com.appsmith.server.services.UserServiceTest#signUpAfterBeingInvitedToAppsmithWorkspace
  • com.appsmith.server.services.UserWorkspaceServiceTest#leaveWorkspace_WhenUserDoesNotExistInWorkspace_ThrowsException
  • com.appsmith.server.services.WorkspaceServiceTest#addNewUserToWorkspaceAsAdmin
  • com.appsmith.server.services.WorkspaceServiceTest#addNewUserToWorkspaceAsViewer
  • com.appsmith.server.services.WorkspaceServiceTest#addNewUsersBulkToWorkspaceAsViewer
  • com.appsmith.server.services.WorkspaceServiceTest#addUserToWorkspaceAsAdminAndCheckApplicationAndDatasourcePermissions
  • com.appsmith.server.services.WorkspaceServiceTest#addUserToWorkspaceAsViewerAndCheckApplicationPermissions
  • com.appsmith.server.services.WorkspaceServiceTest#addUserToWorkspaceIfUserAlreadyMember_throwsError
  • com.appsmith.server.services.WorkspaceServiceTest#getAllMembersForWorkspace
  • com.appsmith.server.services.WorkspaceServiceTest#testInviteDuplicateUsers_shouldReturnUniqueUsers
  • com.appsmith.server.services.ce.ApplicationServiceCETest#testCreatePage_lastModifiedByShouldGetChanged
  • com.appsmith.server.services.ce.ApplicationServiceCETest#testUpdateApplication_modifiedByShouldUpdate
  • com.appsmith.server.solutions.ce.ActionExecutionSolutionCETest#testViewerCanExecutePublishedAction
  • com.appsmith.server.solutions.ce.ActionExecutionSolutionCETest#testViewerCannotExecuteUnpublishedAction

… + test profile flag

Two CI fixes layered onto the previous commit:

1. The new error code AE-APP-5045 collided with the existing
   FEATURE_FLAG_MIGRATION_FAILURE entry (caught by
   AppsmithErrorTest#verifyUniquenessOfAppsmithErrorCode). Bumped the
   MISCONFIGURED_INSTANCE_BASE_URL code to AE-APP-5046, which is unused.

2. The integration test suite (WorkspaceServiceTest, ApplicationForkingServiceTests,
   UserServiceTest email-verification cases, ThemeServiceTest, etc.) exercises
   invite and email-verification flows without setting APPSMITH_BASE_URL. With the
   new fail-closed default, those flows would short-circuit before reaching the
   business-logic validations the tests rely on. Set
   APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true in the test profile so the
   legacy Origin-based behaviour is preserved across the existing integration
   suites. Production deployments default this flag to false (secure).

   The fail-closed semantics introduced by this advisory are still pinned
   directly by the SecureBaseUrlResolverCEImplTest unit tests (which run without
   Spring context), so this does not weaken the security regression coverage.

   This also lets EmailServiceCEImplTest drop the per-class @TestPropertySource
   (added in the previous commit) since the global flag covers it.

Verified locally with the full UserServiceTest + EmailServiceCEImplTest +
SecureBaseUrlResolverCEImplTest + AppsmithErrorTest suites (46/46 passing) and
representative samples from WorkspaceServiceTest, ApplicationForkingServiceTests,
ThemeServiceTest, and UserWorkspaceServiceTest.

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
…vw2f-9hrw)

The GHSA fix in 3b7c865 makes the server fail-closed for token-bearing
email flows (forgot-password, email verification, workspace invite,
instance-admin invite) when APPSMITH_BASE_URL is unset. The Cypress E2E suite
spins up a fresh Appsmith Docker container that did not set the variable, so
specs like Email_settings_Spec, ExportApplication_spec, DeleteWorkspace_spec,
LeaveWorkspaceTest_spec, MemberRoles_Spec, and ShareAppTests_Spec failed with
MISCONFIGURED_INSTANCE_BASE_URL.

Configure APPSMITH_BASE_URL at container startup in every CI environment that
runs Cypress, so E2E exercises the new secure path end-to-end and CI mirrors
how self-hosted operators must configure their instance.

- ci-test-limited.yml, ci-test-limited-with-count.yml,
  ci-test-custom-script.yml, ci-test-playwright.yml: add
  -e APPSMITH_BASE_URL=http://localhost to the docker run --name appsmith
  command. The container exposes port 80 and Cypress hits http://localhost;
  browsers omit :80 from the Origin header for default-port URLs, so the
  strict-mode equality check in SecureBaseUrlResolverCEImpl matches.

- scripts/deploy_preview.sh: add
  --set applicationConfig.APPSMITH_BASE_URL=https://$DOMAINNAME to the helm
  upgrade command. $DOMAINNAME is already computed earlier in the script as
  $edition-$PULL_REQUEST_NUMBER.dp.appsmith.com.

- scripts/local_testing.sh: same env var added to the developer-local Docker
  run helper, for parity with CI.

Approach A (configure the var) was chosen over Approach B (set the migration
flag APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true) so that CI exercises the
new secure path E2E rather than the deprecated migration fallback. The
fail-closed semantics introduced by the GHSA fix remain pinned by the
SecureBaseUrlResolverCEImplTest unit tests (no Spring context) and by the
unit/integration suite's test profile.

Design notes: docs/superpowers/specs/2026-04-28-ci-base-url-config-design.md

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/specs/2026-04-28-ci-base-url-config-design.md`:
- Around line 47-49: The fenced code block containing the Helm/set snippet
"--set applicationConfig.APPSMITH_BASE_URL=https://$DOMAINNAME" is missing a
fence language; update the fenced block to declare a shell language (e.g., add
"bash" after the opening ``` fence) so the example is rendered and markdownlint
passes.
🪄 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

Run ID: 2a8976e2-2a75-457b-9ee6-957502188ce4

📥 Commits

Reviewing files that changed from the base of the PR and between 35eb077 and dd8fbed.

📒 Files selected for processing (7)
  • .github/workflows/ci-test-custom-script.yml
  • .github/workflows/ci-test-limited-with-count.yml
  • .github/workflows/ci-test-limited.yml
  • .github/workflows/ci-test-playwright.yml
  • docs/superpowers/specs/2026-04-28-ci-base-url-config-design.md
  • scripts/deploy_preview.sh
  • scripts/local_testing.sh

Comment on lines +47 to +49
```
--set applicationConfig.APPSMITH_BASE_URL=https://$DOMAINNAME
```
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.

⚠️ Potential issue | 🟡 Minor

Add a language tag to the fenced example.

The shell snippet is missing a fence language, which will keep this doc tripping markdownlint.

Suggested fix
-```
+```bash
 --set applicationConfig.APPSMITH_BASE_URL=https://$DOMAINNAME
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 47-47: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-04-28-ci-base-url-config-design.md` around lines
47 - 49, The fenced code block containing the Helm/set snippet "--set
applicationConfig.APPSMITH_BASE_URL=https://$DOMAINNAME" is missing a fence
language; update the fenced block to declare a shell language (e.g., add "bash"
after the opening ``` fence) so the example is rendered and markdownlint passes.

…SA-j9gf-vw2f-9hrw)

Strict-mode comparison previously used raw String equality, which spuriously
rejected requests where the Origin header and the configured APPSMITH_BASE_URL
differed only in insignificant URL syntax. CI Cypress (and any real-world
deployment whose APPSMITH_BASE_URL syntax doesn't byte-equal the browser's
Origin header) consequently saw HTTP 400s with
'Origin header does not match APPSMITH_BASE_URL configuration'.

Replace the equality check with an RFC 6454 origin comparison:

- Parse both values as URIs.
- Lowercase scheme and host.
- Compute effective port (80 for http, 443 for https when not specified).
- Compare scheme + host + effective port.

Trailing slashes, default-port elision (http://example.com vs
http://example.com:80), and host-name casing now resolve to the same origin —
matching how browsers populate the Origin header. Differences in scheme, host,
or non-default port still error out, preserving the strict-mode protection
from PR #41426. Userinfo tricks (https://appsmith.example@evil.com) still fail
because the URI parser places the legitimate-looking string in userinfo and
puts the actual host in .getHost().

The resolver now also logs a WARN with both observed values when it rejects a
mismatch, so operators can debug misconfigurations without enabling DEBUG.

Eight new unit-test cases pin the new semantics:
- trailing-slash insensitivity (configured vs Origin)
- default-port elision for http and https
- case-insensitive host comparison
- scheme mismatch still rejected
- non-default port mismatch still rejected
- malformed URLs still rejected
- userinfo tricks still rejected

All 16 SecureBaseUrlResolverCEImplTest cases pass locally, plus the broader
UserServiceTest, EmailServiceCEImplTest, WorkspaceServiceTest, and
AppsmithErrorTest suites (57 tests total).

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java (1)

71-76: ⚠️ Potential issue | 🟠 Major

Guard the compat fallback against missing Origin.

If APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true and providedBaseUrl is null or blank, this branch throws or propagates an unusable base URL instead of failing closed. That makes the migration path brittle for requests where Origin is absent/stripped.

Suggested fix
         if (allowInsecureOriginBasedLinks) {
+            if (!StringUtils.hasText(providedBaseUrl)) {
+                log.warn("APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true but request Origin is empty.");
+                return Mono.empty();
+            }
             log.warn("APPSMITH_BASE_URL is not configured and APPSMITH_ALLOW_INSECURE_ORIGIN_BASED_LINKS=true. "
                     + "Token-bearing email links will be derived from the request Origin header. "
                     + "This is INSECURE and intended only as a transition path. "
                     + "Set APPSMITH_BASE_URL to your instance's canonical URL and remove the insecure flag.");
             return Mono.just(providedBaseUrl);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java`
around lines 71 - 76, In SecureBaseUrlResolverCEImpl where
allowInsecureOriginBasedLinks is handled, guard the insecure fallback by
validating providedBaseUrl (the value derived from the request Origin) before
returning it; if providedBaseUrl is null or blank, do not return
Mono.just(providedBaseUrl) — instead return a failed Mono (e.g., Mono.error with
a clear message) or otherwise fail closed so callers do not receive an unusable
URL; update the branch that currently returns Mono.just(providedBaseUrl) to
perform this null/blank check and produce an error when Origin is missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImplTest.java`:
- Around line 60-67: Add a companion regression test in
SecureBaseUrlResolverCEImplTest that covers the compat-mode fallback when the
Origin is null or blank: create a new test (e.g.,
resolveSecureBaseUrl_whenOriginNullOrBlank_andCompatFlagOn_returnsProvidedValue)
that builds a SecureBaseUrlResolverCEImpl via newResolver("", true) and asserts
resolver.resolveSecureBaseUrl(null) and resolver.resolveSecureBaseUrl("") both
emit the provided URL (e.g., "https://attacker.example") and complete; this
mirrors
resolveSecureBaseUrl_whenAppsmithBaseUrlUnsetAndCompatFlagOn_returnsProvidedValue
but exercises the null/blank Origin regression path.

---

Duplicate comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java`:
- Around line 71-76: In SecureBaseUrlResolverCEImpl where
allowInsecureOriginBasedLinks is handled, guard the insecure fallback by
validating providedBaseUrl (the value derived from the request Origin) before
returning it; if providedBaseUrl is null or blank, do not return
Mono.just(providedBaseUrl) — instead return a failed Mono (e.g., Mono.error with
a clear message) or otherwise fail closed so callers do not receive an unusable
URL; update the branch that currently returns Mono.just(providedBaseUrl) to
perform this null/blank check and produce an error when Origin is missing.
🪄 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

Run ID: 9dabe7f7-028f-4a2e-be78-0892ba042d97

📥 Commits

Reviewing files that changed from the base of the PR and between dd8fbed and f020589.

📒 Files selected for processing (2)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImplTest.java

Comment on lines +60 to +67
@Test
void resolveSecureBaseUrl_whenAppsmithBaseUrlUnsetAndCompatFlagOn_returnsProvidedValue() throws Exception {
SecureBaseUrlResolverCEImpl resolver = newResolver("", true);

StepVerifier.create(resolver.resolveSecureBaseUrl("https://attacker.example"))
.expectNext("https://attacker.example")
.verifyComplete();
}
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.

⚠️ Potential issue | 🟡 Minor

Add the missing compat-mode null/blank regression case.

This suite only pins missing Origin when compat mode is off. Adding the same case with allowInsecureFallback=true would catch the current breakage in the migration path.

Suggested test
     `@Test`
     void resolveSecureBaseUrl_whenAppsmithBaseUrlUnsetAndCompatFlagOn_returnsProvidedValue() throws Exception {
         SecureBaseUrlResolverCEImpl resolver = newResolver("", true);

         StepVerifier.create(resolver.resolveSecureBaseUrl("https://attacker.example"))
                 .expectNext("https://attacker.example")
                 .verifyComplete();
     }
+
+    `@Test`
+    void resolveSecureBaseUrl_whenProvidedBaseUrlIsNullOrBlank_andCompatFlagOn_returnsEmpty() throws Exception {
+        SecureBaseUrlResolverCEImpl resolver = newResolver("", true);
+
+        StepVerifier.create(resolver.resolveSecureBaseUrl(null)).verifyComplete();
+        StepVerifier.create(resolver.resolveSecureBaseUrl("")).verifyComplete();
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
void resolveSecureBaseUrl_whenAppsmithBaseUrlUnsetAndCompatFlagOn_returnsProvidedValue() throws Exception {
SecureBaseUrlResolverCEImpl resolver = newResolver("", true);
StepVerifier.create(resolver.resolveSecureBaseUrl("https://attacker.example"))
.expectNext("https://attacker.example")
.verifyComplete();
}
`@Test`
void resolveSecureBaseUrl_whenAppsmithBaseUrlUnsetAndCompatFlagOn_returnsProvidedValue() throws Exception {
SecureBaseUrlResolverCEImpl resolver = newResolver("", true);
StepVerifier.create(resolver.resolveSecureBaseUrl("https://attacker.example"))
.expectNext("https://attacker.example")
.verifyComplete();
}
`@Test`
void resolveSecureBaseUrl_whenProvidedBaseUrlIsNullOrBlank_andCompatFlagOn_returnsEmpty() throws Exception {
SecureBaseUrlResolverCEImpl resolver = newResolver("", true);
StepVerifier.create(resolver.resolveSecureBaseUrl(null)).verifyComplete();
StepVerifier.create(resolver.resolveSecureBaseUrl("")).verifyComplete();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/SecureBaseUrlResolverCEImplTest.java`
around lines 60 - 67, Add a companion regression test in
SecureBaseUrlResolverCEImplTest that covers the compat-mode fallback when the
Origin is null or blank: create a new test (e.g.,
resolveSecureBaseUrl_whenOriginNullOrBlank_andCompatFlagOn_returnsProvidedValue)
that builds a SecureBaseUrlResolverCEImpl via newResolver("", true) and asserts
resolver.resolveSecureBaseUrl(null) and resolver.resolveSecureBaseUrl("") both
emit the provided URL (e.g., "https://attacker.example") and complete; this
mirrors
resolveSecureBaseUrl_whenAppsmithBaseUrlUnsetAndCompatFlagOn_returnsProvidedValue
but exercises the null/blank Origin regression path.

…(GHSA-j9gf-vw2f-9hrw)

Cypress's commands.js had been intentionally setting `req.headers["origin"] =
"Cypress"` (literal string) on five intercepts:

  - POST /api/v1/users/invite
  - POST /api/v1/applications/invite
  - POST /api/v1/git/applications/*/connect
  - PUT  /api/v1/admin/env
  - PUT  /api/v1/tenants

The literal string "Cypress" was a legacy synthetic-traffic flag — never a
meaningful URL. With the GHSA-j9gf-vw2f-9hrw fix, the server now strict-mode
matches `Origin` against `APPSMITH_BASE_URL` (URL-origin comparison per RFC
6454). The literal string fails to parse as a URL, so every intercepted
request errors with HTTP 400 "Origin header does not match APPSMITH_BASE_URL
configuration."

The peer file `HomePage.ts#StubPostHeaderReq` (and `e2e.js`'s admin/env
intercept) already used `Cypress.config("baseUrl")` correctly. Align
commands.js to the same pattern. No production behavior change — only the
test infrastructure now sends a valid Origin matching the running deploy.

Verified locally that the diff is the only mention of the literal string in
real code; the only remaining occurrence is a long-commented-out line in
AggregateHelper.ts which is left untouched.

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
@subrata71 subrata71 self-assigned this Apr 29, 2026
@appsmithorg appsmithorg deleted a comment from github-actions Bot Apr 29, 2026
…HSA-j9gf-vw2f-9hrw)

The previously-assigned code AE-APP-5046 is unique in CE but collides
with EE-only GENERATE_ACTION_VISUALIZATION_FAILED. Bump to the next free
code in both repos (5047 is also taken in EE by GENERATE_ACTION_SCHEMA_FAILED).

This unblocks the EE shadow PR's AppsmithErrorTest#verifyUniquenessOfAppsmithErrorCode
once the cherry-picked CE history reaches it. The companion EE-side commit
applies the same single-line edit on top of the EE shadow PR.

Refs https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
@subrata71 subrata71 closed this May 1, 2026
subrata71 added a commit that referenced this pull request May 6, 2026
…ring emails (GHSA-j9gf-vw2f-9hrw) (#41767)

## Summary

Companion to PR #41766 (the GHSA-j9gf-vw2f-9hrw fail-closed fix).

Adds a non-dismissible top-of-screen banner shown only to instance
super-users when `APPSMITH_BASE_URL` is unset and the resolver is
therefore in fail-closed mode for token-bearing email flows. Multi-org
EE deployments (e.g. Appsmith Cloud) **never** see the banner — verified
via the `license_multi_org_enabled` short-circuit in the EE resolver
override (shadow EE PR linked below).

The banner deep-links to Admin Settings → Configuration where
`APPSMITH_BASE_URL` is the registered field. Saving via the existing
Configuration-tier admin-settings flow restarts the server, the SPA
auto-reloads, the resolver re-reads the env, and the banner clears — no
re-login.

## Architecture

- **Server:** new reactive
`SecureBaseUrlResolverCE#isBaseUrlConfigurationHealthy()` exposes the
health signal. CE returns `true` iff `APPSMITH_BASE_URL` is set; EE
override (in shadow EE PR) returns `true` unconditionally when
`license_multi_org_enabled` is on. Wired into
`ConsolidatedAPIServiceCEImpl` alongside the existing org-config fetch
so it runs in parallel (no sequential cost). Result populates a new
transient boolean field `instanceBaseUrlConfigurationHealthy` on
`OrganizationConfigurationCE`.
- **Client:** new `BaseUrlMissingBanner.tsx` component renders via the
ADS `Banner` component (same pattern as `PageBannerMessage`). Selector
`getShouldShowBaseUrlMissingBanner` gates on `isSuperUser &&
adminSettingsVisible && instanceBaseUrlConfigurationHealthy === false`.
The explicit `=== false` (rather than `!value`) is the rolling-deploy
guard. Mounted inside `PageHeader.tsx` alongside the existing
license/trial banner.
- **Recovery loop:** zero new code — leverages the existing
`RESTART_SERVER_POLL` → `window.location.reload()` flow that every
Configuration-tier admin setting already uses today.

Fixes
https://linear.app/appsmith/issue/APP-15046/security-high-configuration-dependent-origin-validation-bypass-in

## Test plan

- [x] `SecureBaseUrlResolverCEImplTest` — 2 new cases for
`isBaseUrlConfigurationHealthy()` (true when set, false when blank);
18/18 total pass
- [x] `usersSelectors.test.ts` — 6 new cases pinning all gating
dimensions plus the rolling-deploy `=== false` guard
- [x] `BaseUrlMissingBanner.test.tsx` — 5 new Jest cases covering all
gating dimensions
- [x] `ConsolidatedAPIServiceImplTest` — mock wired for resolver
- Cypress: skipped — CI sets `APPSMITH_BASE_URL=http://localhost`,
banner never fires by construction

## Companion / refs

- Shadow EE PR: appsmithorg/appsmith-ee#9003
- Security advisory:
https://github.com/appsmithorg/appsmith/security/advisories/GHSA-j9gf-vw2f-9hrw
- Linear: APP-15046


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

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/25359958615>
> Commit: e9e92ce
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=25359958615&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Tue, 05 May 2026 07:40:44 UTC
<!-- end of auto-generated comment: Cypress test results  -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Required label for CI Security Issues related to information security within the product

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant