Skip to content

fix(security): prevent super user creation race condition (GHSA-9wcp-79g5-5c3c)#41681

Merged
subrata71 merged 6 commits intoreleasefrom
fix/super-user-race-condition
Apr 3, 2026
Merged

fix(security): prevent super user creation race condition (GHSA-9wcp-79g5-5c3c)#41681
subrata71 merged 6 commits intoreleasefrom
fix/super-user-race-condition

Conversation

@subrata71
Copy link
Copy Markdown
Collaborator

@subrata71 subrata71 commented Mar 31, 2026

Description

TL;DR: Fix a critical TOCTOU race condition in the /api/v1/users/super endpoint that allowed concurrent requests to bypass the single-admin restriction during initial Appsmith setup, enabling multiple unauthorized Instance Administrators.

Root Cause

The signupAndLoginSuper() method in UserSignupCEImpl.java performed a non-atomic check-then-act sequence:

  1. CHECK: isUsersEmpty() queries MongoDB — returns true if no users exist
  2. GAP: No lock or atomicity guarantee between check and create
  3. ACT: signupAndLogin() creates the user + makeInstanceAdministrator() grants admin

In the reactive WebFlux environment, concurrent requests all saw an empty database and all proceeded to create admin users.

Fixes https://linear.app/appsmith/issue/APP-15067/security-high-super-user-creation-race-condition-allows-multiple

Fixes https://github.com/appsmithorg/appsmith/security/advisories/GHSA-9wcp-79g5-5c3c

Fix

Introduces an atomic sentinel document claim using MongoDB's findAndModify with upsert=true:

  • A lightweight superUserSetupLock collection with a fixed _id document
  • findAndModify with upsert is atomic at the document level — only the first request to upsert wins
  • All concurrent requests see the existing document and are rejected before user creation
  • The original isUsersEmpty() check is retained as defense-in-depth

Why this approach:

  • Zero race window — single atomic MongoDB operation
  • Works on both standalone MongoDB and replica sets (no transaction support needed)
  • No external dependency (Redis, distributed locks)
  • Minimal code change (7 files, ~200 lines added, 1 line removed)
  • No regression risk — the sentinel collection is completely isolated from existing data

Security Impact

  • CWE: CWE-367 (Time-of-Check Time-of-Use Race Condition)
  • CVSS 3.1: AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1 (HIGH)
  • Impact: Authorization bypass allowing persistent backdoor admin accounts

Files Changed

File Change
CustomUserRepositoryCE.java Added claimSuperUserCreationSlot() interface method
CustomUserRepositoryCEImpl.java Implemented atomic sentinel claim via findAndModify
UserServiceCE.java Exposed claimSuperUserCreationSlot() in service interface
UserServiceCEImpl.java Delegated to repository
UserSignupCEImpl.java Integrated sentinel claim before isUsersEmpty() check
CustomUserRepositoryCEImplTest.java 3 integration tests including concurrent race simulation
UserSignupTest.java 2 unit tests for rejection paths

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/23942098312
Commit: 2f7328a
Cypress dashboard.
Tags: @tag.All
Spec:


Fri, 03 Apr 2026 11:03:13 UTC

Communication

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

  • Yes
  • No

Summary by CodeRabbit

  • Bug Fixes

    • Prevented race conditions so only one instance can claim the super-user setup slot and unauthorized simultaneous attempts are rejected.
  • Improvements

    • Added automatic seeding of a setup lock for existing/upgraded deployments to coordinate startup behavior and ensure cleanup on failure.
  • Tests

    • Added tests verifying single-winner slot claiming, concurrent claim behavior, and related authorization/failure paths.
  • Migrations

    • Added a migration to create the sentinel setup lock when appropriate.

The /api/v1/users/super endpoint had a TOCTOU race condition where
concurrent requests during initial setup could bypass the single-admin
restriction. The isUsersEmpty() check and user creation were non-atomic,
allowing multiple requests to pass the check before any user was created.

This fix introduces an atomic sentinel document claim using MongoDB's
findAndModify with upsert. Only the first request to insert the sentinel
succeeds; all concurrent requests see the existing document and are
rejected. The isUsersEmpty() check is retained as defense-in-depth.

The sentinel approach works on both standalone MongoDB and replica sets,
requires no external dependencies (Redis, distributed locks), and has
zero race window since MongoDB guarantees document-level atomicity on
findAndModify.

CVSS 3.1: AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1 (HIGH)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 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 MongoDB-backed coordination for single-winner super-user creation: repository methods to atomically claim and release a creation slot, service delegations, signup flow gated on the claim (with release on failure), a migration to seed locks for existing installs, and tests for sequential and concurrent claims.

Changes

