Skip to content

🐛 Only read known query results after 'query' middleware - #760

Draft
alecgibson wants to merge 2 commits into
mainfrom
query-resubscribe-after-middleware
Draft

alecgibson wants to merge 2 commits into
mainfrom
query-resubscribe-after-middleware

Conversation

@alecgibson

@alecgibson alecgibson commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #737

At the moment, a query resubscribe (qs with r) starts reading the known results' snapshots and ops before 'query' middleware has run, so a rejected query still sends its ops to the client:

  1. Reject all queries in 'query' middleware
  2. Send a qs with r: [['fido', 0]]
  3. The client receives fido's ops, then the query error

This change only reads the known results once querySubscribe() succeeds, before opening the emitter's streams. The streams buffer until then, so an op committed during the reads arrives as a duplicate rather than going missing, as in _subscribe().

A failed read or re-poll now also destroys the emitter, which used to stay subscribed after the client had dropped the query. That includes a rejected known snapshot, which now fails the resubscribe with ERR_SNAPSHOT_READS_REJECTED, as a fresh subscribe does, instead of replying with an {error} entry the client can't ingest. Other teardown races are left for #755.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

Copilot AI 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.

🟡 Changes recommended

Destroying an emitter during a failed re-poll still allows _finishPoll() to schedule continued polling.

1 open finding
What changed in this PR

Ensures query middleware authorizes resubscriptions before known results are read, while documenting ShareDB read-access controls.

Changes:

  • Defers known-result reads and stream opening until query authorization succeeds.
  • Adds reconnect, failure cleanup, and buffered-op tests.
  • Documents required readSnapshots, op, and query middleware.
File Description
lib/​agent.js Reorders query subscription setup and cleanup.
lib/​query-emitter.js Documents stream buffering before opening.
test/​agent.js Tests authorization, cleanup, and buffered ops.
test/​client/​query-subscribe.js Tests reconnect op catch-up.
docs/​middleware/​access-control.md Adds access-control guidance.
docs/​middleware/​actions.md Clarifies read middleware responsibilities.
docs/​middleware/​index.md Links access-control documentation.
docs/​projections.md Clarifies projection security behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/agent.js
@coveralls

coveralls commented Oct 13, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 97.953% (+0.04%) from 97.916% — query-resubscribe-after-middleware into main

Copilot AI 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.

🟡 Changes recommended

Parallel known-result reads can partially send data after failure, and reconnect behavior is documented inaccurately.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread lib/agent.js Outdated
Comment thread docs/middleware/access-control.md
alecgibson and others added 2 commits October 13, 2026 18:10
Fixes #737

At the moment, a query resubscribe (`qs` with `r`) starts reading the
known results' snapshots and ops before `'query'` middleware has run,
so a rejected query still sends its ops to the client:

 1. Reject all queries in `'query'` middleware
 2. Send a `qs` with `r: [['fido', 0]]`
 3. The client receives `fido`'s ops, then the query error

This change only reads the known results once `querySubscribe()`
succeeds, before opening the emitter's streams. The streams buffer
until then, so an op committed during the reads arrives as a duplicate
rather than going missing, as in `_subscribe()`.

A failed read or re-poll now also destroys the emitter, which used to
stay subscribed after the client had dropped the query. That includes
a rejected known snapshot, which now fails the resubscribe with
`ERR_SNAPSHOT_READS_REJECTED`, as a fresh subscribe does, instead of
replying with an `{error}` entry the client can't ingest. Other
teardown races are left for #755.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Refs #737

At the moment, nothing in the docs says that `'readSnapshots'` only
sees snapshots. Ops go through `'op'` middleware instead, so an app
that only checks reads in `'readSnapshots'` still lets clients fetch a
doc's full history, including its `create` data.

This change adds an "Access control" middleware page listing which
middleware sees which reads, with an example, and links it from the
actions, middleware and projections pages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@alecgibson
alecgibson force-pushed the query-resubscribe-after-middleware branch from 69ca2f4 to e44b624 Compare October 13, 2026 18:23
@alecgibson
alecgibson requested a balanced review from Copilot October 13, 2026 18:25

Copilot AI 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.

🟢 Approval recommended

The implementation addresses the data-leak ordering issue with focused cleanup and regression coverage.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

This branch has not been deployed

No deployments
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.

Document that read access control needs 'op' middleware, not just 'readSnapshots' (and qs+r fetches ops before 'query' resolves)

3 participants