Skip to content

A throwing access rule leaks its raw error to the MCP client and fails every fields-bearing query on the list #1361

Description

@borisno2

Found during review of PR #1352 (issue #1163, part of #1127). Pre-existing (from commit cc37703), out of scope there — filed so it is not lost.

Two related defects in how a throwing access rule behaves on the MCP read path.

1. The raw error message reaches the assistant

When a field's access rule throws, the resulting text is handed to the MCP client verbatim, e.g.:

Operation failed: Cannot read properties of null (reading 'role')

That is an internal implementation detail of the application's own access rule, surfaced to an external consumer. It can name fields, session properties and code structure that the schema deliberately withholds — and it is precisely the class of information the rest of #1163's work goes to some length to conceal.

2. One brittle field fails every fields-bearing query on its list

resolveFieldsProjection evaluates every field's rule up front. So a single rule that throws — for example ({ session }) => session.role === 'admin' on an anonymous request — fails the whole query, even when the caller never asked for that field.

The blast radius is the list, not the field. A field the caller did not name, and may not even be allowed to see, can deny them every row.

This is the read-path counterpart of the tools/list problem #1163 fixed: listing is now contained per field (a throwing rule hides that field and the listing survives), while the read path deliberately still propagates, per ADR-0030's fail-loud convention. That asymmetry is intentional — but "fail loud" arguably should not mean "one bad rule denies the entire list to everyone", nor "hand the raw TypeError to the caller".

Acceptance criteria

  • A rule that throws does not surface its raw error text to the MCP client; the client gets a generic refusal while the real error is logged server-side
  • A throwing rule on a field the caller did not request does not fail the caller's query
  • A throwing rule on a field the caller did request still fails loudly rather than silently returning the row without that field
  • Tests cover both the requested and not-requested cases

Notes

Deciding this needs a view on what "fail loud" means at a boundary with an untrusted consumer: loud to the operator (logs, telemetry) is clearly right; loud to the assistant is what leaks. Those can be separated.

Related: #1360 (write-surface refusal leak), and decideAdvertisement's silent fail-closed catch on the listing path, which #1352 is adding a console.warn to.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions