Skip to main content
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 the x-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 PermissionMatrix component 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=unknown and 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_user for 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 SupportsPromptCacheBreakpoints model capability with a name-based fallback for OpenRouter gates prompt_cache_breakpoint forwarding, so OpenRouter’s non-Claude models stop rejecting requests that carry it.
  • MCP Observed Latency - Native (observed) MCP tool-call latency from observed_latency_ms is 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 EscapeResourceID helper validates and escapes every such ID.
  • Bedrock S3 SSRF (OSS) - A caller-supplied s3:// file ID or s3_bucket param 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 minted BillingNonce is 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_request so the client resends the turn in full, and stripping thread from create requests so no orphaned thread state accumulates upstream.
  • Gemini Flash-Lite Minimal Thinking Promoted to Low (OSS) - gemini-3.1-flash-lite requests silently promoted minimal thinking to low on 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/allOf in a tool’s input_schema are 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 /genai integration 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_tokens now 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_types Forwarded Everywhere (OSS) - The field is gated behind a per-provider capability check so Bedrock and other OpenAI-compatible backends receive a clean web_search tool.
  • Claude Code diagnostics Field Rejected by Non-Native Providers (OSS) - diagnostics.previous_message_id is 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 container Param Dropped (OSS) - The string-form container param on /anthropic/v1/messages is 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_reason is now emitted on the llm.call span for /v1/responses requests, 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_id is cleared.

🐙 Closed OSS Issues

  • #123 - Files API Support
  • #5707 - string-form container param 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.

🔌 If you are compiling plugin against this release - use following deps