Skip to content

fix(security): Appsmith App Viewer datasource configuration leak via import helper (GHSA-93mf-9h52-gfxp)#41764

Merged
subrata71 merged 2 commits into
releasefrom
fix/datasource-import-authz-ghsa-93mf-9h52-gfxp
Apr 27, 2026
Merged

fix(security): Appsmith App Viewer datasource configuration leak via import helper (GHSA-93mf-9h52-gfxp)#41764
subrata71 merged 2 commits into
releasefrom
fix/datasource-import-authz-ghsa-93mf-9h52-gfxp

Conversation

@subrata71
Copy link
Copy Markdown
Collaborator

@subrata71 subrata71 commented Apr 26, 2026

Summary

fix(security): Appsmith App Viewer datasource configuration leak via import helper (GHSA-93mf-9h52-gfxp)

  • Primary fix: DatasourceImportableServiceCEImpl.getEntitiesPresentInWorkspace() was calling getAllByWorkspaceIdWithStorages(workspaceId, null) — passing null as the ACL permission, which bypasses authorization entirely. Changed to enforce datasourcePermission.getReadPermission() (READ_DATASOURCES), consistent with the standard datasource listing endpoint.
  • Constructor update: Injected DatasourcePermission into both DatasourceImportableServiceCEImpl and DatasourceImportableServiceImpl constructors (Spring auto-wires the bean).
  • Test coverage: Added a regression test (should_enforceReadPermission_when_getEntitiesPresentInWorkspace_isCalled) annotated with GHSA ID, verifying that READ_DATASOURCES permission is always passed and null is never used.

Vulnerability

Field Value
GHSA GHSA-93mf-9h52-gfxp
CVSS 7.7 (high)
CWE CWE-200, CWE-862
Affected component DatasourceImportableServiceCEImpl.getEntitiesPresentInWorkspace()

Exposure Analysis

  • Who can exploit this: Any authenticated user with App Viewer access to a workspace. This is the lowest privilege role — no admin, developer, or owner access required.
  • What an attacker can achieve: Full read access to datasource configuration for all datasources in the workspace, including internal URLs, custom headers (e.g., Authorization: Bearer <token>), custom properties (e.g., API keys), and connection metadata. This is a confidentiality breach of backend infrastructure credentials.
  • Evidence of exploitation: No evidence of exploitation in the wild. Discovered via security advisory report.
  • Blast radius: All datasources in the target workspace are exposed. The attack is per-workspace — an App Viewer in workspace A cannot access workspace B's datasources. However, within a workspace, ALL datasource secrets are leaked regardless of which application they belong to.

Fix

  • Root cause: DatasourceImportableServiceCEImpl.getEntitiesPresentInWorkspace() (line 602) called datasourceService.getAllByWorkspaceIdWithStorages(workspaceId, null). When AclPermission is null, the MongoDB repository layer skips ACL filtering and returns all matching documents, bypassing the policy-based permission model.
  • Fix strategy: Inject DatasourcePermission into the service and replace null with datasourcePermission.getReadPermission() (AclPermission.READ_DATASOURCES). This is the same permission used by the standard GET /api/v1/datasources?workspaceId= endpoint — consistent, minimal, and at the correct layer.
  • Intentionally NOT changed: (1) importEntities() in the same class (line 117) also uses null permission — this is intentional because the import pipeline has its own authorization layer (edit permission on existing datasources, create permission on workspace). (2) DatasourceForkableServiceCEImpl.getExistingEntitiesInTarget() also uses null — fork operations require higher-level permissions upstream.
  • Defense-in-depth: The fix is at the data-access layer, which is the correct boundary. All callers of getEntitiesPresentInWorkspace() automatically inherit the permission enforcement.

Test plan

  • Failing test for the exploit scenario passes after fix
  • All existing tests in affected modules pass (no regressions) — 9/9 tests green
  • Codebase audit: grepped for getAllByWorkspaceIdWithStorages.*null — remaining instances are in authorized internal pipelines
  • Compilation clean (mvn compile -DskipTests)
  • Manual reproduction of advisory PoC confirms the fix

CE/EE sync

CE-only safe: the modified files (DatasourceImportableServiceCEImpl.java, DatasourceImportableServiceImpl.java) are CE files that EE inherits. The hourly CE→EE sync will propagate the fix. The EE repo already has identical changes as uncommitted modifications, confirming alignment.

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

Follow-ups

  • No additional vulnerable locations found during codebase audit — no follow-up PRs needed.

Fixes APP-15189
Fixes https://linear.app/appsmith/issue/APP-15045/security-high-app-viewer-datasource-configuration-leak-via-import

Summary by CodeRabbit

  • Bug Fixes

    • Datasource listings now respect computed read permissions so users only see workspace datasources they are allowed to access.
  • Tests

    • Added tests validating read-permission enforcement for datasource visibility to prevent unauthorized exposure.

Automation

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

Tip

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


Mon, 27 Apr 2026 08:09:14 UTC

…sting (GHSA-93mf-9h52-gfxp)

DatasourceImportableServiceCEImpl.getEntitiesPresentInWorkspace() was
calling getAllByWorkspaceIdWithStorages with null permission, bypassing
ACL checks. This allowed App Viewer users to retrieve sensitive
datasource configuration (internal URLs, Bearer tokens, API keys) via
GET /api/v1/applications/import/{workspaceId}/datasources.

Inject DatasourcePermission and enforce READ_DATASOURCES permission on
this method, consistent with the standard datasource listing endpoint.

