Skip to content

feat(security): Spec 076 US1 — 3 hard detect checks + wire scanner (T009-T012)#770

Merged
Dumbris merged 3 commits into
mainfrom
076-t2-mvp-checks
Jun 27, 2026
Merged

feat(security): Spec 076 US1 — 3 hard detect checks + wire scanner (T009-T012)#770
Dumbris merged 3 commits into
mainfrom
076-t2-mvp-checks

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 26, 2026

Copy link
Copy Markdown
Member

Spec 076 · T2 / US1 (MVP) — MCP-3576

Implements the MVP of the deterministic, offline tool-scanner (Spec 076): three near-zero-FP HARD structural checks, delegated into the live tpa-descriptions scan via detect.Engine (T1 foundation, #769). Builds off origin/main (T1 is merged there).

What's in (T009–T012)

  • T009 detect/checks/unicode_hidden.go — flags zero-width / bidi / TAG-block / PUA runes in raw description+schema text; escalates to near-certain (critical) when ≥3 hidden classes are present or TAG-block chars decode to a printable ASCII message (evidence = the decoded message). Runs on raw text deliberately — Normalize strips format runes.
  • T010 detect/checks/shadowing.go — flags distinctive cross-server tool-name collisions and descriptions that reference another server's distinctive tool. Ignores self-reference and generic-verb collisions (search, get, …) to hold FP near zero.
  • T011 detect/checks/payload_decoded.go — decodes base64/hex blobs and flags only when the decoded bytes are a shell/exfil command (curl … | sh, chmod, rm -rf, raw IP:port). Benign/binary decodes (icons, JSON) and short tokens are skipped. Evidence = decoded content.
  • T012 scanner/inprocess.go — builds a detect.RegistryView from the server's tool set, runs detect.Engine with the three checks, converts detect.Finding → ScanFinding 1:1 (additive confidence + signals carried through). CLI/REST/MCP entry points unchanged (FR-015).

Design decision — migration boundary (no regression)

US1 owns only the three structural checks; directive.imperative (T013/US2) and secret.embedded are later tasks. To keep the MVP from regressing existing coverage, the legacy phrase rules (tpaRules) and embedded-secret detection remain alongside the engine until US2 lands their detect equivalents, at which point inProcessToolScan fully delegates. Documented in code + the commit. Cross-server shadowing only sees this server's tools at this wiring point (per-server scan) — the check is correct; a future task can feed a global registry.

Tests (TDD — failing test first)

  • Per-check MUST-flag / MUST-NOT-flag tables (*_test.go) per the contract.
  • Scanner-path tests assert the new finding shape (signals/confidence) flows through inProcessToolScan.
  • Integration smoke test (e2e_tpa_smoke_test.go, -tags integration) proves a base64 curl|sh payload is flagged end-to-end through StartScan.
  • Offline import-guard stays green (the checks subpackage imports only detect + stdlib).

Verification

```
go test -count=1 -race ./internal/security/detect/... ./internal/security/scanner/ # ok
go test -count=1 -tags integration ./internal/security/scanner/ -run E2E_TPA # ok
golangci-lint run --config .github/.golangci.yml ./internal/security/detect/... ./internal/security/scanner/... # 0 issues
go build ./... # ok
```

Docs: docs/features/security-scanner-plugins.md updated to describe the new structural checks + confidence/signals (ENG-9).

Related #MCP-3576

…009-T012)

Implement the MVP of the deterministic offline tool-scanner (Spec 076 US1):
three near-zero-FP HARD checks delegated into the live tpa-descriptions scan.

- detect/checks/unicode_hidden.go: flags zero-width / bidi / TAG-block / PUA
  runes in RAW description+schema text; escalates to near-certain when >=3
  hidden classes are present or TAG-block chars decode to a printable message.
- detect/checks/shadowing.go: flags distinctive cross-server name collisions
  and descriptions that reference another server's distinctive tool; ignores
  self-reference and generic-verb collisions.
