Log the refusal we didn't send - #88
Merged
Merged
Conversation
Since #79, a verified requester denied read access gets the same StackNotFoundError/404 as a genuinely missing record, per the anti-oracle rule. That's correct for the client but leaves the operator with no way to tell a subtly-wrong grant apart from a bad record ID or a write that never landed. errorMiddleware now probes existence with the unscoped Stack (one extra read, only on a path that's already failing) and logs the requester DID, record ID, and whether the record existed. The refusing check is always logged as 'read': ScopedStack's denialFor() and get() both collapse into StackNotFoundError exactly when canRead() is false, so there's no second gate to name. Logged at debug rather than warn, since this is the sharing graph written down and shouldn't ship to a general-purpose aggregator by default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0152A7WX2PwCTVr2zgzkS7LZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #80.
The problem
Since #79, a verified requester denied read access to a record gets the same
StackNotFoundError/404 as a genuinely missing record — the anti-oracle rule. Correct for the client, but it leaves the operator with no way to tell "a grant is subtly wrong" apart from "bad record ID" or "a write never landed," perdocs/spec/wire-format.md§ Server implementation checklist ("Log the refusal you didn't send").What changed
errorMiddlewarenow takes the unscopedStackand, on aStackNotFoundErrorfor a verified (non-anonymous) requester, probes existence with one extra unscoped read and logs:principalId/subjectId— the requester DIDrecordId— from the route's:idparamexisted— whether the record is actually therecheck: 'read'— always'read', becauseScopedStack'sdenialFor()andget()both collapse intoStackNotFoundErrorexactly whencanRead()is false, regardless of which verb (update/delete/etc.) the request asked for. There's no second gate to name, so this isn't a guess.Logged at
debug, notwarn: this line is "who asked after what, and was refused" — the sharing graph, written down — and the spec asks that it not ship to a general-purpose aggregator by default (defaultLOG_LEVELisinfo). The existingwarn-level "Denied a verified requester" line forStackPermissionError(readable-but-write-denied, 403) is untouched.Design decisions
ScopedStack's implementation shows the refusing check is always read access for this error class, so the probe recovers full fidelity for free. Documented in the code comment.src/routes/entity.ts'sGET /entityhas the same ambiguity for a non-owner probing the owner's card, but no URL:idparam to key the probe on (the record id is resolved internally via a cached query). Left uncovered for now, per discussion — low value (an attacker already knows the card exists from discovery) and out of scope for the generic mechanism.src/routes/attachments.tsis unaffected —getAttachment/canAccessFilealready throwsStackPermissionErroruniformly for missing-or-forbidden (pre-existing masking, unrelated to Upgrade to the core 0.13 line and adopt the 404/403 anti-oracle rule #79), so it never reaches this branch.Testing
Added to
tests/middleware/errors.test.ts:existed: truewith DID, record id,check: 'read'existed: falseFull suite: 346/346 passing. Typecheck and lint clean.
Generated by Claude Code