The null permission in importEntities() is intentional — that pipeline
has its own authorization layer (edit/create permission checks).
@subrata71 subrata71 requested a review from a team as a code owner April 26, 2026 18:25
@subrata71 subrata71 added the Security Issues related to information security within the product label Apr 26, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Walkthrough

Constructor injection of DatasourcePermission was added to datasource importable services; getEntitiesPresentInWorkspace now calls datasourceService.getAllByWorkspaceIdWithStorages(workspaceId, datasourcePermission.getReadPermission()) instead of passing null, and tests updated to assert permission-scoped behavior.

Changes

Cohort / File(s) Summary
Datasource Importable CE
app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceCEImpl.java
Add DatasourcePermission field and constructor parameter; change getEntitiesPresentInWorkspace to use datasourcePermission.getReadPermission() instead of null.
Datasource Importable Impl
app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceImpl.java
Update constructor to accept and forward DatasourcePermission to superclass; add necessary import.
Unit Test
app/server/appsmith-server/src/test/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceCEImplTest.java
Add mocked DatasourcePermission, stub getReadPermission() to AclPermission.READ_DATASOURCES, verify datasourceService.getAllByWorkspaceIdWithStorages is called with the read permission and null-variant is never invoked.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🔧 A constructor grows a single key,
Permissions wake where null used to be,
Services call with scoped intent,
Tests now guard the read event,
A small fix, but safe and free.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 identifies the security fix (ACL permission enforcement in datasource import) and references the GHSA advisory, matching the changeset's primary objective.
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 critical elements: vulnerability details, root cause, fix strategy, test coverage, and disclosure guidance.

✏️ 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/datasource-import-authz-ghsa-93mf-9h52-gfxp

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.

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/test/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceCEImplTest.java (1)

246-267: Consider also covering the integration boundary (controller → service).

The unit test confirms getEntitiesPresentInWorkspace passes the read permission, but the original PoC was against GET /api/v1/applications/import/{workspaceId}/datasources. A small integration/controller test that calls the endpoint as an App Viewer and asserts the response is empty/forbidden would close the loop end-to-end and guard against future regressions in the controller wiring.

🤖 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/datasources/importable/DatasourceImportableServiceCEImplTest.java`
around lines 246 - 267, Add an integration/controller test that exercises the
actual endpoint GET /api/v1/applications/import/{workspaceId}/datasources (the
controller method that forwards to importService.getEntitiesPresentInWorkspace)
to verify the permission boundary end-to-end: set up a test user with the App
Viewer role (or mock the security context accordingly), call the endpoint with a
workspaceId, and assert the HTTP response is either 403 Forbidden or an empty
body as expected; ensure the test verifies the controller invoked
importService.getEntitiesPresentInWorkspace and that no higher-privilege
behavior occurs when only READ_DATASOURCES is granted.
🤖 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/test/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceCEImplTest.java`:
- Around line 246-267: Add an integration/controller test that exercises the
actual endpoint GET /api/v1/applications/import/{workspaceId}/datasources (the
controller method that forwards to importService.getEntitiesPresentInWorkspace)
to verify the permission boundary end-to-end: set up a test user with the App
Viewer role (or mock the security context accordingly), call the endpoint with a
workspaceId, and assert the HTTP response is either 403 Forbidden or an empty
body as expected; ensure the test verifies the controller invoked
importService.getEntitiesPresentInWorkspace and that no higher-privilege
behavior occurs when only READ_DATASOURCES is granted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb9e40f5-0e49-4c5c-9fea-9e616697ac56

📥 Commits

Reviewing files that changed from the base of the PR and between 99d6918 and 5b9deb5.

📒 Files selected for processing (3)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/datasources/importable/DatasourceImportableServiceCEImplTest.java

@subrata71 subrata71 self-assigned this Apr 27, 2026
@Override
public Flux<Datasource> getEntitiesPresentInWorkspace(String workspaceId) {
return datasourceService.getAllByWorkspaceIdWithStorages(workspaceId, null);
return datasourceService.getAllByWorkspaceIdWithStorages(workspaceId, datasourcePermission.getReadPermission());
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 is this call used for, do we need this for some internal operation? IMO as long as we are not showing this to user or comparing it, this should be fine?

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.

This method is not internal-only — it feeds directly into the controller endpoint GET /api/v1/applications/import/{workspaceId}/datasources (see ApplicationControllerCE.getUnConfiguredDatasource() at line 286).

The call chain is:

  1. getEntitiesPresentInWorkspace(workspaceId)Flux<Datasource>
  2. ImportServiceCEImpl.findDatasourceByArtifactId() filters by datasource IDs used in the artifact
  3. ApplicationControllerCE.getUnConfiguredDatasource() wraps in ResponseDTO<List<Datasource>> and returns it with @JsonView(Views.Public.class)

The full Datasource objects including datasourceConfiguration (URLs, Bearer tokens, API keys) are serialized to the HTTP response. This is exactly the vulnerability described in the GHSA — an App Viewer who cannot list datasources via the normal API (GET /api/v1/datasources) can use this endpoint to read the same data without READ_DATASOURCES permission.

The null permission in importEntities() (line 117, same file) is a different story — that path is behind import-specific authorization checks and doesn't expose data to HTTP responses.

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.

could we create two separate methods, one internal, and one which controller can see through.
The idea is that we might have workflows, which should not require permission (sometimes for internal gatekeeping or something)

@subrata71 subrata71 requested a review from sondermanish April 27, 2026 10:57
@subrata71 subrata71 merged commit a92dee4 into release Apr 27, 2026
88 of 89 checks passed
@subrata71 subrata71 deleted the fix/datasource-import-authz-ghsa-93mf-9h52-gfxp branch April 27, 2026 14:10
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.

2 participants