Repository navigation
fix(mcp): bind selected-context reads to reviewed snapshots - #195
Merged
Merged
Conversation
johnny4young
marked this pull request as ready for review
October 6, 2026 03:47
johnny4young
commented
Oct 6, 2026
johnny4young
left a comment
Owner
Author
There was a problem hiding this comment.
Summary: MCP get_clip and paste_stack now authorize and read in one GRDB read snapshot (readForMCP). search_clips narrows includedIDs to clips whose reviewed contextRevision still matches before LIMIT, then re-checks the hits.
Verdict: ready. Nothing blocking.
What I verified:
readForMCPkeeps the old decision order and semantics: missing → context (!requiresContextPackwhen there is no explicit pack) → sensitive → metadata scope → boards scope (pinned or on a board) → content. Board membership is still only queried when needed. Archived and expired rows read as missing, as before.searchableIDsreturning an empty set is safe:GRDBClipboardStore+SearchFiltersturns an emptyincludedIDsintoAND 0, not "no filter". Revision keys areuuidStringboth inSelectedContext.grantand here, so they compare correctly.ClipRow: Sendableis needed to return the payload row out of the read closure.MCPClipStoregains a requirement, and the only conformers areGRDBClipboardStoreand the test fake.
🟡 Nits, none blocking:
MCPAccessStore.swift,readForMCP: blob bytes are read viacontent(from:)after the snapshot closes. That is fine for content-addressed blobs. If the orphan sweep removes the file in between, though,get_clipreturnscontent: nilwithcontentWithheld: false, which looks like an empty clip. Consider treating a nil binary payload as.missing.createPinstill usesvisibleItem+isInsideContextas separate awaits. It only toggles a pin, so the impact is minimal, but it is now the one remaining non-snapshot path.- CHANGELOG: if #193 merges first, these entries end up under
[0.10.0]. Make sure that is the intent.
CI is green.
Generated by Claude Code
get_clip reported contentWithheld: false with no body when a blob was reclaimed between the authorized snapshot and the byte read, which looks like an empty clip. Report it as withheld instead. create_pin was the last tool composing separately awaited visibility, context and sensitivity reads. It now takes all three from readForMCP with metadata scope, so it never selects payload columns, and the unused split-read helpers are removed.
- Remove content(for:) and boardIDs(for:) from MCPClipStore so a tool can only read a clip through the single-snapshot readForMCP path - Share one board-membership query between the context menu and readForMCP - Name the reviewed-revision rule once on MCPContextPack and reuse it in contains, the search post-filter and searchableIDs - Wait for the payload gate off the Swift concurrency pool in the WAL test
12 tasks
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.
Summary
MCP
get_clip,paste_stackand nowcreate_pinauthorize from one GRDB read snapshot (readForMCP), andsearch_clipsis bound to the exact reviewed clip revisions of a selected-context grant. Details of the original finding and fix are kept below.Review follow-ups
bf0c61d— fix(mcp): withhold vanished payloads and pin from the policy snapshotget_clipnow reportscontentWithheld: true(instead offalsewith no body) when a blob is reclaimed between the authorized snapshot and the byte read.create_pintakes visibility, context membership and sensitivity fromreadForMCP(called with metadata scope so it never selects payload columns). The now-unused split-read helpersvisibleItem/isInsideContextare removed.MCPSnapshotPolicyTests: vanished payload ⇒ withheld;create_pinobeys the snapshot's sensitive verdict even when a separate read would say otherwise;create_pinasks the snapshot only for metadata scope.Decisions taken
create_pinkeeps its write (setPinned/assign) outside the snapshot; only the policy decision is made consistent.How it was tested (this round)
swiftc -parseandswift-format lint --strict(Swift 6.0.3 container) on changed files. GanchoMCP depends on GRDB/SQLCipher and Apple frameworks, so it was not compiled locally; relies on macOS CI (see below).Merge order
Independent of the storage stack; merge before the v0.10.0 release PR #193. If #193 merges first, move this PR's CHANGELOG bullets back under
[Unreleased].CI: macOS jobs for the new head were queued when this description was written; check the PR checks tab.
Original description (state at
c90e07d; superseded where it conflicts with the sections above):Finding
A selected-context grant records the exact approved
contextRevisionper clip.get_clipandpaste_stackchecked this boundary, butsearch_clipsonly narrowed by clip IDs. Editing an approved clip could therefore expose its new title/preview in search even though its body was correctly denied byget_clip.Fix
Consistent content reads
A deeper review also confirmed the split-read race: get_clip/paste_stack checked metadata, then awaited a separate content lookup. They now consume a GRDB operation that checks visibility, reviewed revisions, membership and sensitive policy within the same database snapshot used to select the payload. Metadata-only reads never select payload columns; immutable blob identity comes from the captured row, without another ID lookup.
Regression coverage
limit=1Verification
c90e07d4298eaebd95439f76fb13b7d65a6c1900passed CI run 37371083389git diff --checkpasses. Local native execution is unavailable on the Linux executor; native evidence is hosted macOS CIHuman-review readiness
Scoped implementation and regression coverage are complete at unchanged head c90e07d. Fresh verification confirms both required native jobs and GitGuardian successful, the expected non-blocking beta-canary skip, and no pending discussion comments, submitted reviews or unresolved inline review threads. Review readiness does not authorize merge, release or deployment.
🤖 Generated with Claude Code
https://claude.ai/code/session_01ExmnfUngrDYjAheGvYwMnn
Generated by Claude Code