v2.2.1
Changelog
v2.2.1 moves the enterprise gateway onto OSS transports v2.2.1. API key scopes are now clamped to the caller’s own permissions at create and update. Virtual key search now matches the assigned user’s name or email, and the assignee column is resolved in one batched query per page instead of one request per row. The OSS release also carries three security fixes: resource IDs are validated before they enter outbound URL paths, Bedrock S3 bucket names can no longer redirect signed requests to a caller-chosen host, and the billing idempotency key can no longer be forged through thex-request-id header.✨ Features
- API Key Scope Ceiling - Creating or updating an API key now clamps the granted scopes to the permissions the caller holds. Unknown permission ids are rejected with 400, scopes the caller does not hold are rejected with 403 naming the offending permissions, and a key’s own scopes win the permission check even when the creator’s role is broader. Local-admin and auth-disabled requests keep their existing bypass.
- Scope Ceiling in the API Key Picker - The scope picker shows the ceiling before save: permissions above the caller’s own are rendered locked with a lock icon and cannot be toggled on, while a locked scope already on the key stays removable so access can always be lowered. If the server still returns a 403 because the permission map went stale, the denied scopes are highlighted inline and the caller’s permissions are re-fetched. The picker is built on a shared
PermissionMatrixcomponent with search and per-resource bulk select, now used by both API key scopes and role permissions. - Virtual Key Assignee Search and Batched Resolution - Virtual key search now matches the assigned user’s name or email through the enterprise store, which previously delegated search to the base store and silently dropped the assignee filter. The “Assigned To” column on the list page and in CSV export is resolved with a single batched query per page instead of one request per key.
- Access-Profile MCP Grants in DAC Scopes - Own-data and team-data scoped reads now treat access-profile grants as a first-class visibility path for MCP servers and Virtual MCPs. An MCP server granted exclusively through a user’s access profile was previously invisible to that user under DAC scoping, because access-profile-managed virtual keys intentionally carry no VK-to-MCP assignment rows.
- Unknown MCP Calls Recorded as Observations - When an MCP tool call cannot be matched to any known installation in the device inventory (stale inventory, removed server, unrecognized name), the gateway now records it as an observation with
status=unknownand full attribution metadata instead of issuing a hard denial, and the rest of the batch continues under normal enforcement. True policy denials and guardrail failures are still enforced strictly.
🌎 Open Source Features
- Virtual Key Assignees and Expanded Search - The virtual key list resolves
assigned_userfor every row via a single batch lookup, and VK search matches team, customer and user names in addition to key fields, so keys can be found by who owns them. - User Attribution in Prometheus Metrics - The telemetry plugin’s metrics export includes user id and user name labels, so per-user usage and error rates can be graphed and alerted on directly.
- Prompt Cache Breakpoints Capability - A
SupportsPromptCacheBreakpointsmodel capability with a name-based fallback for OpenRouter gatesprompt_cache_breakpointforwarding, so OpenRouter’s non-Claude models stop rejecting requests that carry it. - MCP Observed Latency - Native (observed) MCP tool-call latency from
observed_latency_msis surfaced in the logs duration column and detail sheet, kept clearly distinct from true execution time and never fabricating a synthetic start timestamp on the timeline.
🐞 Fixed
- Access Profile Role-Detach Revocation Scope - Detaching a role from an access profile now revokes the profile only from the detached role’s users, sparing users whose remaining role still grants the profile.
- Resource ID Path Injection (OSS) - Caller-supplied resource IDs (batch, file, video, container, response IDs, cached content names) were interpolated into outbound URL paths unvalidated; a crafted ID could redirect the request to an unintended upstream endpoint. A central
EscapeResourceIDhelper validates and escapes every such ID. - Bedrock S3 SSRF (OSS) - A caller-supplied
s3://file ID ors3_bucketparam could control the upstream host of a SigV4-signed connection; bucket names are now validated with a DNS-compatible regex. - Caller-Forged Billing Idempotency (OSS) - The billing idempotency key included the caller-controllable
x-request-id, so two requests sharing a chosen ID collided and the second was never charged. An internally mintedBillingNonceis mixed into the key, making it unforgeable. - Abandoned Non-Streaming Request Hung Forever (OSS) - The worker kept a
ctx.Done()arm on an already-claimed delivery send, so a non-streaming caller could hang indefinitely. - Repeated Empty Thinking Blocks (OSS) - Streaming chat chunks emitted empty reasoning/message fields on every content delta, which clients rendered as repeated empty thinking blocks.
- Claude Code Thread Continuations Behind Key Rotation (OSS) - Server-side conversation threads are bound to the upstream account that creates them, which per-request key selection cannot guarantee; the Anthropic integration now declares itself stateless, refusing continuations with a 400
thread_unsupported_requestso the client resends the turn in full, and strippingthreadfrom create requests so no orphaned thread state accumulates upstream. - Gemini Flash-Lite Minimal Thinking Promoted to Low (OSS) -
gemini-3.1-flash-literequests silently promotedminimalthinking tolowon Gemini and Vertex; the text model’s four supported levels are now registered. - Config.json Virtual Key Limits Broke Under UI Edits (OSS) - VK rate limits and budgets created via the config.json standalone-limits flow and then edited through the UI produced duplicate, conflicting ownership records; a migration consolidates ownership into VK-scoped model configs while preserving usage counters.
- Bedrock Service Tier Rejections (OSS) - Service tier forwarding for Bedrock is now gated on explicit model capability metadata, failing closed when none exists.
- Anthropic Root-Level Tool Schema Compositions (OSS) - Root-level
oneOf/anyOf/allOfin a tool’sinput_schemaare rewritten into a flat object schema before dispatch, unblocking tools that Anthropic would otherwise reject. - GenAI Streaming TTS (OSS) - Speech stream chunks routed through the
/genaiintegration had no converter registered; streaming TTS now works, and a missing stream converter returns a clean error instead of panicking. - xAI Usage and Cost (OSS) -
completion_tokensnow folds reasoning tokens in so token totals add up, and streaming cost normalization preserves xAI’s authoritative cost ticks instead of falling back to catalog pricing. - OpenAI-Only
search_content_typesForwarded Everywhere (OSS) - The field is gated behind a per-provider capability check so Bedrock and other OpenAI-compatible backends receive a cleanweb_searchtool. - Claude Code
diagnosticsField Rejected by Non-Native Providers (OSS) -diagnostics.previous_message_idis stripped for providers that do not support it, fixing 400s on Bedrock, Vertex and Azure. - Unreadable Bedrock Error Logs (OSS) - AWS’s flat
{"message", "__type"}error shape now seeds the error message, and the logs UI falls back to the raw provider error body when no message could be extracted. - Anthropic
containerParam Dropped (OSS) - The string-formcontainerparam on/anthropic/v1/messagesis now carried through the round trip, so container reuse stops provisioning a fresh container every time. - Responses API Finish Reason Missing From OTEL Spans (OSS) -
gen_ai.response.finish_reasonis now emitted on thellm.callspan for/v1/responsesrequests, matching the chat path. - Cohere Fallback Response Shape (OSS) - Responses served by a fallback from a non-Cohere provider are converted to the Cohere v2 shape instead of returning the raw normalized response.
- Skill Serving Race (OSS) - Fixed a race condition in the skill serving handler.
- Log and Dashboard Label Truncation (OSS) - Long model and provider labels truncate from the start so the distinctive suffix stays visible, the logs model column is wider, and key picker options are keyed by id so duplicate labels stop highlighting together.
🗄️ Database Migrations
- migrate_vk_standalone_limits_to_model_configs - Consolidates virtual key standalone budgets and rate limits into VK-scoped model configs: standalone budgets are re-pointed to the VK’s top-level model config (created if missing, usage preserved), orphaned duplicate UUID rate-limit rows created by the UI are deleted with the model config re-pointed to the canonical config.json row, and
vk.rate_limit_idis cleared.
🐙 Closed OSS Issues
- #123 - Files API Support
- #5707 - string-form
containerparam on /anthropic/v1/messages is silently dropped - container reuse provisions a fresh container - #7204 - Responses API path never sets gen_ai.response.finish_reason in OTEL traces (refusals invisible to observability)
- #7287 - Gemini 3.1 Flash-Lite minimal thinking is silently promoted to low
- #7294 - Streaming chat chunks emit empty reasoning/message fields on every content delta, causing repeated thinking blocks
- #7308 - non-streaming caller hangs forever - worker keeps a ctx.Done() arm on a claimed delivery send (regression from #6972)
📀 Base OSS version
transports/v2.2.1 (pinned as github.com/maximhq/bifrost/transports v1.6.12-0.20260918214249-f44b5410fb7b), with core v1.9.1, framework v1.7.2, governance v1.8.1, and logging v1.8.1.
