Skip to content

fix(agents): scope worker verbs, cap conversations, bound listings [session-tools] - #2269

Merged
2witstudios merged 1 commit into
masterfrom
pu/audit-session-tools
Jul 30, 2026
Merged

2witstudios merged 1 commit into
masterfrom
pu/audit-session-tools

Conversation

@2witstudios

Copy link
Copy Markdown
Owner

Summary

Full fix for issue #2262 — the six findings from the post-merge audit of PR #2258's session-tools slice (apps/web/src/lib/ai/tools/session-tools*.ts, apps/web/src/lib/agent-sessions/agent-sessions-runtime.ts):

  1. Workspace scoping (MEDIUM, security). send_session/read_session/kill_session now resolve the caller's own workspace (findOwnWorkspace) and require the target conversation's sessionId binding to match it — mirroring openOwnShell's existing check. Previously ownership of the conversation row alone was sufficient, so a prompt-injected agent could reach any conversation its user owned: a different session, a different drive, or a session-less thread — and exfiltrate a transcript or dispatch turns into a foreign sandbox.
  2. Per-session conversation cap. planSpawnWorkerSession takes a required sessionConversationCount input and enforces MAX_SESSION_CONVERSATIONS (100). The same cap is enforced as a backstop inside createConversationInSessionWith — the one write path both HTTP conversation-create routes and the tool's spawn dep funnel through — so worker minting is bounded on the tool path AND the HTTP path. New SessionFullError / sessionConversationLimitExceeded (429), with truthful denial copy (the old concurrency_exceeded message told callers to kill_session, which frees no counted slot).
  3. No raw error strings into model context. The worker-dispatch fetch failure and worker-create catch now return fixed messages; the real error is logged server-side.
  4. SQL-bounded listings. listSessionWorkers gets a LIMIT; listSessionConversationsBulk is bounded via a ROW_NUMBER() OVER (PARTITION BY sessionId ...) window filter (preserving per-session fairness, which a flat LIMIT would lose) instead of pulling the full row set into JS before capping it.
  5. Truthful guidance. list_sessions' no-session note no longer claims spawn_session/spawn_shell will start a session — both refuse there too, post-unconflation.
  6. Documented exposure decision. The shared-session metadata semantics (session members' agents see titles/activity of all conversations in the session; transcripts stay owner-gated) are now written down as deliberate, at the two query sites.

Test plan

  • New tests in session-tools.test.ts pin: cross-session-same-owner refusal, session-less-target refusal, session-less-caller refusal — identical failure shape to a nonexistent session.
  • New tests in plan-spawn-session.test.ts pin the conversation-cap ordering (account cap before session cap) and the ceiling value.
  • New tests in create-conversation-in-session.test.ts pin the HTTP-path cap, including that an idempotent retry at the ceiling is still allowed through.
  • bun run typecheck && bun run lint && bun run test:unit && bun run knip:check all pass.
  • bun run test:security — 44/50 suites pass; the 6 failures (Session Service, Device Auth Utilities, Permissions, Login/Signup/Mobile-Login Routes) are pre-existing test-security.sh path/config drift unrelated to this change (confirmed unchanged vs origin/master, and touching none of the files in this diff).
  • cd packages/lib && bun run typecheck passes.

Closes #2262

🤖 Generated with Claude Code

https://claude.ai/code/session_01ANkaVygvTgwLNwe6y9UGtT

…ons, bound listings

Post-merge audit follow-up on PR #2258's session-tools slice (six-slice
adversarial review of the agent-session un-conflation).

- send_session/read_session/kill_session now resolve the caller's own
  workspace (findOwnWorkspace) and require the target conversation's
  session binding to match, exactly like openOwnShell already does.
  Ownership of a conversation row was not enough: a caller could reach
  any conversation they owned, including ones in a different session,
  a different drive, or with no session at all.
- planSpawnWorkerSession takes a required sessionConversationCount and
  enforces MAX_SESSION_CONVERSATIONS (100) per session; the same cap is
  a backstop inside createConversationInSessionWith (the one write path
  both HTTP conversation-create routes and the tool's spawn dep funnel
  through), so worker minting is bounded on both the tool and HTTP
  paths. New SessionFullError / sessionConversationLimitExceeded (429).
- Raw error.message no longer reaches model context from the worker
  dispatch or worker-create paths; fixed messages are returned and the
  real error is logged server-side.
- listSessionWorkers and listSessionConversationsBulk are now bounded
  in SQL (a LIMIT and a ROW_NUMBER() OVER (PARTITION BY sessionId ...)
  window filter, respectively) instead of relying on a JS-side cap
  after pulling the full row set into memory.
- list_sessions' no-session guidance no longer claims spawn_session/
  spawn_shell will start a session — both refuse there too.
- Documented the shared-session metadata-exposure semantics (listings
  show titles/agents of every conversation in a session to any member;
  transcripts stay owner-gated) as deliberate, where the queries live.

Closes #2262

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANkaVygvTgwLNwe6y9UGtT
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@2witstudios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 74d95ab4-e441-4536-9cc9-0dfcba3e9657

📥 Commits

Reviewing files that changed from the base of the PR and between 4720452 and edb1b0d.

📒 Files selected for processing (11)
  • apps/web/src/app/api/agent-sessions/[sessionId]/conversations/route.ts
  • apps/web/src/app/api/ai/page-agents/[agentId]/conversations/route.ts
  • apps/web/src/lib/agent-sessions/__tests__/create-conversation-in-session.test.ts
  • apps/web/src/lib/agent-sessions/agent-sessions-runtime.ts
  • apps/web/src/lib/agent-sessions/create-conversation-in-session.ts
  • apps/web/src/lib/agent-sessions/quota-response.ts
  • apps/web/src/lib/ai/tools/__tests__/session-tools.test.ts
  • apps/web/src/lib/ai/tools/session-tools-runtime.ts
  • apps/web/src/lib/ai/tools/session-tools.ts
  • packages/lib/src/agent-sessions/__tests__/plan-spawn-session.test.ts
  • packages/lib/src/agent-sessions/plan-spawn-session.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edb1b0db6e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +123 to +124
const activeCount = await deps.countActiveConversations(sessionId);
if (activeCount >= MAX_SESSION_CONVERSATIONS) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce the conversation ceiling atomically

When multiple conversation-create requests reach a session near the ceiling concurrently, each request can observe the same count below 100 and then insert independently, so the new backstop does not actually guarantee the advertised maximum; a client can intentionally issue a burst through either HTTP route or spawn_session, producing conversations that the capped listings then hide. Serialize the count-and-insert operation for the session, or enforce the limit transactionally in the database.

Useful? React with 👍 / 👎.

Comment on lines +387 to +388
.from(rankedConversations)
.where(sql`${rankedConversations.rowNumber} <= ${MAX_SESSION_CONVERSATIONS}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore explicit ordering after window filtering

The outer query has no ORDER BY, and the ordering inside ROW_NUMBER() only determines rankings; it does not guarantee the returned row order. Consequently each grouped array can be arbitrary even though this function promises newest activity first, and AgentsSidebar.tsx treats session.conversations[0] as the most recent conversation both when opening a session and when choosing the agent for a new conversation. Add an outer ordering by session and rank/lastMessageAt.

Useful? React with 👍 / 👎.

@2witstudios
2witstudios merged commit 3f0ab0d into master Jul 30, 2026
10 checks passed
@2witstudios
2witstudios deleted the pu/audit-session-tools branch July 30, 2026 08:48
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.

fix(session-tools): scope worker verbs to the workspace (H2 parity with shells), per-session caps, bounded listings [MED/security]

1 participant