Skip to content

feat: add virtualKeysByID secondary index and cache signing key + VK lookups on /mcp JWT auth path - #4783

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
06-29-fix_removes_db_ops_from_mcp_oauth_paths
Jun 30, 2026
Merged

feat: add virtualKeysByID secondary index and cache signing key + VK lookups on /mcp JWT auth path#4783
Pratham-Mishra04 merged 1 commit into
devfrom
06-29-fix_removes_db_ops_from_mcp_oauth_paths

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

The /mcp JWT authentication path previously performed a database read on every request to fetch the OAuth2 signing key and resolve virtual keys by ID. This PR eliminates those per-request DB reads by introducing a process-lifetime signing key cache on MCPServerHandler and a secondary in-memory index on LocalGovernanceStore that enables O(1) virtual key lookups by row ID.

Changes

  • Secondary VK index by ID: Added virtualKeysByID sync.Map to LocalGovernanceStore as a secondary index over virtualKeys, keyed by row ID instead of VK value. Introduced storeVirtualKey, deleteVirtualKeyByValue, and GetVirtualKeyByID to keep both maps in lock-step. All existing write paths (CreateVirtualKeyInMemory, UpdateVirtualKeyInMemory, DeleteVirtualKeyInMemory, rebuildInMemoryStructures, and reference-update helpers) now route through these helpers instead of writing virtualKeys directly.

  • Process-lifetime signing key cache: Added an atomic.Pointer[tables.OAuth2SigningKey] field (signingKey) to MCPServerHandler with a cachedSigningKey loader that reads from the config store once and reuses the result. The signing key is created via an idempotent insert and never rotated, making a process-lifetime cache safe. The cache is warmed at handler construction when OAuth discovery is enabled, replacing the equivalent warm-up that previously lived in Bootstrap.

  • RSA public key cache: Added a package-level mcpJWTPublicKeys sync.Map that caches parsed *rsa.PublicKey values keyed by public-key PEM content, so JWT verification skips PEM parsing on every request. verifyMCPJWT now accepts the signing key as a parameter rather than fetching it internally, separating key loading from verification.

  • VirtualKeyCache interface and getVirtualKeyByID helper: Introduced a VirtualKeyCache interface (GetVirtualKeyByID) satisfied by LocalGovernanceStore. MCPServerHandler holds an optional vkCache field; getVirtualKeyByID checks the cache first and falls back to the config store on a miss or when no cache is wired. Both the vk-mode and user-mode JWT auth paths now use this helper instead of calling the config store directly.

  • NewMCPServerHandler signature: Added a vkCache VirtualKeyCache parameter. The server wires the governance store's in-memory cache at startup if available, with a type-assertion guard so the dependency remains optional.

  • Test updates: Updated verifyMCPJWT call sites to pass the signing key explicitly. Split the config-fault test into TestVerifyMCPJWT_NilSigningKeyNotLabeledInvalidToken (nil key → verifyMCPJWT) and TestCachedSigningKey_ConfigFaults (nil store / load error → cachedSigningKey). Added nbf claims to rejection test cases that were missing them.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./plugins/governance/... ./transports/bifrost-http/...

Validate that:

  • /mcp requests authenticated via JWT succeed without triggering DB reads for the signing key or virtual key on subsequent calls.
  • A virtual key created after the governance store last refreshed is still resolved correctly (config store fallback path).
  • Inactive virtual keys are still rejected.
  • JWT rejection cases (wrong algorithm, wrong audience, nil signing key) still return appropriate errors and are never labeled as invalid token.

Breaking changes

  • Yes

NewMCPServerHandler now requires a vkCache VirtualKeyCache parameter (pass nil to preserve previous behaviour with config-store-only lookups).

Security considerations

  • The signing key cache is keyed by public-key PEM content, not kid, preventing a rotated key with a reused kid from aliasing the wrong cached key.
  • verifyMCPJWT no longer reads the signing key internally; the caller (getMCPServerForRequest) owns loading it and logs infrastructure faults distinctly so they are never surfaced to clients as token-validation errors.
  • The VirtualKeyCache interface is read-only; no write path is exposed to the transport layer.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9bc8e9f4-ea66-44e5-a85b-8248abc6bd2f

📥 Commits

Reviewing files that changed from the base of the PR and between fa29eb3 and 01f6a6a.

📒 Files selected for processing (10)
  • core/providers/anthropic/codeexecution_test.go
  • plugins/governance/store.go
  • transports/bifrost-http/handlers/mcpoauth2discovery.go
  • transports/bifrost-http/handlers/mcpoauth2issuance.go
  • transports/bifrost-http/handlers/mcpoauth2issuance_test.go
  • transports/bifrost-http/handlers/mcpoauth2jwt.go
  • transports/bifrost-http/handlers/mcpoauth2jwt_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/server/server.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Improved MCP OAuth handling with faster, cached signing-key and virtual-key lookups.
    • Added support for resolving virtual keys by ID, enabling more reliable authentication and server access.
  • Bug Fixes

    • JWT validation now reports missing signing keys more clearly instead of treating them as invalid tokens.
    • OAuth discovery and token issuance now use a consistent signing-key source, improving reliability across requests.
  • Performance

    • Reduced repeated key-loading work, which can improve request latency and startup behavior.

Walkthrough

Adds ID-based virtual-key lookup and OAuth2 signing-key caching across the governance store and MCP HTTP handlers, updates JWT verification to accept a pre-fetched signing key, and changes three Anthropic stream tests to use exported stream-state helpers.

Changes

MCP JWT and virtual-key caching

Layer / File(s) Summary
Governance store virtual-key index
plugins/governance/store.go
LocalGovernanceStore adds virtualKeysByID, plus helpers that store and delete virtual keys in both caches; rebuild, create, update, delete, and cascade paths now use those helpers.
Signing-key cache and JWT verification
transports/bifrost-http/lib/config.go, transports/bifrost-http/handlers/mcpoauth2jwt.go
Config caches the OAuth2 signing key in an atomic pointer; verifyMCPJWT now accepts an explicit signing key and uses a process-wide RSA public-key cache.
MCP handler cache wiring
transports/bifrost-http/handlers/mcpserver.go, transports/bifrost-http/server/server.go, transports/bifrost-http/handlers/mcpoauth2discovery.go, transports/bifrost-http/handlers/mcpoauth2issuance.go
MCPServerHandler gains VirtualKeyCache and getVirtualKeyByID; JWT auth and user-scoped VK lookup use the cache-aware path; route registration wires the governance cache; startup key bootstrapping is removed; JWKS and token issuance now call GetOAuth2SigningKey.
JWT and issuance tests
transports/bifrost-http/handlers/mcpoauth2jwt_test.go, transports/bifrost-http/handlers/mcpoauth2issuance_test.go
JWT verification tests pass explicit signing keys, adjust rejection payload claims, and split signing-key fault cases; issuance verification loads the signing key before calling verifyMCPJWT.

Anthropic stream-state test updates

Layer / File(s) Summary
Stream-state helper rename
core/providers/anthropic/codeexecution_test.go
Three streaming tests now call AcquireAnthropicResponsesStreamState and ReleaseAnthropicResponsesStreamState instead of the unexported helper names.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • akshaydeo
  • danpiths
  • roroghost17

Possibly related PRs

  • maximhq/bifrost#3599: Also changes plugins/governance/store.go virtual-key cache consistency behavior during updates.
  • maximhq/bifrost#3703: Related to virtual-key-by-ID lookup used in governance-backed MCP connection handling.
  • maximhq/bifrost#4504: Also modifies governance in-memory virtual-key cache keying and rehydration.

Poem

🐇 I hopped through caches, quick and bright,
One key by name, one key by site.
The JWT moon now shines just so,
And VK paths know where to go.
Soft paws on tests, the warren sings,
With exported helpers and tidy springs.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific, concise, and accurately summarizes the main changes to virtual-key indexing and JWT auth caching.
Description check ✅ Passed The description follows the template well with clear summary, changes, testing, breaking change, security, and checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-29-fix_removes_db_ops_from_mcp_oauth_paths

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

Pratham-Mishra04 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
plugins/governance/store.go Adds and maintains the by-ID virtual-key index alongside the existing value-keyed map.
transports/bifrost-http/handlers/mcpserver.go Uses cached signing-key loading and cache-first virtual-key lookup for MCP JWT requests.
transports/bifrost-http/lib/config.go Adds process-lifetime caching for the OAuth2 signing key.
transports/bifrost-http/handlers/mcpoauth2jwt.go Verifies JWTs with a supplied signing key and caches parsed RSA public keys.
transports/bifrost-http/server/server.go Wires the governance store as the optional virtual-key cache for MCP auth.

Reviews (6): Last reviewed commit: "fix: removes db ops from mcp oauth paths" | Re-trigger Greptile

Comment thread plugins/governance/store.go
Comment thread transports/bifrost-http/handlers/mcpserver.go
Comment thread transports/bifrost-http/handlers/mcpserver.go Outdated
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-fix_removes_db_ops_from_mcp_oauth_paths branch from b281729 to fa29eb3 Compare June 30, 2026 07:53
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-feat_adds_pagination_to_mcp_oauth_grants_table branch 2 times, most recently from df6e27c to 6ea5663 Compare June 30, 2026 07:56
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-fix_removes_db_ops_from_mcp_oauth_paths branch from fa29eb3 to 0609b71 Compare June 30, 2026 07:56

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@plugins/governance/store.go`:
- Around line 894-898: The virtual-key mutation path in
LocalGovernanceStore.storeVirtualKey is not fail-closed because the by-ID cache
can briefly retain stale auth data while the primary virtualKeys map changes.
Update the mutation flow to clear or evict the virtualKeysByID entry first
during updates/deletes/rebuilds, then write the new state so GetVirtualKeyByID
cannot serve the old active key during concurrent JWT auth. Review the other
virtual-key mutation helpers in the governance store that feed this index and
apply the same ordering consistently.

In `@transports/bifrost-http/handlers/mcpoauth2jwt.go`:
- Around line 31-36: The JWT key caching in mcpoauth2jwt.go is storing an
interior pointer from the parsed private key via parseRSAPrivateKeyPEM, which
unnecessarily keeps the private key allocation alive. Update the key-loading
logic around the public key cache to parse the standalone rsa.PublicKey from
signingKey.PublicKeyPEM instead of using &privKey.PublicKey, then store that
parsed public key in mcpJWTPublicKeys so verification only retains the public
material.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 02bcd1d2-84d0-4f78-bad2-4f28393bd020

📥 Commits

Reviewing files that changed from the base of the PR and between df6e27c and fa29eb3.

📒 Files selected for processing (6)
  • plugins/governance/store.go
  • transports/bifrost-http/handlers/mcpoauth2issuance_test.go
  • transports/bifrost-http/handlers/mcpoauth2jwt.go
  • transports/bifrost-http/handlers/mcpoauth2jwt_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/server/server.go

Comment thread plugins/governance/store.go
Comment thread transports/bifrost-http/handlers/mcpoauth2jwt.go Outdated
Comment thread plugins/governance/store.go Outdated
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-feat_adds_pagination_to_mcp_oauth_grants_table branch from 6ea5663 to afb130d Compare June 30, 2026 11:43
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-fix_removes_db_ops_from_mcp_oauth_paths branch from 0609b71 to 5f80820 Compare June 30, 2026 11:43
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 30, 2026

Pratham-Mishra04 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Jun 30, 1:53 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 30, 2:47 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 30, 2:48 PM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from 06-29-feat_adds_pagination_to_mcp_oauth_grants_table to graphite-base/4783 June 30, 2026 14:43
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/4783 to dev June 30, 2026 14:46
@Pratham-Mishra04
Pratham-Mishra04 dismissed coderabbitai[bot]’s stale review June 30, 2026 14:46

The base branch was changed.

@Pratham-Mishra04
Pratham-Mishra04 requested a review from a team as a code owner June 30, 2026 14:46
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-fix_removes_db_ops_from_mcp_oauth_paths branch from fabed5e to 01f6a6a Compare June 30, 2026 14:46
@Pratham-Mishra04
Pratham-Mishra04 merged commit 02cf6f8 into dev Jun 30, 2026
14 of 16 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 06-29-fix_removes_db_ops_from_mcp_oauth_paths branch June 30, 2026 14:48
@coderabbitai
coderabbitai Bot requested a review from roroghost17 June 30, 2026 14:50
akshaydeo pushed a commit that referenced this pull request Jul 1, 2026
…K lookups on `/mcp` JWT auth path (#4783)

## Summary

The `/mcp` JWT authentication path previously performed a database read on every request to fetch the OAuth2 signing key and resolve virtual keys by ID. This PR eliminates those per-request DB reads by introducing a process-lifetime signing key cache on `MCPServerHandler` and a secondary in-memory index on `LocalGovernanceStore` that enables O(1) virtual key lookups by row ID.

## Changes

- **Secondary VK index by ID**: Added `virtualKeysByID sync.Map` to `LocalGovernanceStore` as a secondary index over `virtualKeys`, keyed by row ID instead of VK value. Introduced `storeVirtualKey`, `deleteVirtualKeyByValue`, and `GetVirtualKeyByID` to keep both maps in lock-step. All existing write paths (`CreateVirtualKeyInMemory`, `UpdateVirtualKeyInMemory`, `DeleteVirtualKeyInMemory`, `rebuildInMemoryStructures`, and reference-update helpers) now route through these helpers instead of writing `virtualKeys` directly.

- **Process-lifetime signing key cache**: Added an `atomic.Pointer[tables.OAuth2SigningKey]` field (`signingKey`) to `MCPServerHandler` with a `cachedSigningKey` loader that reads from the config store once and reuses the result. The signing key is created via an idempotent insert and never rotated, making a process-lifetime cache safe. The cache is warmed at handler construction when OAuth discovery is enabled, replacing the equivalent warm-up that previously lived in `Bootstrap`.

- **RSA public key cache**: Added a package-level `mcpJWTPublicKeys sync.Map` that caches parsed `*rsa.PublicKey` values keyed by public-key PEM content, so JWT verification skips PEM parsing on every request. `verifyMCPJWT` now accepts the signing key as a parameter rather than fetching it internally, separating key loading from verification.

- **`VirtualKeyCache` interface and `getVirtualKeyByID` helper**: Introduced a `VirtualKeyCache` interface (`GetVirtualKeyByID`) satisfied by `LocalGovernanceStore`. `MCPServerHandler` holds an optional `vkCache` field; `getVirtualKeyByID` checks the cache first and falls back to the config store on a miss or when no cache is wired. Both the vk-mode and user-mode JWT auth paths now use this helper instead of calling the config store directly.

- **`NewMCPServerHandler` signature**: Added a `vkCache VirtualKeyCache` parameter. The server wires the governance store's in-memory cache at startup if available, with a type-assertion guard so the dependency remains optional.

- **Test updates**: Updated `verifyMCPJWT` call sites to pass the signing key explicitly. Split the config-fault test into `TestVerifyMCPJWT_NilSigningKeyNotLabeledInvalidToken` (nil key → `verifyMCPJWT`) and `TestCachedSigningKey_ConfigFaults` (nil store / load error → `cachedSigningKey`). Added `nbf` claims to rejection test cases that were missing them.

## Type of change

- [ ] Bug fix
- [x] Feature
- [x] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./plugins/governance/... ./transports/bifrost-http/...
```

Validate that:
- `/mcp` requests authenticated via JWT succeed without triggering DB reads for the signing key or virtual key on subsequent calls.
- A virtual key created after the governance store last refreshed is still resolved correctly (config store fallback path).
- Inactive virtual keys are still rejected.
- JWT rejection cases (wrong algorithm, wrong audience, nil signing key) still return appropriate errors and are never labeled as `invalid token`.

## Breaking changes

- [x] Yes

`NewMCPServerHandler` now requires a `vkCache VirtualKeyCache` parameter (pass `nil` to preserve previous behaviour with config-store-only lookups).

## Security considerations

- The signing key cache is keyed by public-key PEM content, not `kid`, preventing a rotated key with a reused `kid` from aliasing the wrong cached key.
- `verifyMCPJWT` no longer reads the signing key internally; the caller (`getMCPServerForRequest`) owns loading it and logs infrastructure faults distinctly so they are never surfaced to clients as token-validation errors.
- The `VirtualKeyCache` interface is read-only; no write path is exposed to the transport layer.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
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.

3 participants