Skip to content

Fix semantic search MCP tool leaking embedding vectors into results - #1553

Merged
borisno2 merged 2 commits into
mainfrom
claude/jolly-turing-rpw4yi
Sep 14, 2026
Merged

borisno2 merged 2 commits into
mainfrom
claude/jolly-turing-rpw4yi

Conversation

@borisno2

Copy link
Copy Markdown
Member

Summary

  • The semantic_search_<list> MCP tool built its result by spreading the whole matched row ({ ...match.item, _similarity: match.score }), which included every embedding field's stored vector — 1,536 floats per result on the default OpenAI provider — plus that field's metadata sibling.
  • This burned context on every search, cost tokens, and could leak a column that a field-level access.read rule was meant to restrict.
  • Fixed by stripping all of this list's embedding-typed fields (not just the one searched) out of each match's row before spreading it into the result.
  • Checked the RAG package's other search paths (semanticSearch, findSimilar in runtime/search.ts) — neither spreads a row itself; they return SearchResult<TRow>[] and leave projection to the caller, so no equivalent fix was needed there.

Test plan

  • Added a regression test (plugin.test.ts) with a nearest() double returning a row carrying two embedding fields, asserting the tool's result keeps the plain fields and _similarity but drops both embedding columns and their metadata.
  • Verified the new test fails without the fix (by temporarily reverting plugin.ts) and passes with it.
  • pnpm build in packages/core, packages/ui, packages/rag
  • pnpm test in packages/rag — 258/258 passing
  • pnpm lint, pnpm manypkg fix, pnpm format at the repo root
  • Added a changeset (.changeset/thirty-otters-search.md, patch on @opensaas/stack-rag)

Closes #1297

🤖 Generated with Claude Code

https://claude.ai/code/session_01NZ2pqay76RJhiaV76BUi9a


Generated by Claude Code

The tool's result mapping spread the full row, including every embedding
field's vector and metadata, into each search result — a real cost in
context tokens and a leak of a column that may carry its own read access
restriction. Strip embedding-typed fields out of each match before spreading.

Closes #1297

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2pqay76RJhiaV76BUi9a
@changeset-bot

changeset-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0c7a219

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-rag Patch
@opensaas/stack-cli Patch
@opensaas/stack-auth Patch
@opensaas/stack-core Patch
@opensaas/stack-storage-s3 Patch
@opensaas/stack-storage-vercel Patch
@opensaas/stack-storage Patch
@opensaas/stack-tiptap Patch
@opensaas/stack-ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vercel

vercel Bot commented Sep 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
stack-docs Ready Ready Preview Sep 14, 2026 6:16am UTC

Copy link
Copy Markdown
Member Author

The test check is failing on pnpm check:adr-duplicates, not on anything in this diff:

Duplicate ADR numbers found in docs/adr/:

  0070:
    - 0070-the-auth-plugins-id-strategy-is-config-not-a-hardcoded-pin.md
    - 0070-the-generated-clients-identity-is-a-process-wide-registry-entry.md

Both files already exist at main's current tip (2c000b1, this PR's own base commit) — that's #1550 and #1548 landing back-to-back and each claiming ADR number 0070. This PR touches nothing under docs/adr/, so it inherits the failure rather than causing it, and it isn't red on a check this PR's diff can fix.

No fix for it exists yet on main, so I'm not pushing one into this PR (would just widen it past #1297). The fix is a one-line rename: 0070-the-generated-clients-identity-is-a-process-wide-registry-entry.md0071-the-generated-clients-identity-is-a-process-wide-registry-entry.md (that ADR is the later of the two by commit time), with its own duplicate-ADR-number cross-references updated if any point at "0070".


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Code Review Summary

Reviewed the diff in this PR (branch claude/jolly-turing-rpw4yimain), fixing issue #1297: packages/rag/src/config/plugin.ts (the semantic_search_<list> MCP tool result construction) and the new regression test in packages/rag/src/config/plugin.test.ts.

What I checked:

  • The fix correctly omit()s embedding-typed fields (including companion embedding fields injected by searchable() via extendList, not just directly-declared embedding() fields) from match.item before it is spread into each search result.
  • embeddingFieldNames is computed once per list at plugin-init time, not per request — no per-row overhead added.
  • Confirmed match.item has already passed through core's Field Visibility inside nearest(), so this strip is an additive, MCP-context-specific projection rather than papering over a missing access-control layer — the right layer for this fix.
  • No existing helper duplicated (the admin UI's redactEmbeddingForClient serves a different, partial-redaction purpose and isn't a fit here).
  • A changeset is included per repo convention; no any/type casts introduced.
  • Ran tsc --noEmit and the full plugin.test.ts suite (52/52 passing), including the new regression test.

Verdict: Approve — ready to merge as-is. No correctness bugs or reuse/simplification/efficiency issues found at medium review effort.


Generated by Claude Code

@borisno2
borisno2 enabled auto-merge (squash) September 14, 2026 06:15
@borisno2
borisno2 merged commit 4aa951e into main Sep 14, 2026
8 checks passed
@borisno2
borisno2 deleted the claude/jolly-turing-rpw4yi branch September 14, 2026 06:29
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🟢 Lines 93.86% (🎯 81%) 3532 / 3763
🟢 Statements 92.11% (🎯 76%) 3995 / 4337
🟢 Functions 95.49% (🎯 78%) 805 / 843
🟢 Branches 86.97% (🎯 71%) 2625 / 3018
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 78.7% 244 / 310
🔵 Statements 78.43% 251 / 320
🔵 Functions 69.81% 74 / 106
🔵 Branches 67.51% 160 / 237
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 82.28% 1992 / 2421
🔵 Statements 82% 2142 / 2612
🔵 Functions 87.91% 342 / 389
🔵 Branches 75.17% 1066 / 1418
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 91.42% 288 / 315
🔵 Statements 90.16% 321 / 356
🔵 Functions 96.1% 74 / 77
🔵 Branches 83.38% 276 / 331
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 90.11% 301 / 334
🔵 Statements 90.27% 334 / 370
🔵 Functions 96.9% 94 / 97
🔵 Branches 86.44% 319 / 369
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 92.2% 603 / 654
🔵 Statements 91.44% 663 / 725
🔵 Functions 98.41% 124 / 126
🔵 Branches 85.45% 423 / 495
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/rag/src/config/plugin.ts 93.95% 81.87% 96.87% 96.83% 266, 278, 282-288, 307, 391, 410-414, 496, 498, 536, 590
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 68 / 68
🔵 Statements 100% 71 / 71
🔵 Functions 100% 15 / 15
🔵 Branches 97.87% 46 / 47
File CoverageNo changed files found.
Generated in workflow #2517 for commit 0c7a219 by the Vitest Coverage Report Action

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.

The semantic search MCP tool returns the full embedding vector to the assistant

2 participants