Skip to content

fix(security): enforce ACL and workspace boundary on datasource lookups in NewActionServiceCEImpl (GHSA-rqp9-r5fr-4cc8)#41846

Closed
subrata71 wants to merge 7 commits into
releasefrom
fix/cross-tenant-ds-takeover-ghsa-rqp9-r5fr-4cc8
Closed

fix(security): enforce ACL and workspace boundary on datasource lookups in NewActionServiceCEImpl (GHSA-rqp9-r5fr-4cc8)#41846
subrata71 wants to merge 7 commits into
releasefrom
fix/cross-tenant-ds-takeover-ghsa-rqp9-r5fr-4cc8

Conversation

@subrata71

@subrata71 subrata71 commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes GHSA-rqp9-r5fr-4cc8Cross-tenant datasource takeover via unprotected findById in NewActionServiceCEImpl.

Vulnerability

NewActionServiceCEImpl.validateAction() used the 1-argument datasourceService.findById(id) — which performs no ACL check — to resolve the datasource referenced by an action's DTO. Since the datasource.id field is fully client-controlled (overwritten via copyNestedNonNullProperties deep merge), any authenticated user could reference a datasource in another tenant's workspace.

For public apps, the subsequent updateDatasourcePolicyForPublicAction() method then granted anonymous EXECUTE_DATASOURCES on the foreign datasource, enabling unauthenticated execution of queries against the victim's database using stored credentials.

This is the same 1-arg findById anti-pattern as GHSA-rg2x-4v4h-g78w (fixed in AuthenticationServiceCEImpl), applied here to the action create/update path.

Fix

  1. Primary — ACL on datasource fetch: Replace datasourceService.findById(id) with findById(id, datasourcePermission.getExecutePermission()) in validateAction(). Users must have execute permission on a datasource to reference it in an action.

  2. Defense-in-depth — workspace boundary check: Add workspace membership verification in updateDatasourcePolicyForPublicAction() — reject with UNAUTHORIZED_ACCESS if the datasource's workspace doesn't match the action's workspace. This prevents cross-workspace policy grants even if a future code path omits the ACL check.

  3. ACL on application fetch: Replace applicationService.findById(applicationId) with the ACL-checked variant in the policy mutation path.

  4. Hardening — analytics/hint paths: Add ACL with graceful fallback (.onErrorResume()) to all other 1-arg datasourceService.findById() calls in the same file (post-update analytics, post-delete analytics, hint generation, archive path). These prevented cross-tenant datasource metadata leaks.

Affected Code

All changes are in a single file: NewActionServiceCEImpl.java — 6 call sites fixed.

Line Context Change
~624 validateAction — primary vulnerability Added datasourcePermission.getExecutePermission()
~1627 updateDatasourcePolicyForPublicAction Added workspace boundary check
~1652 updateDatasourcePolicyForPublicAction Added ACL to applicationService.findById
~866 updateUnpublishedAction — analytics Added ACL + graceful fallback
~1136 deleteUnpublishedAction — analytics Added ACL + graceful fallback
~1190 Hint message generation Added ACL
~1587 archiveGivenNewAction — analytics Added ACL + graceful fallback

CE/EE Strategy

CE-only safe. NewActionServiceImpl is a pass-through with no method overrides. Hourly CE→EE sync will carry these changes cleanly.

Testing

  • 2 new security tests in NewActionServiceSecurityTest.java:
    • validateAction_shouldFetchDatasourceWithPermissionCheck_GHSA_rqp9 — verifies ACL enforcement
    • validateAction_shouldRejectCrossWorkspaceDatasourcePolicyGrant_GHSA_rqp9 — verifies workspace boundary check
  • 6 existing tests pass (0 regressions)

Linear Ticket

APP-15250

Severity

Critical (assessed CVSS 9.1) — CWE-862 (Missing Authorization), CWE-639 (IDOR)

/test all

Summary by CodeRabbit

  • Bug Fixes

    • Action validation now enforces permission-checked datasource access (execute permission) and skips permission lookup for dry-run validations.
    • Inaccessible or errored datasource lookups fall back to an embedded default instead of hard-failing.
    • Workspace ID consistency enforced to block unauthorized cross-workspace datasource policy grants; bulk public-datasource updates follow suit.
    • Datasource lookup for hint generation now returns a safe default on missing/errored fetches; public-action policy updates verify application read access first.
  • Tests

    • Added security tests for permission-checked datasource access and cross-workspace rejection.

Review Change Stack

Important

🟣 🟣 🟣 Your tests are running.
Tests running at: https://github.com/appsmithorg/appsmith/actions/runs/27101140319
Commit: d4e6cab
Workflow: PR Automation test suite
Tags: @tag.All
Spec: ``


Sun, 07 Jun 2026 18:32:02 UTC

subrata71 added 2 commits May 26, 2026 13:39
…p9-r5fr-4cc8)

Adds two security tests that verify:
1. validateAction fetches datasource with ACL permission check (not 1-arg findById)
2. updateDatasourcePolicyForPublicAction rejects cross-workspace datasource references
…ps in NewActionServiceCEImpl (GHSA-rqp9-r5fr-4cc8)

Replace all 1-arg datasourceService.findById(id) calls with the 2-arg
ACL-checked variant using datasourcePermission.getExecutePermission().
This prevents cross-tenant datasource reference injection via
client-controlled datasource.id in action create/update DTOs.

Additionally:
- Add workspace boundary check in updateDatasourcePolicyForPublicAction
  to prevent cross-workspace anonymous EXECUTE policy grants
- Add ACL to applicationService.findById in the policy mutation path
- Add ACL with graceful fallback on analytics/hint datasource lookups
  (lines ~866, ~1136, ~1190, ~1584) to prevent cross-tenant info leaks

Closes GHSA-rqp9-r5fr-4cc8
@subrata71 subrata71 added Security Issues related to information security within the product ok-to-test Required label for CI labels May 26, 2026
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

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

Datasource lookups in NewActionServiceCEImpl now use execute-permission-scoped findById across validation, update, hint, and archive flows with fallback to embedded configs on denial. Public-action policy updates verify workspace consistency and load the application with read permission. New tests cover ACL enforcement and cross-workspace rejection.

Changes

Datasource Access Control and Policy Authorization

Layer / File(s) Summary
Permission-scoped datasource lookups with fallback
app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java
validateAction, updateUnpublishedAction, populateHintMessages, archiveGivenNewAction, and unpublished-action archive/delete analytics flows now call datasourceService.findById(..., datasourcePermission.getExecutePermission()); when lookup is empty or errors occur, the code falls back to the embedded/incoming DatasourceConfiguration instead of hard-failing.
Workspace boundary validation and policy authorization
app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java
updateDatasourcePolicyForPublicAction and bulk public-datasource policy updates enforce application/action vs datasource workspace-id consistency (throws UNAUTHORIZED_ACCESS on mismatch) and fetch the application via applicationPermission.getReadPermission() before applying and persisting the execute-policy update.
Security test suite
app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceSecurityTest.java
Adds tests that verify validateAction uses ACL-checked datasourceService.findById(id, executePermission) (and does not call the unscoped overload) and that cross-workspace datasource references cause UNAUTHORIZED_ACCESS and do not trigger datasourceService.save(..., false).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NewActionService
  participant DatasourceService
  participant ApplicationService
  participant InMemoryDatasource

  Client->>NewActionService: request validate/update/archive/populateHints
  NewActionService->>DatasourceService: findById(datasourceId, executePermission)
  alt datasource found
    DatasourceService-->>NewActionService: Datasource
  else not found / permission denied
    NewActionService->>InMemoryDatasource: use embedded datasource/config
  end
  NewActionService->>NewActionService: enforce workspaceId checks
  alt updating public action policy & workspace match
    NewActionService->>ApplicationService: findById(appId, readPermission)
    ApplicationService-->>NewActionService: Application (isPublic?)
    NewActionService->>DatasourceService: save(updatedDatasource)
  else workspace mismatch
    NewActionService-->>Client: error UNAUTHORIZED_ACCESS
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ok-to-test

