Skip to content

Commit 54afe9e

Browse files
feat: add opt-in per-request overrides for content logging and raw request/response visibility (#3066)
## Summary Adds two opt-in configuration flags — `allow_per_request_content_storage_override` and `allow_per_request_raw_override` — that gate whether per-request context keys and HTTP headers can override the global content logging and raw request/response visibility settings. When enabled, callers can suppress sensitive content (e.g. PII, credentials) from log records on specific requests via `x-bf-disable-content-logging`, or override raw capture behavior via `x-bf-send-back-raw-request`/`x-bf-send-back-raw-response`, without changing global configuration. When disabled (the default), provider-level and global settings remain authoritative and per-request overrides are silently ignored. ## Changes - Added `BifrostContextKeyDisableContentLogging`, `BifrostContextKeyAllowPerRequestStorageOverride`, and `BifrostContextKeyAllowPerRequestRawOverride` to the `BifrostContextKey` enum. - Introduced a `contentLoggingEnabled(ctx)` helper on `LoggerPlugin` that checks the per-request context override (only when `BifrostContextKeyAllowPerRequestStorageOverride` is set) before falling back to the global `disableContentLogging` config. This replaces all inline `p.disableContentLogging == nil || !*p.disableContentLogging` checks throughout the logging plugin. - Propagated the resolved `contentLoggingEnabled` bool as an explicit parameter to `updateLogEntry`, `applyStreamingOutputToEntry`, `applyNonStreamingOutputToEntry`, and `applyRealtimeOutputToEntry`, removing the redundant local re-evaluation inside `applyRealtimeOutputToEntry`. - Gated the existing `BifrostContextKeySendBackRawRequest`, `BifrostContextKeySendBackRawResponse`, and `BifrostContextKeyStoreRawRequestResponse` per-request overrides in `requestWorker` behind the new `BifrostContextKeyAllowPerRequestRawOverride` flag. - Added `AllowPerRequestContentStorageOverride` and `AllowPerRequestRawOverride` fields to `ClientConfig` with corresponding hash entries, `Config` accessor methods, and two new methods on the `HandlerStore` interface (`ShouldAllowPerRequestStorageOverride`, `ShouldAllowPerRequestRawOverride`). - Refactored `ConvertToBifrostContext` to accept a `HandlerStore` instead of individual parameters, propagating the new override flags into the bifrost context at request ingress. - Added UI toggles for both new flags in the Logs Settings view and updated the `CoreConfig` TypeScript type and defaults. - Added tests covering all precedence combinations (no config, global on/off, ctx override on/off, nil ctx), as well as integration-style tests for `updateLogEntry` and the apply-output helpers verifying both suppression and force-enable behavior. - Documented the new `x-bf-disable-content-logging` option in `docs/providers/request-options.mdx` with cURL and Go SDK examples, precedence rules, and a prerequisite callout for `allow_per_request_content_storage_override`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [x] Docs ## How to test ```sh go test ./plugins/logging/... ./transports/bifrost-http/lib/... ``` To validate end-to-end via the gateway, first enable `allow_per_request_content_storage_override` in your logging config, then send a chat completion request with the header and confirm the log record omits message content while still recording token counts and latency: ```sh curl --location 'http://localhost:8080/v1/chat/completions' \ --header 'x-bf-disable-content-logging: true' \ --header 'Content-Type: application/json' \ --data '{ "model": "openai/gpt-4o-mini", "messages": [{"role": "user", "content": "Sensitive data here"}] }' ``` The resulting log record should have empty `input_history`, `output_message`, and `raw_request`/`raw_response` fields, while `total_tokens`, `latency`, and routing metadata remain populated. To verify the gate is enforced, send the same request without enabling `allow_per_request_content_storage_override` — the header should be ignored and content should appear in the log record as normal. ## Breaking changes - [x] No ## Security considerations Both override flags default to `false`, meaning existing deployments are unaffected on upgrade. The per-request override is applied only at log-write time and does not affect what is sent to the upstream provider. Operators should consider carefully before enabling `allow_per_request_raw_override`, as it permits callers to request that raw provider payloads be returned in API responses. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
1 parent 4be4b78 commit 54afe9e

25 files changed

Lines changed: 855 additions & 357 deletions

File tree

core/bifrost.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5526,16 +5526,24 @@ func (bifrost *Bifrost) requestWorker(provider schemas.Provider, config *schemas
55265526

55275527
// Step 1: compute effective value for each flag (provider config ← per-request override).
55285528
effectiveSendBackReq := config.SendBackRawRequest
5529-
if override, ok := req.Context.Value(schemas.BifrostContextKeySendBackRawRequest).(bool); ok {
5530-
effectiveSendBackReq = override
5529+
allowRawOverride, _ := req.Context.Value(schemas.BifrostContextKeyAllowPerRequestRawOverride).(bool)
5530+
if allowRawOverride {
5531+
if override, ok := req.Context.Value(schemas.BifrostContextKeySendBackRawRequest).(bool); ok {
5532+
effectiveSendBackReq = override
5533+
}
55315534
}
55325535
effectiveSendBackResp := config.SendBackRawResponse
5533-
if override, ok := req.Context.Value(schemas.BifrostContextKeySendBackRawResponse).(bool); ok {
5534-
effectiveSendBackResp = override
5536+
if allowRawOverride {
5537+
if override, ok := req.Context.Value(schemas.BifrostContextKeySendBackRawResponse).(bool); ok {
5538+
effectiveSendBackResp = override
5539+
}
55355540
}
55365541
effectiveStore := config.StoreRawRequestResponse
5537-
if override, ok := req.Context.Value(schemas.BifrostContextKeyStoreRawRequestResponse).(bool); ok {
5538-
effectiveStore = override
5542+
allowStorageOverride, _ := req.Context.Value(schemas.BifrostContextKeyAllowPerRequestStorageOverride).(bool)
5543+
if allowStorageOverride {
5544+
if override, ok := req.Context.Value(schemas.BifrostContextKeyStoreRawRequestResponse).(bool); ok {
5545+
effectiveStore = override
5546+
}
55395547
}
55405548

55415549
// Step 2: derive per-side capture and strip flags.

core/schemas/bifrost.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ const (
248248
BifrostContextKeyRealtimeEventType BifrostContextKey = "bifrost-realtime-event-type" // string
249249
BifrostIsAsyncRequest BifrostContextKey = "bifrost-is-async-request" // bool (set by bifrost - DO NOT SET THIS MANUALLY)) - whether the request is an async request (only used in gateway)
250250
BifrostContextKeyRequestHeaders BifrostContextKey = "bifrost-request-headers" // map[string]string (all request headers with lowercased keys)
251+
BifrostContextKeyAllowPerRequestStorageOverride BifrostContextKey = "bifrost-allow-per-request-storage-override" // bool (set by transport from config — gates whether x-bf-disable-content-logging and x-bf-store-raw-request-response per-request overrides are honored)
252+
BifrostContextKeyAllowPerRequestRawOverride BifrostContextKey = "bifrost-allow-per-request-raw-override" // bool (set by transport from config — gates whether x-bf-send-back-raw-request and x-bf-send-back-raw-response per-request overrides are honored)
253+
BifrostContextKeyDisableContentLogging BifrostContextKey = "x-bf-disable-content-logging" // bool (per-request override for content logging; only honored when BifrostContextKeyAllowPerRequestStorageOverride is true)
251254
BifrostContextKeySkipListModelsGovernanceFiltering BifrostContextKey = "bifrost-skip-list-models-governance-filtering" // bool (set by bifrost - DO NOT SET THIS MANUALLY))
252255
BifrostContextKeySCIMClaims BifrostContextKey = "scim_claims"
253256
BifrostContextKeyUserID BifrostContextKey = "bifrost-user-id" // string (to store the user ID (set by enterprise auth middleware - DO NOT SET THIS MANUALLY))

docs/openapi/schemas/management/config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ ClientConfig:
3131
disable_content_logging:
3232
type: boolean
3333
description: Whether content logging is disabled
34+
allow_per_request_content_storage_override:
35+
type: boolean
36+
default: false
37+
description: Allow individual requests to override content storage via the x-bf-disable-content-logging header or context key. When false (default), per-request overrides are ignored.
38+
allow_per_request_raw_override:
39+
type: boolean
40+
default: false
41+
description: Allow individual requests to override raw request/response visibility via the x-bf-send-back-raw-request and x-bf-send-back-raw-response headers. When false (default), provider-level settings are authoritative and per-request overrides are ignored.
3442
enforce_auth_on_inference:
3543
type: boolean
3644
description: Whether to enforce virtual key authentication on inference requests

docs/providers/request-options.mdx

Lines changed: 311 additions & 201 deletions
Large diffs are not rendered by default.

framework/configstore/clientconfig.go

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,36 @@ type CompatConfig struct {
4646
// ClientConfig represents the core configuration for Bifrost HTTP transport and the Bifrost Client.
4747
// It includes settings for excess request handling, Prometheus metrics, and initial pool size.
4848
type ClientConfig struct {
49-
DropExcessRequests bool `json:"drop_excess_requests"` // Drop excess requests if the provider queue is full
50-
InitialPoolSize int `json:"initial_pool_size"` // The initial pool size for the bifrost client
51-
PrometheusLabels []string `json:"prometheus_labels"` // The labels to be used for prometheus metrics
52-
EnableLogging *bool `json:"enable_logging"` // Enable logging of requests and responses
53-
DisableContentLogging bool `json:"disable_content_logging"` // Disable logging of content
54-
DisableDBPingsInHealth bool `json:"disable_db_pings_in_health"`
55-
LogRetentionDays int `json:"log_retention_days" validate:"min=1"` // Number of days to retain logs (minimum 1 day)
56-
EnforceAuthOnInference bool `json:"enforce_auth_on_inference"` // Require auth (VK, API key, or user token) on inference endpoints
57-
EnforceGovernanceHeader bool `json:"enforce_governance_header,omitempty"` // Deprecated: use EnforceAuthOnInference
58-
EnforceSCIMAuth bool `json:"enforce_scim_auth,omitempty"` // Deprecated: use EnforceAuthOnInference
59-
AllowDirectKeys bool `json:"allow_direct_keys"` // Allow direct keys to be used for requests
60-
AllowedOrigins []string `json:"allowed_origins,omitempty"` // Additional allowed origins for CORS and WebSocket (localhost is always allowed)
61-
AllowedHeaders []string `json:"allowed_headers,omitempty"` // Additional allowed headers for CORS and WebSocket
62-
MaxRequestBodySizeMB int `json:"max_request_body_size_mb"` // The maximum request body size in MB
63-
Compat CompatConfig `json:"compat"` // Compat plugin configuration
64-
MCPAgentDepth int `json:"mcp_agent_depth"` // The maximum depth for MCP agent mode tool execution
65-
MCPToolExecutionTimeout int `json:"mcp_tool_execution_timeout"` // The timeout for individual tool execution in seconds
66-
MCPCodeModeBindingLevel string `json:"mcp_code_mode_binding_level"` // Code mode binding level: "server" or "tool"
67-
MCPToolSyncInterval int `json:"mcp_tool_sync_interval"` // Global tool sync interval in minutes (default: 10, 0 = disabled)
68-
MCPDisableAutoToolInject bool `json:"mcp_disable_auto_tool_inject"` // When true, MCP tools are not injected into requests by default
69-
HeaderFilterConfig *tables.GlobalHeaderFilterConfig `json:"header_filter_config,omitempty"` // Global header filtering configuration for x-bf-eh-* headers
70-
AsyncJobResultTTL int `json:"async_job_result_ttl"` // Default TTL for async job results in seconds (default: 3600 = 1 hour)
71-
RequiredHeaders []string `json:"required_headers,omitempty"` // Headers that must be present on every request (case-insensitive)
72-
LoggingHeaders []string `json:"logging_headers,omitempty"` // Headers to capture in log metadata
73-
WhitelistedRoutes []string `json:"whitelisted_routes,omitempty"` // Routes that bypass auth middleware
74-
HideDeletedVirtualKeysInFilters bool `json:"hide_deleted_virtual_keys_in_filters"` // Hide deleted virtual keys from logs/MCP filter data
75-
RoutingChainMaxDepth int `json:"routing_chain_max_depth"` // Maximum depth for routing rule chain evaluation (default: 10)
76-
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
49+
DropExcessRequests bool `json:"drop_excess_requests"` // Drop excess requests if the provider queue is full
50+
InitialPoolSize int `json:"initial_pool_size"` // The initial pool size for the bifrost client
51+
PrometheusLabels []string `json:"prometheus_labels"` // The labels to be used for prometheus metrics
52+
EnableLogging *bool `json:"enable_logging"` // Enable logging of requests and responses
53+
DisableContentLogging bool `json:"disable_content_logging"` // Disable logging of content
54+
AllowPerRequestContentStorageOverride bool `json:"allow_per_request_content_storage_override"` // Allow per-request override of content storage via x-bf-disable-content-logging header/context
55+
AllowPerRequestRawOverride bool `json:"allow_per_request_raw_override"` // Allow per-request override of raw request/response visibility via x-bf-send-back-raw-request and x-bf-send-back-raw-response headers
56+
DisableDBPingsInHealth bool `json:"disable_db_pings_in_health"`
57+
LogRetentionDays int `json:"log_retention_days" validate:"min=1"` // Number of days to retain logs (minimum 1 day)
58+
EnforceAuthOnInference bool `json:"enforce_auth_on_inference"` // Require auth (VK, API key, or user token) on inference endpoints
59+
EnforceGovernanceHeader bool `json:"enforce_governance_header,omitempty"` // Deprecated: use EnforceAuthOnInference
60+
EnforceSCIMAuth bool `json:"enforce_scim_auth,omitempty"` // Deprecated: use EnforceAuthOnInference
61+
AllowDirectKeys bool `json:"allow_direct_keys"` // Allow direct keys to be used for requests
62+
AllowedOrigins []string `json:"allowed_origins,omitempty"` // Additional allowed origins for CORS and WebSocket (localhost is always allowed)
63+
AllowedHeaders []string `json:"allowed_headers,omitempty"` // Additional allowed headers for CORS and WebSocket
64+
MaxRequestBodySizeMB int `json:"max_request_body_size_mb"` // The maximum request body size in MB
65+
Compat CompatConfig `json:"compat"` // Compat plugin configuration
66+
MCPAgentDepth int `json:"mcp_agent_depth"` // The maximum depth for MCP agent mode tool execution
67+
MCPToolExecutionTimeout int `json:"mcp_tool_execution_timeout"` // The timeout for individual tool execution in seconds
68+
MCPCodeModeBindingLevel string `json:"mcp_code_mode_binding_level"` // Code mode binding level: "server" or "tool"
69+
MCPToolSyncInterval int `json:"mcp_tool_sync_interval"` // Global tool sync interval in minutes (default: 10, 0 = disabled)
70+
MCPDisableAutoToolInject bool `json:"mcp_disable_auto_tool_inject"` // When true, MCP tools are not injected into requests by default
71+
HeaderFilterConfig *tables.GlobalHeaderFilterConfig `json:"header_filter_config,omitempty"` // Global header filtering configuration for x-bf-eh-* headers
72+
AsyncJobResultTTL int `json:"async_job_result_ttl"` // Default TTL for async job results in seconds (default: 3600 = 1 hour)
73+
RequiredHeaders []string `json:"required_headers,omitempty"` // Headers that must be present on every request (case-insensitive)
74+
LoggingHeaders []string `json:"logging_headers,omitempty"` // Headers to capture in log metadata
75+
WhitelistedRoutes []string `json:"whitelisted_routes,omitempty"` // Routes that bypass auth middleware
76+
HideDeletedVirtualKeysInFilters bool `json:"hide_deleted_virtual_keys_in_filters"` // Hide deleted virtual keys from logs/MCP filter data
77+
RoutingChainMaxDepth int `json:"routing_chain_max_depth"` // Maximum depth for routing rule chain evaluation (default: 10)
78+
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
7779
}
7880

7981
// GenerateClientConfigHash generates a SHA256 hash of the client configuration.
@@ -174,6 +176,15 @@ func (c *ClientConfig) GenerateClientConfigHash() (string, error) {
174176
hash.Write([]byte("mcpDisableAutoToolInject:true"))
175177
}
176178

179+
// Only hash non-default value to avoid legacy config hash churn on upgrade.
180+
if c.AllowPerRequestContentStorageOverride {
181+
hash.Write([]byte("allowPerRequestContentStorageOverride:true"))
182+
}
183+
184+
if c.AllowPerRequestRawOverride {
185+
hash.Write([]byte("allowPerRequestRawOverride:true"))
186+
}
187+
177188
if c.AsyncJobResultTTL > 0 {
178189
hash.Write([]byte("asyncJobResultTTL:" + strconv.Itoa(c.AsyncJobResultTTL)))
179190
} else {

plugins/logging/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- feat: added per-request content logging toggle that overrides the global setting

0 commit comments

Comments
 (0)