GitHub Security Advisory: GHSA-xvf4-ch4q-2m24
Package: studiocms (NPM)
Severity: LOW (CVSS 2.7)
CWE: CWE-639: Authorization Bypass Through User-Controlled Key
Vulnerable versions: <= 0.4.3
Patched in: 0.4.4

Summary: StudioCMS REST getUsers Exposes Owner Account Records to Admin Tokens

Details:
## Summary

The REST API `getUsers` endpoint in StudioCMS uses the attacker-controlled `rank` query parameter to decide whether owner accounts should be filtered from the result set. As a result, an admin token can request `rank=owner` and receive owner account records, including IDs, usernames, display names, and email addresses, even though the adjacent `getUser` endpoint correctly blocks admins from viewing owner users. This is an authorization inconsistency inside the same user-management surface.

## Details

### Vulnerable Code Path

File: `D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts`, lines 1605-1647

```ts
.handle(
    'getUsers',
    Effect.fn(
        function* ({ urlParams: { name, rank, username } }) {
            if (!restAPIEnabled) {
                return yield* new RestAPIError({ error: 'Endpoint not found' });
            }
            const [sdk, user] = yield* Effect.all([SDKCore, CurrentRestAPIUser]);

            if (user.rank !== 'owner' && user.rank !== 'admin') {
                return yield* new RestAPIError({ error: 'Unauthorized' });
            }

            const allUsers = yield* sdk.GET.users.all();
            let data = allUsers.map(...);

            if (rank !== 'owner') {
                data = data.filter((user) => user.rank !== 'owner');
            }

            if (rank) {
                data = data.filter((user) => user.rank === rank);
            }

            return data;
        },
```

The `rank` variable in `if (rank !== 'owner')` is the request query parameter, not the caller's privilege level. An admin can therefore pass `rank=owner`, skip the owner-filtering branch, and then have the second `if (rank)` branch return only owner accounts.

### Adjacent Endpoint Shows Intended Security Boundary

File: `D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts`, lines 1650-1710

```ts
const existingUserRankIndex = availablePermissionRanks.indexOf(existingUserRank);
const loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);

if (loggedInUserRankIndex <= existingUserRankIndex) {
    return yield* new RestAPIError({
        error: 'Unauthorized to view user with higher rank',
    });
}
```

`getUser` correctly blocks an admin from viewing an owner record. `getUsers` bypasses that boundary for bulk enumeration.

### Sensitive Fields Returned

The `getUsers` response includes:

- `id`
- `email`
- `name`
- `username`
- `rank`
- timestamps and profile URL/avatar fields when present

This is enough to enumerate all owner accounts and target them for phishing, social engineering, or follow-on attacks against out-of-band workflows.

## PoC

### HTTP PoC

Use any admin-level REST API token:

```bash
curl -X GET 'http://localhost:4321/studiocms_api/rest/v1/secure/users?rank=owner' \
  -H 'Authorization: Bearer <admin-api-token>'
```

Expected behavior:
- owner records should be exclu