Skip to content

fix(realtime): evict revoked collaborators from live workflow rooms#5917

Open
icecrasher321 wants to merge 10 commits into
stagingfrom
staging-v9
Open

fix(realtime): evict revoked collaborators from live workflow rooms#5917
icecrasher321 wants to merge 10 commits into
stagingfrom
staging-v9

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

Evict revoked collaborators from live workflow rooms via periodic read-access re-validation

Type of Change

  • Other: Security Improvement

Testing

Tested manually

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 Jul 24, 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 Jul 24, 2026 4:40am

Request Review

@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches authorization, eviction, and join races in the realtime security path; incorrect eviction or cache ordering could wrongly kick users or briefly retain access after revocation.

Overview
Closes the gap where revoked collaborators could keep receiving live workflow reads until disconnect, by adding a per-pod periodic sweep (aligned with the existing ~30s role cache TTL) that re-checks workspace permissions and evicts only on a definitive null role—never on DB blips or hung queries.

The new access-revalidation loop runs a Redis-free security scan (emit access-revoked, leave room) separately from deferred presence cleanup, with per-socket timeouts, pass budgets, round-robin scanning, and retry queues so Redis/DB stalls cannot wedge enforcement.

Permission layer changes: resolveCurrentWorkflowRole gains single-flight coalescing and fresher-wins cache writes; verifyWorkflowAccess records join decisions into the same cache; join-workflow re-authorizes immediately before socket.join. Room removeUserFromRoom (memory + Redis Lua) honors an explicit workflow hint and avoids clearing mappings when the socket has moved to another workflow.

Client: AccessRevokedBroadcast in the protocol; socket provider and join controller treat revocation like deletion (block re-join, blocked-join UX); collaborative workflow hook stops undo/collab on the active workflow.

Reviewed by Cursor Bugbot for commit 06784c6. Configure here.

Comment thread apps/sim/app/workspace/providers/socket-provider.tsx
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds periodic realtime access revalidation and client-side revocation handling, with bounded round-robin scans, race-safe authorization caching, and deferred presence cleanup.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failures remain.

Important Files Changed

Filename Overview
apps/realtime/src/access-revalidation.ts Adds bounded periodic authorization scans, immediate socket eviction, round-robin progress, and independent retryable presence cleanup.
apps/realtime/src/middleware/permissions.ts Adds single-flight role resolution and authoritative cache refreshes to coordinate joins, operation checks, and revocation scans.
apps/realtime/src/handlers/workflow.ts Revalidates access immediately before joining a workflow room to prevent a racing join from reversing eviction.
apps/realtime/src/rooms/redis-manager.ts Makes hinted room removal atomic while preserving socket mappings for replacement workflow membership.
apps/realtime/src/rooms/memory-manager.ts Aligns in-memory hinted-removal behavior with Redis while preserving replacement workflow mappings.
apps/sim/app/workspace/providers/socket-provider.tsx Handles access-revoked broadcasts by clearing membership state, blocking rejoin, and enforcing revoked-workflow UX.
packages/realtime-protocol/src/events.ts Defines the shared access-revoked broadcast payload.

Reviews (9): Last reviewed commit: "fix(realtime): round-robin the revocatio..." | Re-trigger Greptile

Comment thread apps/realtime/src/access-revalidation.ts
Comment thread apps/realtime/src/access-revalidation.ts Outdated
…endent

Re-authorize immediately before socket.join so an in-flight join cannot
reverse a sweep eviction, and run the sweep's best-effort cleanups
independently so a room-state failure cannot skip the presence broadcast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/handlers/workflow.ts
Comment thread apps/realtime/src/access-revalidation.ts Outdated

@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 d9f721d. Configure here.

…n cleanup

Coalesce concurrent role resolutions per (user, workflow) so a slow stale
read can never overwrite a recorded revocation, and defer failed eviction
room-state cleanups into a per-sweep retry queue so collaborators are not
left with a stale presence entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/access-revalidation.ts Outdated
Comment thread apps/realtime/src/access-revalidation.ts
…wallowed cleanup failures

Honor the workflowIdHint as the target room in both room managers so
removing a stale room cannot clobber the mapping of a room the socket has
since moved to, and confirm eviction cleanup via the returned workflowId
plus an unswallowed mapping read so Redis failures actually defer into the
retry queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/access-revalidation.ts Outdated
Comment thread apps/realtime/src/handlers/workflow.ts
Comment thread apps/realtime/src/access-revalidation.ts
…e cache on fresh verify

Treat any null removal result as a failed cleanup (the sweep always passes
the target room, so null only means failure — including with expired
mapping keys), move the same-room rejoin guard to a synchronous check
immediately before the removal, and record verifyWorkflowAccess's fresh
decision into the role cache so a re-granted user is not blocked by a
stale cached revocation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/access-revalidation.ts
Comment thread apps/realtime/src/middleware/permissions.ts
…-flight query result

If a fresh authoritative read (join-time verify) records a decision while
a single-flighted resolution's query is in flight, keep the recorded
decision instead of overwriting it with the potentially stale result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/access-revalidation.ts Outdated
Comment thread apps/realtime/src/handlers/workflow.ts
Run the revocation scan (local sockets + DB only) and the best-effort
room-state cleanup as independently-guarded lanes so a hanging Redis
command can stall only presence cleanup, never revocation enforcement.
Evictions now enqueue cleanup instead of awaiting it, and the scan no
longer reads presence for a fallback role.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/access-revalidation.ts Outdated
Comment thread apps/realtime/src/access-revalidation.ts
Race each socket's authorization check against a per-socket timeout and
cap the whole pass with a budget below the sweep interval, so a hanging
DB query skips that socket for the pass (never evicting on uncertainty)
instead of wedging the scan lane and starving subsequent ticks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/access-revalidation.ts Outdated

@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 ead74af. Configure here.

…starve later sockets

Resume each scan pass after the last target the previous pass processed,
so a fixed prefix of hanging authorization checks can never repeatedly
consume the pass budget and leave sockets behind it unexamined.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

@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 06784c6. Configure here.

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