Skip to content

fix(mothership): enforce ownership check on workflow resource attachments#4468

Merged
waleedlatif1 merged 7 commits intostagingfrom
fix/mothership-resource-attachment-bola
May 6, 2026
Merged

fix(mothership): enforce ownership check on workflow resource attachments#4468
waleedlatif1 merged 7 commits intostagingfrom
fix/mothership-resource-attachment-bola

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@waleedlatif1 waleedlatif1 commented May 6, 2026

Summary

  • Fixed BOLA vulnerability in Mothership chat where resourceAttachments of type workflow bypassed authorization
  • resolveActiveResourceContext was passing undefined for userId and workspaceId when calling processWorkflowFromDb, skipping the authorizeWorkflowByWorkspacePermission check entirely
  • Any authenticated user could inject a victim's workflow ID into resourceAttachments and read its full block config (system prompts, code, hardcoded secrets)
  • Fix: pass the authenticated userId and request workspaceId through so the existing ownership check runs normally

Credit: @Maxx191

Type of Change

  • Bug fix

Testing

Tested manually — existing legitimate resource attachment flow works unchanged. Cross-workspace workflow IDs are now rejected.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 6, 2026 5:59am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 6, 2026

PR Summary

High Risk
Touches authorization boundaries for copilot/mothership chat by enforcing workspace permission checks and scoping resource resolution to the request workspace; mistakes here could block legitimate access or reintroduce data exposure.

Overview
Fixes an authorization bypass in unified chat resource attachments by ensuring resolveActiveResourceContext receives the authenticated userId and request workspaceId, so workflow/knowledge ownership checks run for attached resources.

Tightens workspace scoping for table attachments by requiring a workspaceId and rejecting tables from other workspaces, and adds an explicit DB permission lookup in post.ts to reject mothership (workspace) chat requests when the user lacks workspace access.

Includes minor test/mock updates for the new DB query path and a small sleepUntilAborted timeout scoping fix in the webhook data-drain destination.

Reviewed by Cursor Bugbot for commit 8515dce. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 6, 2026

Greptile Summary

This PR fixes a BOLA (Broken Object Level Authorization) vulnerability in the Mothership copilot chat where resourceAttachments of type workflow, knowledgebase, and table bypassed ownership checks. It also adds an explicit workspace permission gate for the non-workflow branch of resolveBranch, and includes a minor variable-hoisting cleanup in webhook.ts.

  • Workflow BOLA fix: resolveActiveResourceContext now passes the authenticated userId and request workspaceId to processWorkflowFromDb, enabling the existing authorizeWorkflowByWorkspacePermission check that was silently skipped before.
  • Table BOLA fix: resolveTableResource now accepts workspaceId and rejects any table whose workspaceId doesn't match, closing the same cross-workspace read that affected workflows.
  • Knowledge base fix: processKnowledgeFromDb now receives the real userId instead of undefined, so the checkKnowledgeBaseAccess guard runs as designed.
  • Workspace permission check: A direct DB permission query is added for the workspace-only path in resolveBranch, ensuring users cannot target workspaces they don't belong to.

Confidence Score: 5/5

The changes close three cross-workspace read vulnerabilities and add a missing workspace membership gate; the logic is narrow, targeted, and backed by existing authorization helpers already in use on the corrected paths.

All three BOLA fixes correctly thread the authenticated userId and verified workspaceId into the existing authorization helpers that were already tested and trusted. The new workspace permission query in resolveBranch is straightforward and the mock in the test file validates the happy path. No new data paths are opened and no existing permission checks are weakened.

No files require special attention; the only minor point is that the access-denied response in post.ts uses a 400 status code where a 403 would be more semantically correct.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/chat/process-contents.ts Fixes three resource-attachment BOLA vulnerabilities: passes real userId/workspaceId to workflow, knowledge base, and table resolvers so all ownership checks now execute correctly.
apps/sim/lib/copilot/chat/post.ts Adds workspace membership check for the non-workflow branch; also always fetches the workflow's canonical workspaceId from DB rather than trusting the client-supplied value.
apps/sim/lib/copilot/chat/post.test.ts Adds mock for the new db.select/permissions query and the drizzle-orm and helper needed by the workspace permission check.
apps/sim/lib/data-drains/destinations/webhook.ts Minor cleanup: converts let timeoutId to const timeoutId to eliminate a temporal-dead-zone risk with no functional change.

Sequence Diagram

sequenceDiagram
    participant Client
    participant post.ts as post.ts (resolveBranch)
    participant process-contents.ts as resolveActiveResourceContext
    participant DB

    Client->>post.ts: POST /api/copilot { resourceAttachments, workspaceId }
    post.ts->>DB: resolveWorkflowIdForUser(userId, workflowId)
    DB-->>post.ts: resolved workflowId

    Note over post.ts: NEW: fetch canonical workspaceId from DB

    post.ts->>DB: getWorkflowById(resolvedWorkflowId)
    DB-->>post.ts: workflow.workspaceId

    alt No workflowId — workspace-only path
        Note over post.ts: NEW: workspace membership check
        post.ts->>DB: SELECT permissionType FROM permissions WHERE userId=? AND entityId=?
        DB-->>post.ts: permissionRow
        alt permissionRow is null
            post.ts-->>Client: 400 Workspace not found or access denied
        end
    end

    post.ts->>process-contents.ts: resolveActiveResourceContext(type, id, workspaceId, userId)

    alt type = workflow
        Note over process-contents.ts: NEW: passes real userId + workspaceId
        process-contents.ts->>DB: processWorkflowFromDb(id, userId, workspaceId)
        DB-->>process-contents.ts: authorizeWorkflowByWorkspacePermission check runs
    else type = knowledgebase
        Note over process-contents.ts: NEW: passes real userId
        process-contents.ts->>DB: checkKnowledgeBaseAccess(id, userId)
        DB-->>process-contents.ts: hasAccess result
    else type = table
        process-contents.ts->>DB: getTableById(tableId)
        DB-->>process-contents.ts: table record
        Note over process-contents.ts: NEW: table.workspaceId !== workspaceId → reject
    end

    process-contents.ts-->>Client: resource content or null
Loading

Reviews (4): Last reviewed commit: "fix(mothership): always derive workspace..." | Re-trigger Greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

Both findings are correct — fixed in the two follow-up commits.

Table (no ownership check): resolveTableResource now takes workspaceId and returns null if table.workspaceId !== workspaceId. Also patched the processContextsServer call site which had the same gap (and now guards on currentWorkspaceId being present before calling).

Knowledge base (userId not forwarded): processKnowledgeFromDb is now called with the real userId, so checkKnowledgeBaseAccess runs as intended.

Also audited the remaining two resource types for completeness — both were already clean: file uses getWorkspaceFile(workspaceId, fileId) which filters on both columns at the DB level, and folder already has eq(workflowFolder.workspaceId, workspaceId) in its query.

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/copilot/chat/process-contents.ts
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread apps/sim/lib/copilot/chat/post.ts
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8515dce. Configure here.

@waleedlatif1 waleedlatif1 merged commit a24e851 into staging May 6, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/mothership-resource-attachment-bola branch May 6, 2026 06:10
@Maxx191
Copy link
Copy Markdown

Maxx191 commented May 6, 2026 via email

waleedlatif1 added a commit that referenced this pull request May 7, 2026
…ents (#4468)

* fix(mothership): enforce ownership check on workflow resource attachments

* fix(mothership): fix table and knowledgebase BOLA in resource attachment resolution

* fix(mothership): apply workspace scope to table in processContextsServer

* fix(mothership): verify workspace membership before resolving workspace branch

* fix(data-drains): use const for timeoutId in sleepUntilAborted

* fix(test): mock db.select and drizzle and for workspace permissions check

* fix(mothership): always derive workspace from workflow record in workflow branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants