Skip to content

Keep virtual server filtering scoped - #1105

Open
manik3160 wants to merge 12 commits into
Kuadrant:mainfrom
manik3160:codex/1082-virtual-server-scope
Open

Keep virtual server filtering scoped#1105
manik3160 wants to merge 12 commits into
Kuadrant:mainfrom
manik3160:codex/1082-virtual-server-scope

Conversation

@manik3160

@manik3160 manik3160 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #1082

This stops the router from re-adding client-supplied internal filter headers on the broker passthrough path after Envoy strips them.

It also makes tool filtering fail closed when x-mcp-virtualserver points to a missing virtual server, so the broker returns no tools instead of the full unfiltered list.

Tests run:

  • go test ./internal/mcp-router
  • go test ./internal/broker

Summary by CodeRabbit

  • Bug Fixes

    • Validation for virtual-server headers now rejects unknown values with a 400 and causes filtered results to be empty instead of returning unfiltered items.
    • Router no longer reinjects internal-only headers and will forward authorization headers when present.
  • Tests

    • Tests updated and expanded to cover stricter filtering, header handling, and 400 response behavior.
  • Chores

    • CI lint installs now retry on failure; tooling version bumped.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The router stops reinjecting client-supplied x-mcp-authorized and x-mcp-virtualserver headers into broker requests. The broker’s applyVirtualServerFilter now returns an empty tool list on virtual-server lookup error. The code-style CI workflow adds a retry helper for installing Go tooling and kind is bumped to v0.32.0.

Changes

Virtual Server Scope Bypass Fix

Layer / File(s) Summary
Virtual server filter fail-closed behavior
internal/broker/filtered_tools_handler.go, internal/broker/filtered_tools_handler_test.go
applyVirtualServerFilter now returns empty []mcp.Tool{} on virtual server lookup error instead of falling back to the full tool list; test updated to verify the fail-closed behavior.
Remove internal header re-injection from broker passthrough
internal/mcp-router/request_handlers.go, internal/mcp-router/request_handlers_test.go
HandleNoneToolCall removes the re-injection logic that copied internalOnlyHeaders into broker-bound headers; adds validVirtualServerHeader validation and returns 400 for unknown virtual servers; tests updated/added to cover passthrough, accepted virtual server, and unknown virtual server cases.
CI tool install retry wrapper & kind bump
.github/workflows/code-style.yaml, build/tools.mk
Adds a shell retry() helper and uses it to retry go install for goimports and golangci-lint; bumps KIND_VERSION to v0.32.0; make check remains in the generated-resources sync step.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

high-risk

Suggested reviewers

  • jasonmadigan
  • maleck13

"A rabbit hops through headers and code,
I tidy the paths where the sneaky bits go,
I strip the untrusted and close every door,
So tools only answer when rules are in store.
🐇✨"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to build/tools.mk (KIND_VERSION update) and .github/workflows/code-style.yaml (retry helper) are unrelated to the virtual server filtering scope objectives. Remove KIND_VERSION update and workflow retry helper changes; defer to a separate maintenance PR focused on dependency and tooling updates.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Keep virtual server filtering scoped' accurately captures the main objective of the PR—enforcing stricter virtual server filtering to prevent scope bypass.
Linked Issues check ✅ Passed The PR addresses both M1 (stops re-injecting x-mcp-authorized and x-mcp-virtualserver from client headers) and L4 (makes virtual server filtering fail-closed) as required by issue #1082.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added review-effort/large High review effort (4-5): many files, complex, cross-cutting high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing labels Jun 9, 2026

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/code-style.yaml (1)

17-19: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Concurrency group violates coding guidelines.

The group must use ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} per coding guidelines. Current pattern lacks PR number isolation, causing incorrect run cancellation across different PRs.

🔧 Required fix
 concurrency:
-  group: code-style-${{ github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/code-style.yaml around lines 17 - 19, Replace the current
concurrency group declaration so it uses the required workflow+PR-or-ref
pattern; specifically update the concurrency.group value (the "concurrency"
block with keys group and cancel-in-progress) to use the expression ${{
github.workflow }}-${{ github.event.pull_request.number || github.ref }} instead
of code-style-${{ github.ref }} so runs are isolated per workflow and PR.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/code-style.yaml:
- Around line 17-19: Replace the current concurrency group declaration so it
uses the required workflow+PR-or-ref pattern; specifically update the
concurrency.group value (the "concurrency" block with keys group and
cancel-in-progress) to use the expression ${{ github.workflow }}-${{
github.event.pull_request.number || github.ref }} instead of code-style-${{
github.ref }} so runs are isolated per workflow and PR.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 24e44d17-d122-4482-b359-bf1a1c5063c3

📥 Commits

Reviewing files that changed from the base of the PR and between 2961e5b and e84021d.

📒 Files selected for processing (1)
  • .github/workflows/code-style.yaml

@coderabbitai coderabbitai Bot added review-effort/medium Medium review effort (3): few files, moderate logic high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing and removed review-effort/large High review effort (4-5): many files, complex, cross-cutting high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing review-effort/medium Medium review effort (3): few files, moderate logic labels Jun 9, 2026

@jasonmadigan jasonmadigan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

virtual server validation and fail-closed behaviour look correct. one issue: x-mcp-authorized is still re-injected from client-supplied headers (line 868). #1082 flagged this as a bypass risk -- the value should come from validated session state, not from what the client sent. the VS half is fixed properly, but the authorized header needs the same treatment before this fully closes #1082.

Comment thread internal/mcp-router/request_handlers.go
@manik3160

Copy link
Copy Markdown
Contributor Author

Thanks @jasonmadigan ! I've removed the re-injection entirely since Authorino sets it on the request anyway, so it still reaches the broker without needing to be copied from client headers.

@manik3160
manik3160 requested a review from jasonmadigan June 11, 2026 12:19
@manik3160
manik3160 force-pushed the codex/1082-virtual-server-scope branch 2 times, most recently from a1643d3 to e23c448 Compare June 11, 2026 13:08
@manik3160
manik3160 marked this pull request as draft June 11, 2026 13:13
@manik3160
manik3160 force-pushed the codex/1082-virtual-server-scope branch from e23c448 to f89c0fe Compare June 11, 2026 13:14
@manik3160
manik3160 marked this pull request as ready for review June 11, 2026 13:27
@manik3160

Copy link
Copy Markdown
Contributor Author

Hey @jasonmadigan ! just following up on this PR. I've addressed the review feedback and all checks are passing. Would appreciate another look when you have time. Thanks!

@Patryk-Stefanski

Copy link
Copy Markdown
Contributor

@manik3160 please resolve conflicts

@manik3160
manik3160 force-pushed the codex/1082-virtual-server-scope branch from f89c0fe to e0106a2 Compare June 25, 2026 17:45
@manik3160

Copy link
Copy Markdown
Contributor Author

Sure @Patryk-Stefanski !

@Patryk-Stefanski Patryk-Stefanski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core fixes are correct — failing closed on missing virtual server and stopping the re-injection of client-supplied headers on the broker path are both the right calls.

One blocking issue before this can merge, plus two minor nits.


Blocking — x-mcp-authorized accidentally removed from client stripping

clientStrippedHeaders no longer includes mcpAuthorizedHeader:

var clientStrippedHeaders = []string{mcpVirtualServerHeader, mcpVerifiedSubHeader}

The PR's intent was to stop the router re-injecting x-mcp-authorized — correct. But it also stopped stripping the client-supplied value in the headers phase, which is a separate concern and should stay.

The consequence: if a client sends any value in x-mcp-authorized, Authorino appends its wristband JWT rather than overwriting, so the broker receives 2 values. parseAuthorizedCapabilitiesJWT requires exactly 1 value (internal/broker/filtered_tools_handler.go:130) and returns an error when it gets 2. The broker then returns an empty tool list — even when enforceCapabilityFilter is false. Any client that sends this header gets silently empty tools.

Fix — add mcpAuthorizedHeader back to clientStrippedHeaders:

var clientStrippedHeaders = []string{mcpAuthorizedHeader, mcpVirtualServerHeader, mcpVerifiedSubHeader}

Stripping the client value in the headers phase and not re-injecting it in the body phase are both independently correct.


Nit — validVirtualServerHeader returns 400 when RoutingConfig is nil

A nil RoutingConfig is a server error, not a bad client request. The 400 status code is misleading. Since HandleRequestHeaders already dereferences RoutingConfig unconditionally and would panic first, this path is unreachable in practice — but if the guard is there for safety, a 500 would be more accurate.


Nit — nil vs empty slice inconsistency in applyVirtualServerFilter

On error the function returns []mcp.Tool{} (non-nil empty slice), but when a VS exists and no tools match, filtered is nil. Both mean "no tools" and callers using len() are unaffected, but it's worth making consistent — use var filtered []mcp.Tool and return that on error too.

Copilot AI review requested due to automatic review settings July 4, 2026 02:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses issue #1082 by tightening how virtual server scoping is enforced across the router→broker path: preventing re-use of client-supplied internal filter headers after Envoy strips them, and ensuring virtual-server-based tool filtering fails closed when a referenced virtual server doesn’t exist.

Changes:

  • Router: stops re-injecting broker-only internal headers from the client header snapshot; validates x-mcp-virtualserver before forwarding it to the broker passthrough path.
  • Broker: changes virtual server filtering to return an empty tool set when the virtual server lookup fails (fail-closed instead of fail-open).
  • Tests/CI: updates e2e/unit tests for the stricter behavior and adds retry logic for Go tool installs in the code-style workflow; bumps KIND version.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/e2e/user_specific_list_test.go Creates a real MCPVirtualServer and retries client creation to match new virtual-server validation.
tests/e2e/tool_discovery_test.go Retries initialize/notify-initialized sequence to reduce flakes under stricter header validation.
tests/e2e/happy_path_test.go Retries client creation when using X-Mcp-Virtualserver header in happy-path flows.
tests/e2e/auth_policy_test.go Retries initialize flow for AuthPolicy + virtual-server cases.
internal/mcp-router/request_handlers.go Switches stripped-header set usage and adds virtual server header validation before broker passthrough.
internal/mcp-router/request_handlers_test.go Adds coverage to ensure internal headers aren’t re-injected and validates behavior for unknown virtual servers.
internal/mcp-router/headers.go Introduces clientStrippedHeaders (currently identical to internalOnlyHeaders).
internal/broker/filtered_tools_handler.go Makes virtual server filtering return no tools when the virtual server cannot be found.
internal/broker/filtered_tools_handler_test.go Updates expectations to verify fail-closed behavior for missing virtual servers.
build/tools.mk Bumps KIND version used by CI tooling.
.github/workflows/code-style.yaml Adds a retry wrapper around Go tool installs for CI robustness.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/mcp-router/request_handlers.go
Comment thread internal/mcp-router/request_handlers_test.go
Comment thread internal/mcp-router/headers.go Outdated
@manik3160
manik3160 force-pushed the codex/1082-virtual-server-scope branch 2 times, most recently from f8b82d9 to 9642b75 Compare July 4, 2026 02:52
manik3160 added 10 commits July 4, 2026 08:24
Signed-off-by: Manik <maniksingh3606@gmail.com>
Signed-off-by: Manik <maniksingh3606@gmail.com>
Signed-off-by: Manik <maniksingh3606@gmail.com>
Signed-off-by: Manik <maniksingh3606@gmail.com>
The previous commit validating virtual server headers accidentally
dropped the re-injection of the x-mcp-authorized header in
HandleNoneToolCall. Because of this, auth-based filtering
was broken as the broker never received the authorization header
to filter tools.

This commit restores the re-injection of x-mcp-authorized and
updates the unit test that incorrectly asserted it should be absent.

Signed-off-by: Manik <maniksingh3606@gmail.com>
Fixes 'ERROR: unknown containerd config version: 4 (supported versions: 2 and 3)'
which occurs in GitHub Actions runner with newer containerd versions
by updating kind from v0.29.0 to v0.32.0.

Signed-off-by: Manik <maniksingh3606@gmail.com>
The router now strictly validates the X-Mcp-Virtualserver header
and returns a 400 Bad Request if the virtual server isn't found.
When tests create an MCPVirtualServer and immediately make a request,
the router might not have received the updated config from the
controller yet.

This wraps the initial client connection in an Eventually block
to ensure we wait for the router configuration to sync before
failing.

Signed-off-by: Manik <maniksingh3606@gmail.com>
The router now strictly validates the X-Mcp-Virtualserver header
and returns a 400 Bad Request if the virtual server isn't found.
The security test was using a fake 'test/vs' header, which caused
the router to reject the request.

This updates the test to create a valid MCPVirtualServer and wait
for it to sync before testing header forwarding.

Signed-off-by: Manik <maniksingh3606@gmail.com>
Signed-off-by: Manik <maniksingh3606@gmail.com>
Signed-off-by: Manik <maniksingh3606@gmail.com>
@manik3160
manik3160 force-pushed the codex/1082-virtual-server-scope branch from 9642b75 to 7c3c96a Compare July 4, 2026 02:54
@manik3160

Copy link
Copy Markdown
Contributor Author

Thanks @Patryk-Stefanski ! fixed all the points

Signed-off-by: Manik <maniksingh3606@gmail.com>
@manik3160
manik3160 force-pushed the codex/1082-virtual-server-scope branch from 7c3c96a to 604ffb7 Compare July 4, 2026 03:01
…headers in broker

This reverts the addition of x-mcp-authorized to clientStrippedHeaders in the router, as doing so breaks E2E tests which inject this header directly from the client. Instead, the broker now safely accepts multiple headers and uses the last one, protecting against Authorino wristband duplication without breaking client testability.

Signed-off-by: Manik <maniksingh3606@gmail.com>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mcpAuthorizedHeader is still missing from clientStrippedHeaders — flagged in #1105 (review) as a blocking issue.

var clientStrippedHeaders = []string{mcpVirtualServerHeader, mcpVerifiedSubHeader}

This means a client-supplied x-mcp-authorized value is no longer stripped in the headers phase. The fix is one line:

var clientStrippedHeaders = []string{mcpAuthorizedHeader, mcpVirtualServerHeader, mcpVerifiedSubHeader}

Stopping re-injection in the body phase and stripping in the headers phase are two independent concerns — both need to be in place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing triage/needs-issue PR needs a linked issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Internal header re-injection allows virtual server scope bypass

5 participants