Summary
Make session-scoped workspace permissions a first-class, observable contract across MCP Bridge, custom tools, and the UI. When an agent is blocked from an external path, surface a safe permission request through the existing notification UI so the user can explicitly approve or deny read access instead of receiving only a tool error.
This is a future design/implementation item. Do not weaken folder guards or automatically grant paths as part of this issue.
Incident / motivation
A Claude Code workflow-builder session had a valid read-only grant for /Users/<user>/Downloads, but execute_shell_command rejected the path.
RCA:
- MCP Bridge sent the correct session identity in
X-Session-ID.
- The backend used the header to select the session-scoped custom-tool registry.
- The global custom-tool route did not copy that identity into the Go execution context.
- Workspace policy therefore fell back to the generic workflow guard and missed session-specific state such as host Downloads access, blocked-write paths, working directory, browser mode, and session shell environment.
The immediate regression is fixed in local commit 0fb9f2d4 by propagating the header into context and adding a trusted MCP_SESSION_ID fallback. This issue tracks the broader contract, E2E coverage, observability, and permission UX so the same class of bug cannot return elsewhere.
Proposed design
1. One authoritative session identity
- Resolve session identity once at the HTTP/tool boundary.
- Carry a typed runtime/session context through every tool call.
- Use the same identity for registry lookup, folder guard, working directory, shell environment, browser mode, costs, logs, and notifications.
- Reject conflicting session identities from URL, trusted header, request body, or client environment instead of silently choosing one.
2. Structured permission-denied event
When folder guard rejects access, emit a structured event rather than only a text error:
- session, workflow, execution, tool, and request IDs
- canonical requested path
- requested operation: read, write, move, delete, or execute
- denial reason and active policy scope
- whether the request is eligible for user approval
- safe suggested scope, such as one file or one folder read-only
Never include file contents, secrets, or raw authorization material in the event.
3. Notification UI integration
Show eligible denials in the existing notification surface as a permission request:
- Allow once: one request only
- Allow for this session: expires with the session
- Always allow read-only on this device: optional durable local grant
- Deny
The UI must clearly show the workflow, agent, operation, and canonical path. The safest scope should be selected by default. Initially, approval should not silently replay a potentially non-idempotent tool call; notify the agent/user that the operation can be retried. Automatic replay can be designed separately with request IDs and idempotency protection.
4. Permission model
- External paths remain denied by default.
- Host Downloads may be offered as read-only; writes, moves, and deletes remain blocked unless separately and explicitly designed.
- Durable grants are device-local and must not sync through workflow JSON or backups.
- Canonicalize paths and resolve symlinks before comparing scopes.
- Provide a settings view to inspect and revoke grants.
- Record an audit trail of approval, denial, expiry, and use.
5. Cross-provider E2E contract
Run the same tests for Claude Code, Codex, Cursor, and Pi through the real MCP Bridge path:
- global
/tools/custom/... with X-Session-ID
- custom tools redirected through
/tools/mcp/...
- session-scoped
/s/{session}/tools/...
- direct in-process execution where still supported
Verify that every path resolves the same session, working directory, folder guard, blocked-write paths, shell environment, and notification owner.
Acceptance criteria
- Session identity has one documented resolver and conflict behavior.
- No custom or MCP tool route can select a session registry while executing with a different or empty policy session.
- A real bridge E2E test reproduces the original host Downloads case.
- Read-only approval cannot be used for writes, moves, deletes, symlink escapes, parent traversal, or command indirection.
- Eligible denials appear once in the notification UI and can be approved or denied.
- Grants expire/revoke correctly and are visible in an audit log.
- Existing workflow, Pulse, Chief of Staff, background-agent, and scheduled-run folder guards remain fail-closed.
Security cases to test manually
- symlink inside an approved folder pointing outside it
- path replacement between approval and execution (TOCTOU)
- quoted paths, spaces, globbing, pipes, redirects, heredocs, and subshells
- one workflow attempting to reuse another workflow's grant
- stale or forged session IDs
- approval on one laptop appearing on another laptop
- background agent requesting a broader scope than its parent
- notification floods from repeated retries
Non-goals
- Automatically allowing arbitrary external folders
- Giving agents write access to host Downloads by default
- Syncing local filesystem grants across devices
- Implementing the approval UI in the current fix
Summary
Make session-scoped workspace permissions a first-class, observable contract across MCP Bridge, custom tools, and the UI. When an agent is blocked from an external path, surface a safe permission request through the existing notification UI so the user can explicitly approve or deny read access instead of receiving only a tool error.
This is a future design/implementation item. Do not weaken folder guards or automatically grant paths as part of this issue.
Incident / motivation
A Claude Code workflow-builder session had a valid read-only grant for
/Users/<user>/Downloads, butexecute_shell_commandrejected the path.RCA:
X-Session-ID.The immediate regression is fixed in local commit
0fb9f2d4by propagating the header into context and adding a trustedMCP_SESSION_IDfallback. This issue tracks the broader contract, E2E coverage, observability, and permission UX so the same class of bug cannot return elsewhere.Proposed design
1. One authoritative session identity
2. Structured permission-denied event
When folder guard rejects access, emit a structured event rather than only a text error:
Never include file contents, secrets, or raw authorization material in the event.
3. Notification UI integration
Show eligible denials in the existing notification surface as a permission request:
The UI must clearly show the workflow, agent, operation, and canonical path. The safest scope should be selected by default. Initially, approval should not silently replay a potentially non-idempotent tool call; notify the agent/user that the operation can be retried. Automatic replay can be designed separately with request IDs and idempotency protection.
4. Permission model
5. Cross-provider E2E contract
Run the same tests for Claude Code, Codex, Cursor, and Pi through the real MCP Bridge path:
/tools/custom/...withX-Session-ID/tools/mcp/.../s/{session}/tools/...Verify that every path resolves the same session, working directory, folder guard, blocked-write paths, shell environment, and notification owner.
Acceptance criteria
Security cases to test manually
Non-goals