briefercloud / briefercloud/briefer

Security: Cross-Workspace Password Reset IDOR + Schedule Injection + Component Instance IDOR (CWE-639)

Open
#369 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
4.3k
Forks
290
PR merge metrics
No merged PRs in 30d

Description

## Summary

Multiple authorization bypass vulnerabilities allow cross-workspace operations. The most critical allows an admin of Workspace A to reset the password of ANY user in the system (including users exclusively in Workspace B), with the new plaintext password returned in the response.

**Severity:** HIGH (account takeover via cross-workspace password reset)

## Finding 1: Cross-Workspace Password Reset (HIGH)

**File:** `apps/api/src/v1/workspaces/workspace/users/user.ts`, lines 169-210

```typescript
userRouter.post('/reset-password', async (req, res) => {
const workspaceId = getParam(req, 'workspaceId')
const userId = getParam(req, 'userId')

if (req.session.userWorkspaces[workspaceId]?.role !== UserWorkspaceRole.admin) {
res.sendStatus(403)
return
}

const user = await prisma().user.findUnique({
where: { id: userId }, // <-- No workspace scoping!
select: { email: true },
})
// ...
await prisma().user.update({
where: { id: userId }, // <-- No workspace scoping!
data: { passwordDigest },
})
res.json({ password }) // <-- Plaintext password in response
})
```

The endpoint verifies the caller is admin of the workspace in the URL, but fetches/updates the target user from the global `User` table by raw ID without verifying the target is a member of that workspace.

**Secure comparison** (adjacent PUT endpoint in same file):
```typescript
const user = await prisma().userWorkspace.update({
where: { userId_workspaceId: { userId, workspaceId } }, // Compound key!
...
})
```

## Finding 2: Cross-Workspace Schedule Injection (MEDIUM)

**File:** `apps/api/src/v1/workspaces/workspace/documents/document/schedules/index.ts`, lines 19-36

The POST endpoint validates that the URL's `:documentId` belongs to the workspace, but creates the schedule using `documentId` from `req.body.scheduleParams` — an unvalidated value. An attacker can create a schedule targeting a document in another workspace.

**Secure comparison:** The `belongsToDoc` middleware on individual schedule routes correctly validates the schedule's documentId matches the URL's documentId.

## Finding 3: Component Instance IDOR (LOW-MEDIUM)

**File:** `apps/api/src/v1/workspaces/workspace/components.ts`, lines 195-250

- **Create:** Neither `componentId` (URL) nor `documentId`/`blockId` (body) validated against workspace
- **Delete:** Deletes by `blockId` alone without workspace check; `componentId` from URL unused in query

**Secure comparison:** Adjacent PUT and DELETE on `/:componentId` correctly validate workspace ownership via `component.document.workspaceId !== workspaceId`.

## Suggested Fix

**Finding 1:** After fetching user, verify they are a member of the workspace:
```typescript
const membership = await prisma().userWorkspace.findUnique({
where: { userId_workspaceId: { userId, workspaceId } }
})
if (!membership) { res.sendStatus(404); return; }
```

**Finding 2:** Use URL's `documentId` instead of body's when creating schedule.

**Finding 3:** Add workspace validation matching the parent component endpoints.

## Disclosure

Found via static code analysis. Reporting responsibly.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.