Cohort / File(s) Summary
Repository Interface & Impl
app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCE.java, app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java
Added claimSuperUserCreationSlot(): Mono<Boolean> and releaseSuperUserCreationSlot(): Mono<Void> to interface and implemented them. Impl uses ReactiveMongoOperations and an upsert/findAndModify sentinel in superUserSetupLock; first caller returns true, duplicates return false; DuplicateKey mapped to false.
Service Interface & Impl
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java, app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java
Declared and implemented claimSuperUserCreationSlot() and releaseSuperUserCreationSlot() as simple delegations to repository.
Signup Flow
app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java
signupAndLoginSuper(...) now calls userService.claimSuperUserCreationSlot() up front and fails with UNAUTHORIZED_ACCESS if not true. Super-user creation moved into createAndSetupSuperUser(...). On errors after claiming, flow logs failure and calls userService.releaseSuperUserCreationSlot() before propagating the error.
Database Migration
app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java
New Mongock migration (id seed-super-user-setup-lock, order 075) that seeds a sentinel document into superUserSetupLock for existing deployments with non-system users; handles concurrent insert (DuplicateKey) gracefully.
Tests
app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java, app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java
Added repository tests for first, sequential, and concurrent claims (assert exactly one true). Added signup tests covering unauthorized paths: when claim is false, and when claim true but users already exist (verifies releaseSuperUserCreationSlot() is called).

Sequence Diagram

sequenceDiagram
    actor Client
    participant UserSignup as UserSignupService
    participant UserSvc as UserService
    participant UserRepo as UserRepository
    participant MongoDB

    Client->>UserSignup: signupAndLoginSuper(request)
    UserSignup->>UserSvc: claimSuperUserCreationSlot()
    UserSvc->>UserRepo: claimSuperUserCreationSlot()
    UserRepo->>MongoDB: findAndModify(filter {_id:"super-user-setup"}, upsert=true)
    alt First claim -> document created
        MongoDB-->>UserRepo: inserted document
        UserRepo-->>UserSvc: true
        UserSvc-->>UserSignup: true
        UserSignup->>UserSvc: isUsersEmpty()
        alt empty -> proceed
            UserSignup-->>Client: create & login super user
        else not empty -> fail
            UserSignup->>UserSvc: releaseSuperUserCreationSlot()
            UserSvc->>UserRepo: releaseSuperUserCreationSlot()
            UserRepo->>MongoDB: remove lock document
            UserRepo-->>UserSvc: void
            UserSignup-->>Client: error UNAUTHORIZED_ACCESS
        end
    else Document exists -> claim lost
        MongoDB-->>UserRepo: existing document
        UserRepo-->>UserSvc: false
        UserSvc-->>UserSignup: false
        UserSignup-->>Client: error UNAUTHORIZED_ACCESS
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🔐 One sentinel set in Mongo's keep,
A single claim for the crown to keep,
First hand wins the quiet race,
Others wait their turn with patient grace. 👑

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing a race condition in super user creation and references the security advisory.
Description check ✅ Passed PR description is comprehensive and well-structured, including clear TL;DR, root cause analysis, fix approach with security justification, and proper issue references.

✏️ 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/super-user-race-condition

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 subrata71 self-assigned this Mar 31, 2026
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.

🧹 Nitpick comments (5)
app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java (2)

175-178: Remove unnecessary fully-qualified class name.

Flux is already imported on line 13. Use the simple name for consistency.

♻️ Suggested fix
-        List<Boolean> results = reactor.core.publisher.Flux.merge(claimMonos)
+        List<Boolean> results = Flux.merge(claimMonos)
                 .collectList()
                 .block();
🤖 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/repositories/ce/CustomUserRepositoryCEImplTest.java`
around lines 175 - 178, In CustomUserRepositoryCEImplTest, replace the
unnecessary fully-qualified reference reactor.core.publisher.Flux.merge with the
simple imported Flux.merge (leave the claimMonos variable and
collectList().block() logic unchanged) so the test uses the already-imported
Flux type consistently; update the line using Flux.merge(claimMonos) instead of
the fully-qualified name.

192-198: Consider using @BeforeEach / @AfterEach for cleanup.

Calling dropSuperUserSetupLockCollection() at the start and end of each test is repetitive. Moving cleanup to lifecycle methods would reduce duplication and ensure consistent state.

🤖 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/repositories/ce/CustomUserRepositoryCEImplTest.java`
around lines 192 - 198, Move the repeated dropSuperUserSetupLockCollection()
calls into JUnit lifecycle methods to ensure consistent cleanup: add a
`@BeforeEach` method in CustomUserRepositoryCEImplTest that calls
dropSuperUserSetupLockCollection() to ensure a clean state before each test, and
optionally add an `@AfterEach` method that calls
dropSuperUserSetupLockCollection() to guarantee teardown after each test;
reference the existing private helper dropSuperUserSetupLockCollection() and
invoke it from these annotated methods rather than duplicating calls in
individual tests.
app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java (2)

33-34: Prefer constructor injection over field injection.

Field injection with @Autowired makes the class harder to test and hides dependencies. Consider constructor injection for consistency with Spring best practices.

♻️ Suggested refactor
-    `@Autowired`
-    private ReactiveMongoOperations reactiveMongoOperations;
+    private final ReactiveMongoOperations reactiveMongoOperations;
+
+    public CustomUserRepositoryCEImpl(ReactiveMongoOperations reactiveMongoOperations) {
+        this.reactiveMongoOperations = reactiveMongoOperations;
+    }

Note: You may need to adjust if this class already has a constructor or relies on proxy behavior.

🤖 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/repositories/ce/CustomUserRepositoryCEImpl.java`
around lines 33 - 34, Replace the field-injected ReactiveMongoOperations in
CustomUserRepositoryCEImpl with constructor injection: remove the `@Autowired`
annotation on the reactiveMongoOperations field, add a constructor that accepts
a ReactiveMongoOperations parameter and assigns it to the
reactiveMongoOperations field (or make the field final and assign in the
constructor), and update any existing constructors if present to include this
dependency so the repository's dependency is explicit and testable.

74-77: Consider storing claimedAt as a native Date type.

Storing Instant.now().toString() as a String works but loses native date querying/indexing benefits. For future debugging or auditing, a proper date type is preferable.

♻️ Suggested change
-                .setOnInsert("claimedAt", Instant.now().toString());
+                .setOnInsert("claimedAt", new java.util.Date());
🤖 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/repositories/ce/CustomUserRepositoryCEImpl.java`
around lines 74 - 77, Replace storing claimedAt as a String by inserting a
native Date object: change the Update construction that calls
setOnInsert("claimedAt", Instant.now().toString()) to setOnInsert("claimedAt",
Date.from(Instant.now())) (or new Date()), ensuring java.util.Date is imported;
this preserves SUPER_USER_SETUP_LOCK_ID semantics and uses the same Update
update and setOnInsert("claimedAt", ...) call site so claimedAt becomes a proper
date type for querying/indexing.
app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java (1)

192-216: Good defense-in-depth test.

Verifies that even if the slot is claimed, the isUsersEmpty() check still gates access when users already exist.

Consider adding a happy-path test that mocks both claimSuperUserCreationSlot()true and isUsersEmpty()true to verify successful super user creation proceeds correctly. This would complete the test matrix.

🤖 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/solutions/UserSignupTest.java`
around lines 192 - 216, Add a happy-path test in UserSignupTest that mocks
userService.claimSuperUserCreationSlot() to return Mono.just(true) and
userService.isUsersEmpty() to return Mono.just(true), constructs a
UserSignupRequestDTO (same fields as the failing test), calls
userSignup.signupAndLoginSuper(request, "http://localhost", exchange) and
verifies via StepVerifier that the Mono emits a User with expected properties
(email/name/state/isEnabled) and completes successfully; ensure you reference
the same symbols (UserSignupRequestDTO, userService.claimSuperUserCreationSlot,
userService.isUsersEmpty, userSignup.signupAndLoginSuper, StepVerifier) and
assert the created user's fields match the request.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java`:
- Around line 33-34: Replace the field-injected ReactiveMongoOperations in
CustomUserRepositoryCEImpl with constructor injection: remove the `@Autowired`
annotation on the reactiveMongoOperations field, add a constructor that accepts
a ReactiveMongoOperations parameter and assigns it to the
reactiveMongoOperations field (or make the field final and assign in the
constructor), and update any existing constructors if present to include this
dependency so the repository's dependency is explicit and testable.
- Around line 74-77: Replace storing claimedAt as a String by inserting a native
Date object: change the Update construction that calls setOnInsert("claimedAt",
Instant.now().toString()) to setOnInsert("claimedAt", Date.from(Instant.now()))
(or new Date()), ensuring java.util.Date is imported; this preserves
SUPER_USER_SETUP_LOCK_ID semantics and uses the same Update update and
setOnInsert("claimedAt", ...) call site so claimedAt becomes a proper date type
for querying/indexing.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java`:
- Around line 175-178: In CustomUserRepositoryCEImplTest, replace the
unnecessary fully-qualified reference reactor.core.publisher.Flux.merge with the
simple imported Flux.merge (leave the claimMonos variable and
collectList().block() logic unchanged) so the test uses the already-imported
Flux type consistently; update the line using Flux.merge(claimMonos) instead of
the fully-qualified name.
- Around line 192-198: Move the repeated dropSuperUserSetupLockCollection()
calls into JUnit lifecycle methods to ensure consistent cleanup: add a
`@BeforeEach` method in CustomUserRepositoryCEImplTest that calls
dropSuperUserSetupLockCollection() to ensure a clean state before each test, and
optionally add an `@AfterEach` method that calls
dropSuperUserSetupLockCollection() to guarantee teardown after each test;
reference the existing private helper dropSuperUserSetupLockCollection() and
invoke it from these annotated methods rather than duplicating calls in
individual tests.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java`:
- Around line 192-216: Add a happy-path test in UserSignupTest that mocks
userService.claimSuperUserCreationSlot() to return Mono.just(true) and
userService.isUsersEmpty() to return Mono.just(true), constructs a
UserSignupRequestDTO (same fields as the failing test), calls
userSignup.signupAndLoginSuper(request, "http://localhost", exchange) and
verifies via StepVerifier that the Mono emits a User with expected properties
(email/name/state/isEnabled) and completes successfully; ensure you reference
the same symbols (UserSignupRequestDTO, userService.claimSuperUserCreationSlot,
userService.isUsersEmpty, userSignup.signupAndLoginSuper, StepVerifier) and
assert the created user's fields match the request.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0f4dbb86-8bf3-472e-8a9f-cc47f8be2234

📥 Commits

Reviewing files that changed from the base of the PR and between c4c9303 and 41a72c0.

📒 Files selected for processing (7)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.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/solutions/ce/UserSignupCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java

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.

🧹 Nitpick comments (1)
app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java (1)

65-66: Prevent sentinel-seeding errors from short-circuiting later startup steps.

At Line 66, seedSuperUserSetupLockIfNeeded() is in the main chain without local fallback. If it errors, license validation and org feature-flag cache refresh are skipped in this run. Consider isolating this step with a local onErrorResume so startup-critical post-steps still execute.

Proposed patch
                 // Prefill the server cache with anonymous user permission group ids.
                 .then(cacheableRepositoryHelper.preFillAnonymousUserPermissionGroupIdsCache())
                 // Seed the super user setup sentinel for upgraded instances that already have users.
-                .then(userService.seedSuperUserSetupLockIfNeeded())
+                .then(userService.seedSuperUserSetupLockIfNeeded().onErrorResume(errorSignal -> {
+                    log.warn("Super user setup sentinel seeding failed; continuing startup: {}", errorSignal.getMessage());
+                    return Mono.empty();
+                }))
                 // Cold publisher to wait for upstream execution to complete as we have dependency on the instance
                 // registration
🤖 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/configurations/InstanceConfig.java`
around lines 65 - 66, The call to userService.seedSuperUserSetupLockIfNeeded()
in InstanceConfig is currently in the main reactive chain and can short-circuit
subsequent startup tasks if it errors; update that step to handle errors locally
by adding a reactive fallback (e.g., onErrorResume) around
seedSuperUserSetupLockIfNeeded() so any exception is logged/ignored for this
step but does not propagate, ensuring the subsequent license validation and org
feature-flag cache refresh steps still run; locate the chain that calls
seedSuperUserSetupLockIfNeeded() and wrap only that Mono/Flux with onErrorResume
to swallow or log the error and continue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java`:
- Around line 65-66: The call to userService.seedSuperUserSetupLockIfNeeded() in
InstanceConfig is currently in the main reactive chain and can short-circuit
subsequent startup tasks if it errors; update that step to handle errors locally
by adding a reactive fallback (e.g., onErrorResume) around
seedSuperUserSetupLockIfNeeded() so any exception is logged/ignored for this
step but does not propagate, ensuring the subsequent license validation and org
feature-flag cache refresh steps still run; locate the chain that calls
seedSuperUserSetupLockIfNeeded() and wrap only that Mono/Flux with onErrorResume
to swallow or log the error and continue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: be6cff87-de86-4ce8-8d9f-240cc1d309f0

📥 Commits

Reviewing files that changed from the base of the PR and between 41a72c0 and 17e9058.

📒 Files selected for processing (6)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java
🚧 Files skipped from review as they are similar to previous changes (5)
  • 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/UserServiceCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCE.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java

…gration

Adds a Mongock migration (075) that runs once during upgrade. For
existing instances that already have non-system users (meaning the
super admin setup completed before this fix), the migration inserts
the sentinel document into the superUserSetupLock collection. This
ensures the /api/v1/users/super endpoint is properly guarded from
the first moment the upgraded instance starts serving requests.

For fresh instances with no users, the migration is a no-op — the
sentinel will be atomically claimed by the first legitimate setup
request via the findAndModify mechanism in the first commit.
@subrata71 subrata71 force-pushed the fix/super-user-race-condition branch from 17e9058 to b3e2486 Compare March 31, 2026 07:26
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.

🧹 Nitpick comments (3)
app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java (3)

36-37: Consider adding a comment explaining the intentionally empty rollback.

The empty rollback is correct (removing the sentinel would re-expose the race condition), but a brief inline comment would clarify the intent for future maintainers.

📝 Suggested comment
 `@RollbackExecution`
-public void rollbackExecution() {}
+public void rollbackExecution() {
+    // Intentionally empty: removing the sentinel would re-expose the TOCTOU vulnerability
+}
🤖 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/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`
around lines 36 - 37, Add a brief inline comment inside the rollbackExecution
method of the Migration075SeedSuperUserSetupLock class explaining that the
method is intentionally empty and why (removing the sentinel would re-expose the
race condition), so future maintainers understand the intent; reference the
`@RollbackExecution-annotated` rollbackExecution method and document that no
rollback is performed on purpose to preserve the sentinel protection.

65-65: Consider defensive error handling for the insert.

The plain insert() relies on Mongock's migration locking to prevent concurrent execution. For defense-in-depth (consistent with the PR's approach), you could catch DuplicateKeyException and log/skip gracefully rather than failing the migration.

This is unlikely to matter in practice since Mongock serializes migrations, but it would align with the application code's defensive style (see claimSuperUserCreationSlot() which handles this case explicitly).

🛡️ Optional defensive wrapper
-mongoTemplate.insert(sentinel, SUPER_USER_SETUP_COLLECTION);
-log.info("Seeded super user setup sentinel for existing instance with {} non-system user(s).",
-        nonSystemUserCount);
+try {
+    mongoTemplate.insert(sentinel, SUPER_USER_SETUP_COLLECTION);
+    log.info("Seeded super user setup sentinel for existing instance with {} non-system user(s).",
+            nonSystemUserCount);
+} catch (org.springframework.dao.DuplicateKeyException e) {
+    log.info("Sentinel was inserted concurrently. Skipping.");
+}
🤖 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/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`
at line 65, The insert of the sentinel document in
Migration075SeedSuperUserSetupLock currently calls
mongoTemplate.insert(sentinel, SUPER_USER_SETUP_COLLECTION) without handling
DuplicateKeyException; update the migration method to wrap that insert in a
try/catch that specifically catches
org.springframework.dao.DuplicateKeyException (or the Mongo duplicate key
equivalent), and on that exception log a debug/info message and skip/return
gracefully so the migration does not fail if the sentinel already exists,
keeping behavior consistent with claimSuperUserCreationSlot().

24-24: Empty author field.

The author field is blank. Consider adding the PR author's username or team identifier for traceability in Mongock's changelog history.

🤖 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/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`
at line 24, The `@ChangeUnit` annotation on the Migration075SeedSuperUserSetupLock
class has an empty author attribute; update the annotation (ChangeUnit(order =
"075", id = "seed-super-user-setup-lock", author = "")) to include a non-empty
identifier (e.g., your GitHub username or team name) so Mongock changelogs have
traceability—edit the author value directly in the
Migration075SeedSuperUserSetupLock class annotation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`:
- Around line 36-37: Add a brief inline comment inside the rollbackExecution
method of the Migration075SeedSuperUserSetupLock class explaining that the
method is intentionally empty and why (removing the sentinel would re-expose the
race condition), so future maintainers understand the intent; reference the
`@RollbackExecution-annotated` rollbackExecution method and document that no
rollback is performed on purpose to preserve the sentinel protection.
- Line 65: The insert of the sentinel document in
Migration075SeedSuperUserSetupLock currently calls
mongoTemplate.insert(sentinel, SUPER_USER_SETUP_COLLECTION) without handling
DuplicateKeyException; update the migration method to wrap that insert in a
try/catch that specifically catches
org.springframework.dao.DuplicateKeyException (or the Mongo duplicate key
equivalent), and on that exception log a debug/info message and skip/return
gracefully so the migration does not fail if the sentinel already exists,
keeping behavior consistent with claimSuperUserCreationSlot().
- Line 24: The `@ChangeUnit` annotation on the Migration075SeedSuperUserSetupLock
class has an empty author attribute; update the annotation (ChangeUnit(order =
"075", id = "seed-super-user-setup-lock", author = "")) to include a non-empty
identifier (e.g., your GitHub username or team name) so Mongock changelogs have
traceability—edit the author value directly in the
Migration075SeedSuperUserSetupLock class annotation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bab08d29-3611-4426-8009-643e583f4e7d

📥 Commits

Reviewing files that changed from the base of the PR and between 17e9058 and b3e2486.

📒 Files selected for processing (1)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java

@github-actions
Copy link
Copy Markdown

Failed server tests

  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testErrorResponseWhenAnonymousUserAccessPrivateApp
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testErrorWhenModeIsNullAndPageIdAvailable
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testPageLoadResponseForEditMode
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testPageLoadResponseForEditModeWhenDefaultBranchIsDifferentFromDefault
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testPageLoadResponseForViewMode
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testPageLoadResponseForViewMode_whenBranchNameIsPresentInNonGitApp
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testPageLoadResponseWhenPageIdAndApplicationIdMissing
  • com.appsmith.server.services.ce.ConsolidatedAPIServiceImplTest#testPageLoadWhenPageFromFeatureBranchWithoutRefName_shouldRouteToDefaultBranch

@linear
Copy link
Copy Markdown

linear bot commented Mar 31, 2026

@subrata71 subrata71 added the ok-to-test Required label for CI label Mar 31, 2026
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 (4)
app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java (1)

169-177: Consider extracting duplicated UserSignupRequestDTO construction.

The request setup is repeated in both tests. A small helper would reduce maintenance overhead and keep test intent focused.

Refactor sketch
+    private UserSignupRequestDTO createSuperSignupRequest() {
+        UserSignupRequestDTO request = new UserSignupRequestDTO();
+        request.setEmail("admin@test.com");
+        request.setPassword("ValidP@ssw0rd!");
+        request.setName("Admin");
+        request.setSource(LoginSource.FORM);
+        request.setState(UserState.ACTIVATED);
+        request.setIsEnabled(true);
+        request.setAllowCollectingAnonymousData(false);
+        return request;
+    }
...
-        UserSignupRequestDTO request = new UserSignupRequestDTO();
-        request.setEmail("admin@test.com");
-        request.setPassword("ValidP@ssw0rd!");
-        request.setName("Admin");
-        request.setSource(LoginSource.FORM);
-        request.setState(UserState.ACTIVATED);
-        request.setIsEnabled(true);
-        request.setAllowCollectingAnonymousData(false);
+        UserSignupRequestDTO request = createSuperSignupRequest();
...
-        UserSignupRequestDTO request = new UserSignupRequestDTO();
-        request.setEmail("admin@test.com");
-        request.setPassword("ValidP@ssw0rd!");
-        request.setName("Admin");
-        request.setSource(LoginSource.FORM);
-        request.setState(UserState.ACTIVATED);
-        request.setIsEnabled(true);
-        request.setAllowCollectingAnonymousData(false);
+        UserSignupRequestDTO request = createSuperSignupRequest();

Also applies to: 195-203

🤖 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/solutions/UserSignupTest.java`
around lines 169 - 177, Several tests duplicate construction of
UserSignupRequestDTO; extract that block into a reusable helper (e.g., a private
method in UserSignupTest such as buildDefaultUserSignupRequest or
createDefaultSignupRequest) that returns a populated UserSignupRequestDTO with
Email, Password, Name, Source (LoginSource.FORM), State (UserState.ACTIVATED),
isEnabled(true) and allowCollectingAnonymousData(false); replace the inline
construction in both test methods with calls to this helper so tests read
cleaner and changes to defaults are maintained in one place.
app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java (1)

24-24: Consider filling in the author field.

The author field is empty. While not a functional issue, populating it improves traceability in the migration history.

🤖 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/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`
at line 24, The `@ChangeUnit` annotation on class
Migration075SeedSuperUserSetupLock has an empty author value; update the
annotation's author attribute (the string in `@ChangeUnit`(order = "075", id =
"seed-super-user-setup-lock", author = "")) to a non-empty identifier (e.g.,
your name or handle) so the migration record includes traceability information
for the Migration075SeedSuperUserSetupLock change unit.
app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java (2)

33-34: Consider constructor injection for better testability.

Field injection with @Autowired works, but constructor injection is generally preferred for easier unit testing and clearer dependency declaration. If this class follows an existing pattern in the codebase, feel free to keep as-is.

🤖 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/repositories/ce/CustomUserRepositoryCEImpl.java`
around lines 33 - 34, Replace field injection of ReactiveMongoOperations in
CustomUserRepositoryCEImpl with constructor injection: remove the `@Autowired`
private ReactiveMongoOperations reactiveMongoOperations field and add a
constructor that accepts ReactiveMongoOperations (and other dependencies if
present) and assigns it to a final field, updating usages to the new final field
to improve testability and make dependencies explicit.

74-77: Redundant setOnInsert for _id.

Line 75's setOnInsert("_id", ...) is unnecessary. MongoDB automatically uses the _id from the query filter during upsert. No harm done, just a minor cleanup opportunity.

♻️ Optional cleanup
         Update update = new Update()
-                .setOnInsert("_id", SUPER_USER_SETUP_LOCK_ID)
                 .setOnInsert("status", "CLAIMED")
                 .setOnInsert("claimedAt", Instant.now().toString());
🤖 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/repositories/ce/CustomUserRepositoryCEImpl.java`
around lines 74 - 77, The Update builder is calling setOnInsert("_id",
SUPER_USER_SETUP_LOCK_ID) which is redundant because MongoDB uses the query
filter's _id for upserts; remove the call to setOnInsert("_id", ...) from the
Update construction (leave the other setOnInsert calls for "status" and
"claimedAt" intact) — locate the Update variable (Update update = new Update()
...) and drop the setOnInsert referencing SUPER_USER_SETUP_LOCK_ID.
🤖 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/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`:
- Around line 41-63: The exists() then insert() sequence in
Migration075SeedSuperUserSetupLock is racy: when multiple instances run the
migration they can both pass mongoTemplate.exists(...) for
SUPER_USER_SETUP_LOCK_ID and one insert will throw DuplicateKeyException; wrap
the mongoTemplate.insert(...) call (or replace with an atomic upsert) in a
try/catch that catches DuplicateKeyException
(org.springframework.dao.DuplicateKeyException or
com.mongodb.DuplicateKeyException), treat it as a benign race (log an info/debug
message referencing SUPER_USER_SETUP_LOCK_ID and "migration-075") and return
without failing the migration so concurrent starts succeed gracefully.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java`:
- Around line 190-212: The test lacks an interaction assertion to confirm the
claimed-slot path reached userService.isUsersEmpty(); after the
StepVerifier.verify() add a Mockito.verify(userService).isUsersEmpty() (or
verify(userService, times(1)).isUsersEmpty()) to assert the method was invoked
during signupAndLoginSuper_WhenSlotClaimedButUsersExist_RaisesUnauthorized,
ensuring the branch was actually executed.

---

Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java`:
- Line 24: The `@ChangeUnit` annotation on class
Migration075SeedSuperUserSetupLock has an empty author value; update the
annotation's author attribute (the string in `@ChangeUnit`(order = "075", id =
"seed-super-user-setup-lock", author = "")) to a non-empty identifier (e.g.,
your name or handle) so the migration record includes traceability information
for the Migration075SeedSuperUserSetupLock change unit.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java`:
- Around line 33-34: Replace field injection of ReactiveMongoOperations in
CustomUserRepositoryCEImpl with constructor injection: remove the `@Autowired`
private ReactiveMongoOperations reactiveMongoOperations field and add a
constructor that accepts ReactiveMongoOperations (and other dependencies if
present) and assigns it to a final field, updating usages to the new final field
to improve testability and make dependencies explicit.
- Around line 74-77: The Update builder is calling setOnInsert("_id",
SUPER_USER_SETUP_LOCK_ID) which is redundant because MongoDB uses the query
filter's _id for upserts; remove the call to setOnInsert("_id", ...) from the
Update construction (leave the other setOnInsert calls for "status" and
"claimedAt" intact) — locate the Update variable (Update update = new Update()
...) and drop the setOnInsert referencing SUPER_USER_SETUP_LOCK_ID.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java`:
- Around line 169-177: Several tests duplicate construction of
UserSignupRequestDTO; extract that block into a reusable helper (e.g., a private
method in UserSignupTest such as buildDefaultUserSignupRequest or
createDefaultSignupRequest) that returns a populated UserSignupRequestDTO with
Email, Password, Name, Source (LoginSource.FORM), State (UserState.ACTIVATED),
isEnabled(true) and allowCollectingAnonymousData(false); replace the inline
construction in both test methods with calls to this helper so tests read
cleaner and changes to defaults are maintained in one place.
🪄 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: 604eb891-7729-4b0a-af4a-ed206b5241a7

📥 Commits

Reviewing files that changed from the base of the PR and between b3e2486 and 56d9bc6.

📒 Files selected for processing (4)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java
✅ Files skipped from review due to trivial changes (1)
  • app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java

@subrata71 subrata71 requested a review from sondermanish April 1, 2026 05:50
@subrata71
Copy link
Copy Markdown
Collaborator Author

/build-deploy-preview skip-tests=true

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

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

@subrata71 subrata71 changed the title fix(security): prevent super user creation race condition (CWE-367) fix(security): prevent super user creation race condition (GHSA-9wcp-79g5-5c3c) Apr 1, 2026
if (!Boolean.TRUE.equals(claimed)) {
return Mono.error(new AppsmithException(AppsmithError.UNAUTHORIZED_ACCESS));
}
return userService.isUsersEmpty();
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.

what happens to the claim when further step fails? i.e. does the claim expire?

Query query = new Query(Criteria.where("_id").is(SUPER_USER_SETUP_LOCK_ID));
Update update = new Update()
.setOnInsert("_id", SUPER_USER_SETUP_LOCK_ID)
.setOnInsert("status", "CLAIMED")
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.

Is this claim ephemeral?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No but it's released on failure. Once setup succeeds, it stays permanently

@ChangeUnit(order = "075", id = "seed-super-user-setup-lock", author = "")
public class Migration075SeedSuperUserSetupLock {

private static final String SUPER_USER_SETUP_COLLECTION = "superUserSetupLock";
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.

nit: this could come from a central place. not to be minded of

- Add releaseSuperUserCreationSlot() to release the sentinel if super
  user creation fails after claiming the slot (e.g., password validation
  error, DB write failure). This prevents the instance from being bricked
  by a permanently claimed but unused sentinel.
- Extract createAndSetupSuperUser() private method to cleanly scope the
  onErrorResume handler that triggers the release.
- Catch DuplicateKeyException in Migration075 for defense-in-depth
  against concurrent migration execution.
- Add verify(userService).isUsersEmpty() interaction assertion in test.
- Fix fully-qualified Flux reference in concurrent test.

Addresses review comments from @sondermanish and @coderabbitai.
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.

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-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java (1)

303-385: ⚠️ Potential issue | 🟠 Major

Releasing the slot here still leaves bootstrap unrecoverable.

signupAndLogin(...) persists the first user before makeInstanceAdministrator(...) runs. If elevation fails, Lines 383-385 only remove the sentinel; the next attempt still fails isUsersEmpty(), so the instance can end up with no Instance Administrator and no retry path. Please make admin elevation part of the initial create flow, or compensate by deleting the created user before releasing the slot.

🤖 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/solutions/ce/UserSignupCEImpl.java`
around lines 303 - 385, The current flow persists a user in signupAndLogin(...)
before calling userUtils.makeInstanceAdministrator(...), so if elevation fails
the onErrorResume only calls userService.releaseSuperUserCreationSlot() leaving
a created, non-admin user and making bootstrap unrecoverable; fix by ensuring
the created user is removed before releasing the slot (e.g., invoke
userService.deleteById(...) or userService.delete(...) for the user created by
signupAndLogin() and wait for that Mono to complete, then call
userService.releaseSuperUserCreationSlot()), or alternatively move the
makeInstanceAdministrator(...) call into the initial create/login flow
(signupAndLogin or its helper) so user creation+elevation are atomic.
🧹 Nitpick comments (1)
app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java (1)