- detect/checks/payload_decoded.go: decodes base64/hex blobs and flags only
  when the decoded bytes are a shell/exfil command (curl|sh, chmod, rm -rf,
  raw IP:port); benign/binary decodes are skipped. Evidence = decoded content.
- scanner/inprocess.go: builds a detect.RegistryView from the server's tool
  set, runs detect.Engine with the three checks, converts detect.Finding ->
  ScanFinding 1:1 (additive confidence/signals carried through). CLI/REST/MCP
  entry points unchanged. Legacy phrase rules + embedded-secret detection are
  retained until US2 lands detect's directive.imperative/secret.embedded, so
  the MVP regresses no existing coverage.

TDD: per-check MUST-flag/MUST-NOT-flag tests written first; scanner-path and
integration smoke tests assert the new finding shape. Offline import-guard
still green (checks subpackage imports only detect + stdlib).

Related #MCP-3576
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 427b3c1
Status: ✅  Deploy successful!
Preview URL: https://96981c3c.mcpproxy-docs.pages.dev
Branch Preview URL: https://076-t2-mvp-checks.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

codecov-commenter commented Jun 26, 2026

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 79.46768% with 54 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/security/detect/checks/payload_decoded.go 66.66% 10 Missing and 7 partials ⚠️
internal/server/server.go 0.00% 11 Missing ⚠️
internal/security/detect/checks/unicode_hidden.go 88.88% 5 Missing and 3 partials ⚠️
internal/security/scanner/service.go 0.00% 8 Missing ⚠️
internal/security/detect/checks/shadowing.go 88.88% 3 Missing and 3 partials ⚠️
internal/security/scanner/inprocess.go 94.02% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: 076-t2-mvp-checks

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 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 28279235674 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

Dumbris and others added 2 commits June 27, 2026 07:35
… (Codex #770)

CodexReviewer found that the in-process scanner stamped every detect.ToolView
with the same serverName (only the scanned server's tools.json), so the
shadowing.cross_server check — which only emits when a collision/reference
points at a *different* server — could never fire end-to-end. Spec 076 FR-003
was unsatisfied for that check.

Fix: feed the engine a real cross-server RegistryView.
- New optional capability `allServerToolsProvider` (GetAllServerTools); the
  Service builds a peer snapshot (every other server's current tools) and
  carries it on ScanRequest.PeerTools. Implemented on configServerInfoProvider
  by iterating the live config's servers. Optional type-assertion → zero blast
  radius on the required ServerInfoProvider interface and its mocks.
- inProcessToolScan/detectEngineFindings now tag each ToolView with its TRUE
  owning server (scanned server + peers), deterministically ordered, and filter
  emitted findings back to the scanned server (peers are context only).

Tests (regression locked end-to-end, not just the check in isolation):
- TestInProcessToolScan_ShadowingCrossServerThroughAdapter: peers present →
  shadowing.cross_server fires; no peers → it does not (the bug state).
- TestEngineInProcessScan_ShadowingViaPeerTools: full StartScan path proves
  ScanRequest.PeerTools threads through the live adapter.

Verification: go test -race ./internal/security/..., -tags integration E2E_TPA,
golangci-lint v2 on scanner+server (0 issues), go build ./... — all green.

Related #MCP-3576

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…770)

CodexReviewer's second finding: the in-process tpa-descriptions scanner dropped
each tool's outputSchema, so a hidden-Unicode / decoded-payload / directive
payload smuggled into the OUTPUT schema was invisible — even though Spec 076
FR-001 scans name+description+inputSchema+outputSchema and the detect checks
already inspect ToolView.OutputSchema.

- toolDef now parses `outputSchema`.
- Legacy phrase + embedded-secret text concatenation includes the output schema.
- The detect adapter populates ToolView.OutputSchema, so the structural checks
  (unicode.hidden / payload.decoded) see it via the engine too.

Test: TestInProcessToolScan_DetectEngineOutputSchemaPayload — a base64 curl|sh
blob placed only in outputSchema is flagged payload.decoded end-to-end.

Verification: go test -race ./internal/security/..., golangci-lint v2 (0 issues),
go build ./... — all green.

Related #MCP-3576

Co-Authored-By: Paperclip <noreply@paperclip.ing>

@mcpproxy-gatekeeper mcpproxy-gatekeeper Bot 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.

Gatekeeper approval — Codex review verdict: ACCEPT.

This approval is posted automatically by the MCPProxy Gatekeeper App on behalf of the Codex reviewer (verdict of record lives in the Paperclip review thread). Author≠approver satisfied; QA + CI gates enforced separately.

Auto-approved per Model B (MCP-1249).

@Dumbris Dumbris merged commit 54d10a7 into main Jun 27, 2026
49 checks passed
Dumbris added a commit that referenced this pull request Jun 28, 2026
…confidence (MCP-3577)

Adds the US2 false-positive-discriminating SOFT checks to the Spec-076
offline detect engine, plus per-match confidence on the reused secret
matchers. Soft signals raise a finding for review and never auto-quarantine.

- T013 checks/directive_imperative.go: prompt-injection directives
  (<IMPORTANT> tags, 'do not tell the user', 'ignore previous
  instructions', 'before using this tool') matched over NORMALIZED text
  with position discounting so example-position mentions are suppressed.
- T014 checks/capability_mismatch.go: declared-vs-implied capability gap
  (a compute/string tool touching ~/.ssh, /etc/passwd, a URL or shell)
  plus an unexplained data-sink param ('sidenote'); legitimate file/network
  tools are not flagged.
- T015 internal/security/patterns: additive per-match confidence
  (WithConfidence builder + ConfidenceFor) — Luhn-validated card 0.95,
  generic bearer 0.3, documented examples 0.1, severity defaults otherwise.
  Existing Match/IsValid/Scan behavior is unchanged.
- T016 checks/embedded_secret.go: wraps the patterns matchers with
  confidence and masked evidence, skipping documented placeholders; the
  three soft checks are registered in the scanner detect-engine wiring.

TDD with MUST-flag and hard-negative MUST-NOT-flag cases for each check.

Coordination: detectEngineFindings in inprocess.go is the shared US1/US2
integration point; this branch registers the three SOFT checks and the
Checks slice is the merge point with US1's hard checks (#770).
Dumbris added a commit that referenced this pull request Jun 28, 2026
…confidence (MCP-3577) (#775)

Adds the US2 false-positive-discriminating SOFT checks to the Spec-076
offline detect engine, plus per-match confidence on the reused secret
matchers. Soft signals raise a finding for review and never auto-quarantine.

- T013 checks/directive_imperative.go: prompt-injection directives
  (<IMPORTANT> tags, 'do not tell the user', 'ignore previous
  instructions', 'before using this tool') matched over NORMALIZED text
  with position discounting so example-position mentions are suppressed.
- T014 checks/capability_mismatch.go: declared-vs-implied capability gap
  (a compute/string tool touching ~/.ssh, /etc/passwd, a URL or shell)
  plus an unexplained data-sink param ('sidenote'); legitimate file/network
  tools are not flagged.
- T015 internal/security/patterns: additive per-match confidence
  (WithConfidence builder + ConfidenceFor) — Luhn-validated card 0.95,
  generic bearer 0.3, documented examples 0.1, severity defaults otherwise.
  Existing Match/IsValid/Scan behavior is unchanged.
- T016 checks/embedded_secret.go: wraps the patterns matchers with
  confidence and masked evidence, skipping documented placeholders; the
  three soft checks are registered in the scanner detect-engine wiring.

TDD with MUST-flag and hard-negative MUST-NOT-flag cases for each check.

Coordination: detectEngineFindings in inprocess.go is the shared US1/US2
integration point; this branch registers the three SOFT checks and the
Checks slice is the merge point with US1's hard checks (#770).
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