Skip to content

fix(chat): fail closed when embed gate cannot resolve workspace#5046

Merged
waleedlatif1 merged 1 commit into
stagingfrom
fix/chat-embed-gate-fail-closed
Jun 15, 2026
Merged

fix(chat): fail closed when embed gate cannot resolve workspace#5046
waleedlatif1 merged 1 commit into
stagingfrom
fix/chat-embed-gate-fail-closed

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Harden the cross-origin chat embed paywall (assertChatEmbedAllowed) to fail closed when the workflow row can't be resolved (archived/deleted), instead of inheriting isWorkspaceApiExecutionEntitled's undefined → entitled default and silently skipping the gate.
  • Defense-in-depth only: the triggering state is currently unreachable — archiving a workflow atomically archives + deactivates its chat in the same transaction (lib/workflows/lifecycle.ts), and the route filters chats on isNull(archivedAt) + isActive before the gate runs. This guards against a future refactor breaking that invariant.
  • Added a test covering the missing-workspace path; wired the embed tests to the chainable @sim/db mock so the workflow lookup is controllable.

Type of Change

  • Bug fix (hardening)

Testing

  • vitest run app/api/chat/utils.test.ts — 23 passed
  • vitest run app/api/chat/[identifier]/route.test.ts — 16 passed

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

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 15, 2026 1:47am

Request Review

@cursor

cursor Bot commented Jun 15, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Small, defensive change to embed authorization with no change to happy-path paid/free gating; low blast radius beyond external embed 403 behavior.

Overview
Cross-origin chat embed gating in assertChatEmbedAllowed now blocks third-party embeds when the workflow lookup does not yield a workspaceId (missing/archived row), returning 403 with "This chat is currently unavailable" instead of calling isWorkspaceApiExecutionEntitled with undefined, which treats missing workspace as entitled and would skip the paywall.

Paid-plan enforcement still runs only after a resolved workspaceId. Tests mock @sim/db via dbChainMock, default the workflow query to ws-1, and add a case asserting 403 and that entitlement is not invoked when the DB returns no row.

Reviewed by Cursor Bugbot for commit 0d8ebc8. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the assertChatEmbedAllowed gate in the chat embed paywall to fail closed when the workflow DB lookup returns no row (or a row with no workspaceId), rather than silently passing undefined into isWorkspaceApiExecutionEntitled and inheriting its permissive default.

  • utils.ts: Adds an explicit early return of 403 with a log warning when wf?.workspaceId is falsy, then calls isWorkspaceApiExecutionEntitled with the resolved (non-nullable) workspace ID.
  • utils.test.ts: Wires the assertChatEmbedAllowed test suite to a chainable @sim/db mock (via dbChainMock), sets a default workspace resolution in beforeEach, and adds a new test verifying the missing-workspace path returns 403 without invoking the entitlement check.

Confidence Score: 5/5

Safe to merge; the change is a small, well-scoped guard that only tightens access and cannot introduce a regression in the allow path.

The guard is correct — !wf?.workspaceId covers both an empty DB result and a null workspaceId column, and the entitlement check below now receives a guaranteed non-nullable string. The new test directly exercises the missing-workspace code path and confirms the short-circuit. Existing tests remain green because beforeEach sets a default workspace mock that keeps the happy path intact.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/api/chat/utils.ts Adds an explicit null-guard before the entitlement check; correctly fails closed (403) when no active workspace is found, eliminating the prior undefined → entitled silent bypass.
apps/sim/app/api/chat/utils.test.ts Integrates the @sim/db chain mock into the embed-allowed suite; adds a test for the missing-workspace 403 path and sets a correct default in beforeEach so existing tests remain unaffected.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[assertChatEmbedAllowed called] --> B{Billing & gate flags enabled?}
    B -- No --> Z[return null — allow]
    B -- Yes --> C{Origin header present\nand non-first-party?}
    C -- No --> Z
    C -- Yes --> D[DB: SELECT workspaceId\nWHERE id=workflowId\nAND archivedAt IS NULL]
    D --> E{wf?.workspaceId\ntruthy?}
    E -- No: NEW fail closed --> F[warn + return 403\n'chat unavailable']
    E -- Yes --> G[isWorkspaceApiExecutionEntitled\nworkspaceId]
    G --> H{Entitled?}
    H -- No --> I[warn + return 403\n'paid plan required']
    H -- Yes --> Z
Loading

Reviews (1): Last reviewed commit: "fix(chat): fail closed when embed gate c..." | Re-trigger Greptile

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 0d8ebc8. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the assertChatEmbedAllowed paywall gate by failing closed when the queried workflow row has no resolvable workspace, replacing the prior behavior where undefined passed into isWorkspaceApiExecutionEntitled could silently evaluate as entitled. The test suite is also updated to wire @sim/db through the chainable dbChainMock so the DB result is controllable per-test.

  • utils.ts: Adds an explicit !wf?.workspaceId guard that returns a 403 before the billing entitlement check; the entitlement call now always receives a concrete, non-null workspaceId.
  • utils.test.ts: Mounts the chainable @sim/db mock, sets a sane default workspace row in beforeEach, and adds a new test covering the empty-result code path.

Confidence Score: 5/5

Safe to merge — the change tightens a security gate and is backed by a new test that exercises the previously unguarded code path.

Both changed files are small and self-contained. The guard added to assertChatEmbedAllowed correctly handles all falsy workspaceId states (missing row, null column) and narrows the type so the downstream entitlement call always receives a concrete string. The test wiring follows the established @sim/testing dbChainMock pattern, uses mockResolvedValueOnce for the one-shot override, and the beforeEach re-establishes the permanent default on every test — no ordering hazards observed.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/api/chat/utils.ts Adds a fail-closed guard before the billing entitlement check; logic is correct and the TypeScript narrowing on wf.workspaceId is sound after the guard.
apps/sim/app/api/chat/utils.test.ts Wires the chainable dbChainMock correctly via vi.mock, resets the default result in beforeEach, and the new test properly uses mockResolvedValueOnce([]) for a one-shot empty-result override.

Sequence Diagram

sequenceDiagram
    participant Client as External Embed
    participant Gate as assertChatEmbedAllowed
    participant DB as Database (workflow table)
    participant Billing as isWorkspaceApiExecutionEntitled

    Client->>Gate: request (cross-origin)
    Gate->>Gate: check billing/gate flags enabled
    Gate->>Gate: check isFirstPartyOrigin → false
    Gate->>DB: "SELECT workspaceId FROM workflow WHERE id=? AND archivedAt IS NULL LIMIT 1"
    alt Workflow not found / no workspaceId (new guard)
        DB-->>Gate: []
        Gate-->>Client: 403 — This chat is currently unavailable
    else Workspace found
        DB-->>Gate: "[{ workspaceId }]"
        Gate->>Billing: isWorkspaceApiExecutionEntitled(workspaceId)
        alt Not entitled (free plan)
            Billing-->>Gate: false
            Gate-->>Client: 403 — Requires paid plan
        else Entitled
            Billing-->>Gate: true
            Gate-->>Client: null (allow)
        end
    end
Loading

Reviews (2): Last reviewed commit: "fix(chat): fail closed when embed gate c..." | Re-trigger Greptile

@waleedlatif1 waleedlatif1 merged commit 3ada4a3 into staging Jun 15, 2026
15 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/chat-embed-gate-fail-closed branch June 15, 2026 01:53
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.

1 participant