Suggested reviewers

  • sondermanish
  • wyattwalter

🔐 Datasources now check their papers,
Permission-scoped guards close the capers,
Workspace lines are drawn and kept,
Unauthorized tries are gently swept,
Tests stand watch while policies adapt.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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 title clearly and specifically describes the primary security fix: enforcing ACL and workspace boundaries on datasource lookups with the CVE reference.
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 The pull request description is comprehensive, addressing the vulnerability, fix strategy, affected code locations, testing approach, and severity assessment with clear context and motivation.

✏️ 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/cross-tenant-ds-takeover-ghsa-rqp9-r5fr-4cc8

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 requested a review from sondermanish May 26, 2026 07:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java (2)

1191-1203: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep hint generation best-effort on read paths.

populateHintMessages(...) runs during normal action fetches. With the new ACL lookup, a datasource the caller can't execute now turns the whole action read into NO_RESOURCE_FOUND instead of just skipping hints. This should degrade to an empty/default datasource config rather than failing the request.

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

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java`
around lines 1191 - 1203, The current dsConfigMono chain calls
datasourceService.findById(..., datasourcePermission.getExecutePermission()) and
then switchIfEmpty(Mono.error(...)) which turns a missing/unauthorized
datasource into a NO_RESOURCE_FOUND and fails action reads; instead make
hint-generation best-effort by returning a default empty DatasourceConfiguration
when the datasource is absent or inaccessible. Update the dsConfigMono flow (the
datasourceService.findById(...) call used by populateHintMessages and referenced
as
dsConfigMono/action.getDatasource().getId/datasourcePermission.getExecutePermission)
to fall back to Mono.just(new DatasourceConfiguration()) rather than
Mono.error(...) (or use onErrorResume for that specific NO_RESOURCE_FOUND error
to return the default) so populateHintMessages can proceed without failing the
whole read.

621-640: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Don't continue with the request datasource after an ACL miss.

When findById(..., datasourcePermission.getExecutePermission()) returns empty, this path falls back to the client-supplied datasource and then uses it for the workspace/policy flow. That lets an unauthorized datasource still reach updateDatasourcePolicyForPublicAction(...), and the later boundary check is neutralized once newAction.setWorkspaceId(datasource.getWorkspaceId()) runs.

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

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java`
around lines 621 - 640, The code continues the datasource flow using the
client-supplied datasource after an ACL miss, allowing unauthorized data to
reach updateDatasourcePolicyForPublicAction and letting
newAction.setWorkspaceId(datasource.getWorkspaceId()) run; change the
switchIfEmpty branch in the datasourceMono pipeline (where findById(...,
datasourcePermission.getExecutePermission()) is called) to return Mono.empty()
after recording the error/setting editActionDTO invalid instead of
Mono.just(editActionDTO.getDatasource()), and ensure downstream map/flatMap (the
map that calls newAction.setWorkspaceId(...) and the flatMap to
updateDatasourcePolicyForPublicAction) will not execute for empty Monos so
unauthorized datasources are never used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceSecurityTest.java`:
- Around line 220-258: The test currently stubs
permissionGroupService.isEntityAccessible(...) to always return true so it never
hits the rejection path; change the stubbing to return true for the action
entity and false for the datasource entity (e.g., use Mockito.doAnswer(...) on
permissionGroupService.isEntityAccessible to inspect the first argument and
return Mono.just(true) when it's an Action/has the action's id and
Mono.just(false) when it's the victimDatasource), then call
newActionService.validateAction(publicAction) and assert it fails with the
UNAUTHORIZED_ACCESS error (use StepVerifier.create(...).expectErrorMatches(e ->
e instanceof AppsmithException && ((AppsmithException)e).getErrorCode() ==
AppsmithError.UNAUTHORIZED_ACCESS).verify()) so the cross-workspace guard is
exercised; reference symbols: permissionGroupService.isEntityAccessible,
victimDatasource, publicAction, newActionService.validateAction,
UNAUTHORIZED_ACCESS.

---

Outside diff comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java`:
- Around line 1191-1203: The current dsConfigMono chain calls
datasourceService.findById(..., datasourcePermission.getExecutePermission()) and
then switchIfEmpty(Mono.error(...)) which turns a missing/unauthorized
datasource into a NO_RESOURCE_FOUND and fails action reads; instead make
hint-generation best-effort by returning a default empty DatasourceConfiguration
when the datasource is absent or inaccessible. Update the dsConfigMono flow (the
datasourceService.findById(...) call used by populateHintMessages and referenced
as
dsConfigMono/action.getDatasource().getId/datasourcePermission.getExecutePermission)
to fall back to Mono.just(new DatasourceConfiguration()) rather than
Mono.error(...) (or use onErrorResume for that specific NO_RESOURCE_FOUND error
to return the default) so populateHintMessages can proceed without failing the
whole read.
- Around line 621-640: The code continues the datasource flow using the
client-supplied datasource after an ACL miss, allowing unauthorized data to
reach updateDatasourcePolicyForPublicAction and letting
newAction.setWorkspaceId(datasource.getWorkspaceId()) run; change the
switchIfEmpty branch in the datasourceMono pipeline (where findById(...,
datasourcePermission.getExecutePermission()) is called) to return Mono.empty()
after recording the error/setting editActionDTO invalid instead of
Mono.just(editActionDTO.getDatasource()), and ensure downstream map/flatMap (the
map that calls newAction.setWorkspaceId(...) and the flatMap to
updateDatasourcePolicyForPublicAction) will not execute for empty Monos so
unauthorized datasources are never used.
🪄 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: 0554daf9-3586-4aaa-93af-f4f5461b4cf3

📥 Commits

Reviewing files that changed from the base of the PR and between d6d749e and d60bc63.

📒 Files selected for processing (2)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceSecurityTest.java

@subrata71 subrata71 self-assigned this May 26, 2026
@subrata71 subrata71 added ok-to-test Required label for CI and removed ok-to-test Required label for CI labels May 26, 2026
@subrata71 subrata71 requested a review from Copilot May 26, 2026 07:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Addresses GHSA-rqp9-r5fr-4cc8 (cross-tenant datasource takeover) by replacing the unprotected 1-arg datasourceService.findById(id) calls in NewActionServiceCEImpl with the ACL-checked 2-arg variant using EXECUTE_DATASOURCES, and adds a workspace-boundary guard plus an ACL-checked application lookup in the public-action datasource policy path.

Changes:

  • Replace 1-arg datasourceService.findById with execute-permission variant in validateAction, analytics, hint, delete, and archive paths (with graceful fallback on non-validation paths).
  • Add workspace-equality check and ACL-checked applicationService.findById in updateDatasourcePolicyForPublicAction.
  • Add new NewActionServiceSecurityTest with two GHSA regression tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java Switches 6 datasource lookups to ACL-checked variants, adds workspace boundary check and ACL'd application fetch in the public-action policy mutation path.
app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceSecurityTest.java New Spring Boot security test class with two regression tests for the GHSA fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1631 to +1634
String actionWorkspaceId = action.getWorkspaceId();
if (actionWorkspaceId != null && !actionWorkspaceId.equals(datasource.getWorkspaceId())) {
return Mono.error(new AppsmithException(AppsmithError.UNAUTHORIZED_ACCESS));
}
Comment on lines +254 to +260
StepVerifier.create(newActionService.validateAction(publicAction))
.assertNext(validatedAction -> {
assertThat(validatedAction).isNotNull();
})
.verifyComplete();

Mockito.verify(datasourceService, never()).save(any(Datasource.class), eq(false));
…space check (GHSA-rqp9-r5fr-4cc8)

Addresses CodeRabbit and Copilot review comments:

1. switchIfEmpty fallback no longer continues with the client-supplied
   datasource for workspace/policy operations. Returns a stub Datasource
   without an ID, preventing updateDatasourcePolicyForPublicAction from
   running on unverified data.

2. Workspace boundary check moved upstream into the datasourceMono
   flatMap — before newAction.setWorkspaceId() overwrites the action's
   workspace. This ensures the check compares the action's original
   workspace against the datasource's actual workspace.

3. Hint generation (populateHintMessages) is now best-effort: falls
   back to empty DatasourceConfiguration on permission denial instead
   of failing the entire action read with NO_RESOURCE_FOUND.

4. Cross-workspace test rewritten to assert UNAUTHORIZED_ACCESS error
   when action workspace differs from datasource workspace, properly
   exercising the boundary check.
@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#forkApplication_withForkWithConfigurationFalseAndDatasourceUsed_IsPartialImportTrue
  • com.appsmith.server.fork.ApplicationForkingServiceTests#forkApplication_withForkWithConfigurationTrue_IsPartialImportFalse

…to avoid fork regression (GHSA-rqp9-r5fr-4cc8)

The workspace boundary check in validateAction's datasourceMono.flatMap()
caused UNAUTHORIZED_ACCESS in fork flows where the ActionDTO carries the
source workspace while the forked datasource is in the target workspace.

Moved the check into updateDatasourcePolicyForPublicAction where it
compares the APPLICATION's workspace against the DATASOURCE's workspace.
This correctly blocks cross-workspace policy grants (the attack vector)
without breaking legitimate fork/import flows:

- Fork: application and datasource are both in the target workspace -> OK
- Attack: application in attacker's workspace, datasource in victim's -> BLOCKED

Also updated the cross-workspace test to exercise the actual policy
mutation path with proper isEntityAccessible stubbing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java`:
- Around line 638-643: The code unconditionally sets
newAction.setWorkspaceId(datasource.getWorkspaceId()) inside
NewActionServiceCEImpl which can overwrite a valid action workspace with null
when the fallback datasource lacks a workspaceId; change the mapping so you only
set/backfill the action workspace when datasource.getWorkspaceId() is non-null
(or non-empty) and otherwise leave newAction.getWorkspaceId() unchanged before
calling updateDatasourcePolicyForPublicAction and
validateAndSaveActionToRepository to avoid orphaning the action.
- Around line 1658-1662: The import path is missing the workspace guard used in
the direct mutation path; add the same workspace comparison
(application.getWorkspaceId() != null && datasource.getWorkspaceId() != null &&
!application.getWorkspaceId().equals(datasource.getWorkspaceId())) that aborts
with AppsmithError.UNAUTHORIZED_ACCESS to the import-side flow—either by
inserting this check in validateActionsBeforeImport(...) before calling
updateDatasourcePoliciesForPublicActions(...) or by centralizing it inside
updateDatasourcePoliciesForPublicActions(...) (which is invoked by
updateDatasourcePolicyForPublicAction and the import flow) so that any call that
loads the application via getApplicationById(...) /
applicationService.findById(...) will enforce the application-vs-datasource
workspace equality before saving datasource execute policies.
🪄 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: cd47b884-efe9-4631-946e-8c1de320e72f

📥 Commits

Reviewing files that changed from the base of the PR and between 038b9e3 and a5a0ee9.

📒 Files selected for processing (2)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceSecurityTest.java

sondermanish
sondermanish previously approved these changes May 26, 2026
…dd workspace check to import path (GHSA-rqp9-r5fr-4cc8)

- Guard newAction.setWorkspaceId() against null when the ACL fallback
  datasource has no workspaceId, preventing orphaned actions.
- Add workspace boundary check to the import-path
  updateDatasourcePoliciesForPublicActions, mirroring the guard already
  present in the single-action updateDatasourcePolicyForPublicAction.
@subrata71 subrata71 removed the ok-to-test Required label for CI label May 28, 2026
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Actionable comments posted: 0

@subrata71

Copy link
Copy Markdown
Collaborator Author

Review Comments Resolution Summary

All review comments from CodeRabbit and Copilot have been addressed across the following commits:

Already resolved in earlier commits (038b9e39a5a0ee90fb):

  1. Copilot @ line 1634 — workspace check was a no-op because it compared action.getWorkspaceId() (just overwritten from datasource) vs datasource.getWorkspaceId(). Fixed in a5a0ee90fb by switching to application.getWorkspaceId() (fetched from DB), which correctly differs from the victim datasource's workspace.

  2. CodeRabbit @ test:258 / Copilot @ test:260isEntityAccessible(...) was stubbed to return true unconditionally, causing isPublicDatasource to short-circuit before reaching the workspace check. Fixed in a5a0ee90fb with a doAnswer that returns true for NewAction (public action) and false for Datasource (non-public victim datasource), and the assertion now expects UNAUTHORIZED_ACCESS.

Resolved in latest commit (5340685954):

  1. CodeRabbit @ line 643newAction.setWorkspaceId(datasource.getWorkspaceId()) could overwrite with null when the ACL fallback datasource has no workspaceId. Fixed with a null guard.

  2. CodeRabbit @ line 1662 — Import-path updateDatasourcePoliciesForPublicActions was missing the workspace boundary check. Fixed by adding the same application.getWorkspaceId() != datasource.getWorkspaceId() guard that exists in the single-action path.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

.findById(
updatedActionDTO.getDatasource().getId(),
datasourcePermission.getExecutePermission())
.onErrorResume(e -> Mono.justOrEmpty(updatedActionDTO.getDatasource()));
return datasourceService
.findById(
action.getDatasource().getId(), datasourcePermission.getExecutePermission())
.onErrorResume(e -> Mono.justOrEmpty(action.getDatasource()));
if (action.getDatasource() != null && action.getDatasource().getId() != null) {
return datasourceService.findById(action.getDatasource().getId());
return datasourceService
.findById(action.getDatasource().getId(), datasourcePermission.getExecutePermission())
Comment on lines +638 to +641
fallback.setPluginId(
editActionDTO.getDatasource().getPluginId());
fallback.setName(
editActionDTO.getDatasource().getName());
Comment on lines +1212 to +1213
.switchIfEmpty(Mono.just(new DatasourceConfiguration()))
.onErrorResume(e -> Mono.just(new DatasourceConfiguration()));
…dary paths (GHSA-rqp9-r5fr-4cc8)

The ACL-checked findById returns Mono.empty() (not an error) when the
user lacks EXECUTE_DATASOURCES. Three analytics/archival paths used
zipWith/zipWhen which collapsed the entire chain to empty on ACL miss,
causing silent data loss:

- updateUnpublishedAction: REST controller returned empty response
- deleteGivenNewAction: delete chain returned Mono.empty()
- archiveGivenNewAction: archive chain returned Mono.empty()

Add .switchIfEmpty() fallback before .onErrorResume() on all three
paths so the embedded datasource is used for analytics when the ACL
lookup yields empty.

Also:
- Preserve isAutoGenerated and datasourceConfiguration on the
  validateAction fallback datasource so saved invalid actions retain
  display-relevant fields.
- Add warning log in populateHintMessages onErrorResume to surface
  data-integrity issues instead of silently swallowing errors.
@subrata71

Copy link
Copy Markdown
Collaborator Author

Copilot Round 2 — All 5 comments addressed (commit 652071f)

1. updateUnpublishedAction (line 879) — empty signal collapses zipWhen

Added .switchIfEmpty(Mono.justOrEmpty(updatedActionDTO.getDatasource())) before .onErrorResume(). The ACL-checked findById returns Mono.empty() (not an error) when the user lacks EXECUTE_DATASOURCES, which was collapsing the zipWhen and causing the REST controller to return an empty response.

2. deleteGivenNewAction (line 1151) — empty signal collapses zipWith

Same fix — added .switchIfEmpty(Mono.justOrEmpty(action.getDatasource())). Without this, deleting an action when the user lacks execute on the datasource would silently return Mono.empty() instead of the deleted ActionDTO.

3. archiveGivenNewAction (line 1597) — empty signal collapses zipWith

Same pattern — added .switchIfEmpty(Mono.justOrEmpty(action.getDatasource())) to keep the archive chain alive.

4. validateAction fallback (line 641) — lost datasource fields

The fallback datasource now preserves isAutoGenerated and datasourceConfiguration from the client DTO in addition to pluginId and name. The id and workspaceId are intentionally NOT copied (security boundary). This ensures saved invalid actions retain display-relevant fields for downstream processing.

5. populateHintMessages (line 1213) — silent error swallowing

Added log.warn() in the onErrorResume handler to surface data-integrity issues (e.g., action referencing a deleted datasource) instead of silently returning an empty DatasourceConfiguration.

@subrata71 subrata71 requested a review from Copilot May 28, 2026 12:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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/newactions/base/NewActionServiceCEImpl.java (1)

632-643: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid persisting the full datasource config on ACL fallback.

When the ACL-scoped lookup misses, this fallback now embeds datasourceConfiguration into the action. That bypasses the normal external-datasource-by-reference flow at Lines 690-700 and can expose connection settings to users who can read the action but not the datasource. Keep only display-safe fields here, or redact the config before persisting.

Suggested minimal change
                                 Datasource clientDs = editActionDTO.getDatasource();
                                 Datasource fallback = new Datasource();
                                 fallback.setPluginId(clientDs.getPluginId());
                                 fallback.setName(clientDs.getName());
                                 fallback.setIsAutoGenerated(clientDs.getIsAutoGenerated());
-                                fallback.setDatasourceConfiguration(clientDs.getDatasourceConfiguration());
                                 return Mono.just(fallback);
                             }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java`
around lines 632 - 643, In the switchIfEmpty fallback in NewActionServiceCEImpl
(the block manipulating editActionDTO and creating Datasource fallback), do not
copy or set datasourceConfiguration onto the fallback Datasource; instead
construct the fallback with only display-safe fields (e.g., pluginId, name,
isAutoGenerated, and optionally id/display fields) and ensure
datasourceConfiguration is null or redacted so the full connection settings are
not embedded into the action DTO; update any subsequent code that expects a
config to rely on the external-datasource-by-reference flow rather than this
fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java`:
- Around line 632-643: In the switchIfEmpty fallback in NewActionServiceCEImpl
(the block manipulating editActionDTO and creating Datasource fallback), do not
copy or set datasourceConfiguration onto the fallback Datasource; instead
construct the fallback with only display-safe fields (e.g., pluginId, name,
isAutoGenerated, and optionally id/display fields) and ensure
datasourceConfiguration is null or redacted so the full connection settings are
not embedded into the action DTO; update any subsequent code that expects a
config to rely on the external-datasource-by-reference flow rather than this
fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c2e3476-f2be-4809-8bcc-162be66140e1

📥 Commits

Reviewing files that changed from the base of the PR and between 5340685 and 652071f.

📒 Files selected for processing (1)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@subrata71 subrata71 added the ok-to-test Required label for CI label May 28, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

This PR has not seen activitiy for a while. It will be closed in 7 days unless further activity is detected.

@github-actions github-actions Bot added the Stale label Jun 4, 2026
@github-actions

Copy link
Copy Markdown

This PR has been closed because of inactivity.

@github-actions github-actions Bot closed this Jun 15, 2026
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 Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants