Skip to content

fix(pods): default GET /api/pods to membership-filtered listing#299

Closed
samxu01 wants to merge 1 commit intomainfrom
sam/pods-default-membership-filter
Closed

fix(pods): default GET /api/pods to membership-filtered listing#299
samxu01 wants to merge 1 commit intomainfrom
sam/pods-default-membership-filter

Conversation

@samxu01
Copy link
Copy Markdown
Contributor

@samxu01 samxu01 commented May 4, 2026

Summary

Fixes a long-standing isolation leak in GET /api/pods: the default listing returns every chat/team/study/games pod on the instance to every authenticated user. Only the three personal pod types (agent-admin/agent-room/agent-dm) were membership-gated previously. This commit extends the same gate to the default listing.

Repro on current main

A fresh test user on dev (created with no joined pods other than one) sees 58 pods in their V2 sidebar — none of which they're a member of:

```bash
curl -sS "$INSTANCE_URL/api/pods" -H "Authorization: Bearer $TEST_USER_TOKEN" | jq length

58

```

That's the entire active pod inventory of the dev instance leaking into a random test account's sidebar.

Behavior after this PR

Request Returns
GET /api/pods Pods the caller is a member of (default — same gate as agent-dm/room/admin had)
GET /api/pods?scope=all Every readable pod (legacy behavior — for admin tools, marketplace browse, pod-discovery surfaces)
GET /api/pods?type=chat Only chat pods the caller is in
GET /api/pods?type=agent-dm Unchanged (already membership-gated)

Global admins still bypass the filter on every code path — preserves the moderation surface for the 1:1 invariant on agent-rooms (ADR-001 §3.10).

Why now

Surfaced while staging accounts for a YC demo recording — a clean test user (Sam, Mike) had every dev-cluster pod showing in their sidebar, including unrelated agent-DMs between dev agents. The blast radius is bigger than the demo though: every multi-tenant Commonly instance has this leak.

Frontend impact

None. V2 sidebar's existing GET /api/pods call hits the default path and will get filtered listings post-deploy. Surfaces that legitimately need everything (admin tools, marketplace browse) need to add ?scope=all — none currently exist in main, so no changes needed today.

Test plan

  • tsc --noEmit clean
  • Smoke after deploy: same curl from a non-member token returns only joined pods
  • V2 sidebar on dev shows only the pods the operator account is in

🤖 Generated with Claude Code

Previous behavior: GET /api/pods returned every chat/team/study/games
pod on the instance to every authenticated user. Only the personal
pod types (agent-admin / agent-room / agent-dm) had membership gating.

This made the V2 sidebar unusable on a shared / multi-tenant
instance — a fresh user account would see dozens of unrelated pods
they have no business browsing. Especially bad for demos and test
accounts on the dev cluster, which now sees ~58 pods leak.

This commit extends the existing membership filter (used for the
three personal pod types) to the default listing. Behavior:

  GET /api/pods                — returns only pods the user is a member of
  GET /api/pods?scope=all      — returns every readable pod (legacy)
  GET /api/pods?type=chat      — returns only chat pods the user is in
  GET /api/pods?type=agent-dm  — unchanged (already membership-gated)

Global admins still bypass — preserves the audit surface for
moderating the 1:1 invariant on agent-rooms (ADR-001 §3.10).

Net diff: 11 lines in podController.getAllPods. No frontend
changes — V2 sidebar's existing call hits the default path and
will Just Work after deploy.

For surfaces that legitimately need to enumerate all pods (admin
tools, future marketplace browse, pod-discovery), pass
`?scope=all` explicitly. That path still requires the user have
read permission via the existing pod ACL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
samxu01 added a commit that referenced this pull request May 4, 2026
Extends the existing membership filter from agent-admin/agent-room/
agent-dm to chat/team/study/games on the default listing. Closes the
isolation leak where every authenticated user saw every pod on the
instance.

Pass ?scope=all to opt into the legacy "list everything readable"
behavior for admin tools, marketplace, pod-discovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@samxu01 samxu01 closed this May 4, 2026
@samxu01 samxu01 deleted the sam/pods-default-membership-filter branch May 4, 2026 07:44
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