Skip to content

feat(prompts): per-prompt rug-pull baseline MVP (spec 100) - #1010

Merged
Dumbris merged 1 commit into
mainfrom
feat/prompt-rugpull-mvp
Aug 19, 2026
Merged

feat(prompts): per-prompt rug-pull baseline MVP (spec 100)#1010
Dumbris merged 1 commit into
mainfrom
feat/prompt-rugpull-mvp

Conversation

@Dumbris

@Dumbris Dumbris commented Aug 19, 2026

Copy link
Copy Markdown
Member

Implements the MVP of spec 100 (merged in #1009) — the tool-quarantine (Spec 032) analogue for aggregated upstream prompts, closing the last F2 gap from the PR #973 review. A trusted server that passes admission with a benign prompt and later mutates its advertised metadata is now caught, and the changed prompt is withheld from prompts/list until approved.

The one constraint (metadata only)

The baseline hashes advertised LIST metadata (name + description + arguments). Get-time prompts/get message content has no list-time artifact to hash against and is inherently not baselineable here (spec Non-Goals); that path stays defended by the existing F2 sanitisation and F12 size cap. This is stated loudly in the spec and the code so it's never mistaken for content protection.

What's here

  • storage — parallel PromptApprovalRecord + prompt_approvals bucket + CRUD (+ Manager wrappers), mirroring the tool ops 1:1 (not a ToolApprovalRecord overload — the server:tool/server:prompt key spaces would collide).
  • engine (internal/server/prompt_quarantine.go) — calculatePromptApprovalHash (sha256(name|desc|normalizeJSON(args)), excludes Meta/Title), checkPromptApprovals with the pending/changed/approved state machine + baseline-pass + QuarantineEnabled kill-switch + TrustMode/AutoApproveToolChanges reuse, a fail-closed enforcePromptInvariant transition spine, filterBlockedPrompts, and the ApprovePrompt/ApproveAllPrompts mutators.
  • hook — runs in RefreshPrompts after the TPA scan-and-drop, before registration. Withholding is the block: a prompt never passed to SetPrompts is absent from prompts/list and fails prompts/get natively, so there's no runtime get-time gate (the big simplification over tools).

Tests

Hash stability/change, fail-closed invariant, first-seen→pending, approve→registered, rug-pull change→withheld→revert→re-approved, trust=auto auto-approve, ApproveAllPrompts, and a full-RefreshPrompts withholding integration test.

Deferred (immediate follow-up)

The quarantine_security MCP ops (inspect_prompts/approve_prompt/approve_all_prompts), REST twins, and the Vue review banner. The approve API (ApprovePrompt/ApproveAllPrompts) is ready to wire — I held the MCP tool-schema op out of this PR because it requires regenerating the 3 frozen tool-surface goldens, which is cleaner to do deliberately in that PR. Until then a held prompt is recoverable by approving programmatically or setting the server to trust: auto.

The enforceInvariant port is the security-critical fail-closed spine and is the part most worth a careful review.

🤖 Generated with Claude Code

Implements the core of spec 100 (merged in #1009): the tool-quarantine
(Spec 032) analogue for aggregated upstream prompts. A trusted server that
passes admission with a benign prompt and later mutates its advertised metadata
is now caught and the changed prompt is WITHHELD from prompts/list until
approved — closing the last F2 gap from the PR #973 review.

Scope (metadata only): the baseline hashes advertised LIST metadata
(name + description + arguments); get-time prompts/get message content is
inherently not baselineable here (spec Non-Goals) and stays defended by the
existing F2 sanitisation + F12 caps.

- storage: parallel PromptApprovalRecord + prompt_approvals bucket + CRUD
  (+ Manager wrappers), mirroring the tool ops 1:1 — NOT a ToolApprovalRecord
  overload (the server:tool / server:prompt key spaces would collide).
- engine (internal/server/prompt_quarantine.go): calculatePromptApprovalHash
  (sha256(name|desc|normalizeJSON(args)), excludes Meta/Title), checkPromptApprovals
  with the pending/changed/approved state machine + baseline-pass + QuarantineEnabled
  kill-switch + TrustMode/AutoApproveToolChanges reuse, a fail-closed
  enforcePromptInvariant transition spine, filterBlockedPrompts, and the
  ApprovePrompt/ApproveAllPrompts mutators (which re-baseline + RefreshPrompts()).
- hook: checkPromptApprovals + filterBlockedPrompts run in RefreshPrompts after
  the TPA scan-and-drop, before registration. Withholding IS the block — a prompt
  never passed to SetPrompts is absent from prompts/list and fails prompts/get
  natively, so there is no runtime get-time gate.

Tests: hash stability/change, fail-closed invariant, first-seen→pending,
approve→registered, rug-pull change→withheld→revert→re-approved, trust=auto
auto-approve, ApproveAllPrompts, and a full-RefreshPrompts withholding integration
test. Existing aggregation tests set quarantine off (they verify aggregation, not
the baseline).

Deferred to a follow-up: the quarantine_security MCP ops
(inspect_prompts/approve_prompt/approve_all_prompts), REST twins, and the Vue
review banner — the approve API (ApprovePrompt/ApproveAllPrompts) is ready to
wire. Adding the MCP tool-schema op requires regenerating the 3 frozen
tool-surface goldens, done deliberately in that PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7J8Yv5zr4tMQZaY3ot3Za
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4e55ef9
Status: ✅  Deploy successful!
Preview URL: https://2bc3d56f.mcpproxy-docs.pages.dev
Branch Preview URL: https://feat-prompt-rugpull-mvp.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 55.97015% with 118 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/storage/bbolt.go 1.81% 54 Missing ⚠️
internal/server/prompt_quarantine.go 80.00% 21 Missing and 15 partials ⚠️
internal/storage/manager.go 0.00% 20 Missing ⚠️
internal/storage/models.go 0.00% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: feat/prompt-rugpull-mvp

Available Artifacts

  • archive-darwin-amd64 (29 MB)
  • archive-darwin-arm64 (26 MB)
  • archive-linux-amd64 (17 MB)
  • archive-linux-arm64 (15 MB)
  • archive-windows-amd64 (29 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (23 MB)
  • installer-dmg-darwin-arm64 (20 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 32262266585 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris
Dumbris merged commit 2378d37 into main Aug 19, 2026
39 checks passed
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.

2 participants