134-197: Make the sentinel cleanup fixture-scoped and narrower.

If one assertion fails, the trailing cleanup never runs, and dropCollection(...) is wider than these tests need. Prefer removing just the fixed lock document in @BeforeEach/@AfterEach; that avoids the broad catch and reduces cross-test interference.

🤖 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/repositories/ce/CustomUserRepositoryCEImplTest.java`
around lines 134 - 197, Replace the broad dropSuperUserSetupLockCollection()
that drops the whole collection with a narrower per-test remove of the sentinel
lock document: add `@BeforeEach` and `@AfterEach` methods that run
reactiveMongoOperations.remove(Query.query(Criteria.where("<uniqueLockField>").is("<uniqueLockValue>")),
"superUserSetupLock").block() (use the actual lock document field/value used by
claimSuperUserCreationSlot()), remove or stop calling the old
dropSuperUserSetupLockCollection() in the tests (and delete that helper), and
ensure tests (claimSuperUserCreationSlot_*) only delete that single lock
document so cleanup runs even if an assertion fails and avoids dropping the
collection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java`:
- Around line 303-385: The current flow persists a user in signupAndLogin(...)
before calling userUtils.makeInstanceAdministrator(...), so if elevation fails
the onErrorResume only calls userService.releaseSuperUserCreationSlot() leaving
a created, non-admin user and making bootstrap unrecoverable; fix by ensuring
the created user is removed before releasing the slot (e.g., invoke
userService.deleteById(...) or userService.delete(...) for the user created by
signupAndLogin() and wait for that Mono to complete, then call
userService.releaseSuperUserCreationSlot()), or alternatively move the
makeInstanceAdministrator(...) call into the initial create/login flow
(signupAndLogin or its helper) so user creation+elevation are atomic.

---

Nitpick comments:
In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java`:
- Around line 134-197: Replace the broad dropSuperUserSetupLockCollection() that
drops the whole collection with a narrower per-test remove of the sentinel lock
document: add `@BeforeEach` and `@AfterEach` methods that run
reactiveMongoOperations.remove(Query.query(Criteria.where("<uniqueLockField>").is("<uniqueLockValue>")),
"superUserSetupLock").block() (use the actual lock document field/value used by
claimSuperUserCreationSlot()), remove or stop calling the old
dropSuperUserSetupLockCollection() in the tests (and delete that helper), and
ensure tests (claimSuperUserCreationSlot_*) only delete that single lock
document so cleanup runs even if an assertion fails and avoids dropping the
collection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 47fa39eb-0a6c-436c-995d-ea8d5ef7a25d

📥 Commits

Reviewing files that changed from the base of the PR and between 56d9bc6 and 6f3dc8b.

📒 Files selected for processing (8)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.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/solutions/ce/UserSignupCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCEImplTest.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java
✅ Files skipped from review due to trivial changes (1)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075SeedSuperUserSetupLock.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserRepositoryCE.java

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

Failed server tests

  • com.appsmith.server.helpers.GitUtilsTest#isRepoPrivate

@subrata71 subrata71 merged commit 598cbce into release Apr 3, 2026
89 of 91 checks passed
@subrata71 subrata71 deleted the fix/super-user-race-condition branch April 3, 2026 11:45
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants