Skip to content

Commit c7aa3ca

Browse files
feat: add db migrations and live-reload for per-request content storage and raw override flags (#3117)
## Summary Persisting raw provider bytes in logs is gated on content logging being active. This PR makes that dependency explicit in the database schema, config store, HTTP handler, UI, and documentation — and removes a spurious restart requirement when toggling `disable_content_logging`. ## Changes - Added `allow_per_request_content_storage_override` and `allow_per_request_raw_override` columns to `TableClientConfig` with corresponding database migrations, and wired both fields through `UpdateClientConfig` / `GetClientConfig` in the RDB config store. - Propagated both new fields through the HTTP config update handler so they are persisted when the settings are changed via the API or UI. - Removed the restart trigger for `disable_content_logging` changes in both the Go handler and the React UI, since the logging plugin holds a live pointer to `ClientConfig` and picks up the new value on the next request without a restart. - Updated the UI description for **Disable Content Logging** to make clear that raw provider bytes (`store_raw_request_response`) are also dropped when content logging is off, while send-back behavior (`send_back_raw_*`) is unaffected. - Updated the UI description for **Allow Per-Request Content Storage Override** to clarify that raw-byte storage requires content logging to be on — either globally or via `x-bf-disable-content-logging: false` on the same request. - Added a documentation warning in `request-options.mdx` stating that raw bytes are only persisted when content logging is active, and updated the `x-bf-disable-content-logging` header description to reflect that it also gates raw-byte storage and can be used to opt a single request into full content+raw capture while content logging is globally disabled. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [x] Docs ## How to test ```sh # Core/Transports go test ./framework/configstore/... ./transports/bifrost-http/... # Verify migrations run cleanly against a fresh or existing DB # Start the gateway and confirm allow_per_request_content_storage_override # and allow_per_request_raw_override columns exist in config_client. # UI cd ui pnpm i pnpm build ``` 1. Enable logging, then toggle **Disable Content Logging** — confirm no restart banner appears. 2. Enable **Allow Per-Request Content Storage Override** and send a request with `x-bf-store-raw-request-response: true` while `x-bf-disable-content-logging: true` — confirm raw bytes are absent from the log record. 3. Repeat with `x-bf-disable-content-logging: false` — confirm raw bytes are present. ## Breaking changes - [ ] Yes - [x] No ## Security considerations `allow_per_request_content_storage_override` and `allow_per_request_raw_override` are opt-in and default to `false`, so existing deployments retain their current behavior. Operators must explicitly enable these flags before per-request headers can influence content or raw-byte storage, preserving the existing access control boundary around sensitive log data. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] 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 fa2e537 commit c7aa3ca

6 files changed

Lines changed: 184 additions & 86 deletions

File tree

docs/providers/request-options.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@ This is orthogonal to the send-back flags: enabling this does not affect whether
418418
Per-request overrides are **disabled by default**. You must first enable `allow_per_request_content_storage_override` in your logging configuration (or in the UI under **Logs Settings**) before this header or context key has any effect. Note that this is gated by the **content storage** override, not the raw override — `allow_per_request_raw_override` only gates `x-bf-send-back-raw-request` and `x-bf-send-back-raw-response` (sending raw bytes back to the caller).
419419
</Warning>
420420
421+
<Warning>
422+
**Content logging must also be enabled for raw bytes to be persisted.** The logging plugin only writes raw bytes when content logging is on — i.e. either global `disable_content_logging` is `false`, or the request sets `x-bf-disable-content-logging: false` (with `allow_per_request_content_storage_override` enabled). If content logging is off, raw bytes are dropped from the log row even when `x-bf-store-raw-request-response: true`.
423+
</Warning>
424+
421425
<Tabs>
422426
<Tab title="Gateway (cURL)">
423427
```bash
@@ -460,9 +464,9 @@ Input: messages,
460464
**Type:** `bool` (header values: `"true"` or `"false"`)
461465
**Required:** No
462466

463-
Override the logging plugin's global `disable_content_logging` config for a single request. When set to `true`, messages, parameters, tool arguments, and tool results are omitted from the log record for that request. When set to `false`, content is recorded even if the global toggle is off.
467+
Override the logging plugin's global `disable_content_logging` config for a single request. When set to `true`, messages, parameters, tool arguments, tool results, **and raw provider bytes** are omitted from the log record for that request. When set to `false`, content (and raw bytes, if `x-bf-store-raw-request-response` is also enabled) is recorded even if the global toggle is off.
464468
465-
This is useful when you need to suppress sensitive data (e.g. PII, credentials) for specific requests while keeping content logging enabled globally.
469+
This is useful when you need to suppress sensitive data (e.g. PII, credentials) for specific requests while keeping content logging enabled globally — or, conversely, to opt a single request into full content+raw capture while content logging is globally disabled.
466470
467471
<Warning>
468472
Per-request overrides are **disabled by default**. You must first enable `allow_per_request_content_storage_override` in your logging configuration (or in the UI under **Logs Settings**) before this header or context key has any effect. When the toggle is off, the global `disable_content_logging` setting is authoritative and this value is ignored.

framework/configstore/migrations.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,12 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error {
623623
if err := migrationMakeOAuthTokenExpiryNullable(ctx, db); err != nil {
624624
return err
625625
}
626+
if err := migrationAddAllowPerRequestContentStorageOverrideColumn(ctx, db); err != nil {
627+
return err
628+
}
629+
if err := migrationAddAllowPerRequestRawOverrideColumn(ctx, db); err != nil {
630+
return err
631+
}
626632
return nil
627633
}
628634

@@ -6564,6 +6570,76 @@ func migrationMakeOAuthTokenExpiryNullable(ctx context.Context, db *gorm.DB) err
65646570
return nil
65656571
}
65666572

6573+
// migrationAddAllowPerRequestContentStorageOverrideColumn adds the allow_per_request_content_storage_override column to config_client.
6574+
func migrationAddAllowPerRequestContentStorageOverrideColumn(ctx context.Context, db *gorm.DB) error {
6575+
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
6576+
ID: "add_allow_per_request_content_storage_override_column",
6577+
Migrate: func(tx *gorm.DB) error {
6578+
tx = tx.WithContext(ctx)
6579+
migrator := tx.Migrator()
6580+
6581+
if !migrator.HasColumn(&tables.TableClientConfig{}, "allow_per_request_content_storage_override") {
6582+
if err := migrator.AddColumn(&tables.TableClientConfig{}, "AllowPerRequestContentStorageOverride"); err != nil {
6583+
return fmt.Errorf("failed to add allow_per_request_content_storage_override column: %w", err)
6584+
}
6585+
}
6586+
6587+
return nil
6588+
},
6589+
Rollback: func(tx *gorm.DB) error {
6590+
tx = tx.WithContext(ctx)
6591+
migrator := tx.Migrator()
6592+
6593+
if migrator.HasColumn(&tables.TableClientConfig{}, "allow_per_request_content_storage_override") {
6594+
if err := migrator.DropColumn(&tables.TableClientConfig{}, "allow_per_request_content_storage_override"); err != nil {
6595+
return fmt.Errorf("failed to drop allow_per_request_content_storage_override column: %w", err)
6596+
}
6597+
}
6598+
6599+
return nil
6600+
},
6601+
}})
6602+
if err := m.Migrate(); err != nil {
6603+
return fmt.Errorf("error running allow_per_request_content_storage_override migration: %s", err.Error())
6604+
}
6605+
return nil
6606+
}
6607+
6608+
// migrationAddAllowPerRequestRawOverrideColumn adds the allow_per_request_raw_override column to config_client.
6609+
func migrationAddAllowPerRequestRawOverrideColumn(ctx context.Context, db *gorm.DB) error {
6610+
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
6611+
ID: "add_allow_per_request_raw_override_column",
6612+
Migrate: func(tx *gorm.DB) error {
6613+
tx = tx.WithContext(ctx)
6614+
migrator := tx.Migrator()
6615+
6616+
if !migrator.HasColumn(&tables.TableClientConfig{}, "allow_per_request_raw_override") {
6617+
if err := migrator.AddColumn(&tables.TableClientConfig{}, "AllowPerRequestRawOverride"); err != nil {
6618+
return fmt.Errorf("failed to add allow_per_request_raw_override column: %w", err)
6619+
}
6620+
}
6621+
6622+
return nil
6623+
},
6624+
Rollback: func(tx *gorm.DB) error {
6625+
tx = tx.WithContext(ctx)
6626+
migrator := tx.Migrator()
6627+
6628+
if migrator.HasColumn(&tables.TableClientConfig{}, "allow_per_request_raw_override") {
6629+
if err := migrator.DropColumn(&tables.TableClientConfig{}, "allow_per_request_raw_override"); err != nil {
6630+
return fmt.Errorf("failed to drop allow_per_request_raw_override column: %w", err)
6631+
}
6632+
}
6633+
6634+
return nil
6635+
},
6636+
}})
6637+
if err := m.Migrate(); err != nil {
6638+
return fmt.Errorf("error running allow_per_request_raw_override migration: %s", err.Error())
6639+
}
6640+
return nil
6641+
}
6642+
65676643
// migrationAddMCPClientDiscoveredToolsColumns adds discovered_tools_json and tool_name_mapping_json columns to the mcp_client table
65686644
func migrationAddMCPClientDiscoveredToolsColumns(ctx context.Context, db *gorm.DB) error {
65696645
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{

framework/configstore/rdb.go

Lines changed: 51 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -153,38 +153,40 @@ func mcpExternalBaseURLToString(e *schemas.EnvVar) string {
153153
// UpdateClientConfig updates the client configuration in the database.
154154
func (s *RDBConfigStore) UpdateClientConfig(ctx context.Context, config *ClientConfig) error {
155155
dbConfig := tables.TableClientConfig{
156-
DropExcessRequests: config.DropExcessRequests,
157-
InitialPoolSize: config.InitialPoolSize,
158-
EnableLogging: config.EnableLogging,
159-
DisableContentLogging: config.DisableContentLogging,
160-
DisableDBPingsInHealth: config.DisableDBPingsInHealth,
161-
LogRetentionDays: config.LogRetentionDays,
162-
EnforceAuthOnInference: config.EnforceAuthOnInference,
163-
EnforceGovernanceHeader: config.EnforceGovernanceHeader,
164-
EnforceSCIMAuth: config.EnforceSCIMAuth,
165-
AllowDirectKeys: config.AllowDirectKeys,
166-
PrometheusLabels: config.PrometheusLabels,
167-
AllowedOrigins: config.AllowedOrigins,
168-
AllowedHeaders: config.AllowedHeaders,
169-
MaxRequestBodySizeMB: config.MaxRequestBodySizeMB,
170-
CompatConvertTextToChat: config.Compat.ConvertTextToChat,
171-
CompatConvertChatToResponses: config.Compat.ConvertChatToResponses,
172-
CompatShouldDropParams: config.Compat.ShouldDropParams,
173-
CompatShouldConvertParams: config.Compat.ShouldConvertParams,
174-
MCPAgentDepth: config.MCPAgentDepth,
175-
MCPToolExecutionTimeout: config.MCPToolExecutionTimeout,
176-
MCPCodeModeBindingLevel: config.MCPCodeModeBindingLevel,
177-
MCPToolSyncInterval: config.MCPToolSyncInterval,
178-
MCPDisableAutoToolInject: config.MCPDisableAutoToolInject,
179-
AsyncJobResultTTL: config.AsyncJobResultTTL,
180-
RequiredHeaders: config.RequiredHeaders,
181-
LoggingHeaders: config.LoggingHeaders,
182-
WhitelistedRoutes: config.WhitelistedRoutes,
183-
HideDeletedVirtualKeysInFilters: config.HideDeletedVirtualKeysInFilters,
184-
RoutingChainMaxDepth: config.RoutingChainMaxDepth,
185-
MCPExternalBaseURL: mcpExternalBaseURLToString(config.MCPExternalBaseURL),
186-
HeaderFilterConfig: config.HeaderFilterConfig,
187-
ConfigHash: config.ConfigHash,
156+
DropExcessRequests: config.DropExcessRequests,
157+
InitialPoolSize: config.InitialPoolSize,
158+
EnableLogging: config.EnableLogging,
159+
DisableContentLogging: config.DisableContentLogging,
160+
DisableDBPingsInHealth: config.DisableDBPingsInHealth,
161+
LogRetentionDays: config.LogRetentionDays,
162+
EnforceAuthOnInference: config.EnforceAuthOnInference,
163+
EnforceGovernanceHeader: config.EnforceGovernanceHeader,
164+
EnforceSCIMAuth: config.EnforceSCIMAuth,
165+
AllowDirectKeys: config.AllowDirectKeys,
166+
PrometheusLabels: config.PrometheusLabels,
167+
AllowedOrigins: config.AllowedOrigins,
168+
AllowedHeaders: config.AllowedHeaders,
169+
MaxRequestBodySizeMB: config.MaxRequestBodySizeMB,
170+
CompatConvertTextToChat: config.Compat.ConvertTextToChat,
171+
CompatConvertChatToResponses: config.Compat.ConvertChatToResponses,
172+
CompatShouldDropParams: config.Compat.ShouldDropParams,
173+
CompatShouldConvertParams: config.Compat.ShouldConvertParams,
174+
MCPAgentDepth: config.MCPAgentDepth,
175+
MCPToolExecutionTimeout: config.MCPToolExecutionTimeout,
176+
MCPCodeModeBindingLevel: config.MCPCodeModeBindingLevel,
177+
MCPToolSyncInterval: config.MCPToolSyncInterval,
178+
MCPDisableAutoToolInject: config.MCPDisableAutoToolInject,
179+
AsyncJobResultTTL: config.AsyncJobResultTTL,
180+
RequiredHeaders: config.RequiredHeaders,
181+
LoggingHeaders: config.LoggingHeaders,
182+
WhitelistedRoutes: config.WhitelistedRoutes,
183+
HideDeletedVirtualKeysInFilters: config.HideDeletedVirtualKeysInFilters,
184+
RoutingChainMaxDepth: config.RoutingChainMaxDepth,
185+
MCPExternalBaseURL: mcpExternalBaseURLToString(config.MCPExternalBaseURL),
186+
HeaderFilterConfig: config.HeaderFilterConfig,
187+
AllowPerRequestContentStorageOverride: config.AllowPerRequestContentStorageOverride,
188+
AllowPerRequestRawOverride: config.AllowPerRequestRawOverride,
189+
ConfigHash: config.ConfigHash,
188190
}
189191
// Delete existing client config and create new one in a transaction
190192
return s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
@@ -382,20 +384,22 @@ func (s *RDBConfigStore) GetClientConfig(ctx context.Context) (*ClientConfig, er
382384
ShouldDropParams: dbConfig.CompatShouldDropParams,
383385
ShouldConvertParams: dbConfig.CompatShouldConvertParams,
384386
},
385-
MCPAgentDepth: dbConfig.MCPAgentDepth,
386-
MCPToolExecutionTimeout: dbConfig.MCPToolExecutionTimeout,
387-
MCPCodeModeBindingLevel: dbConfig.MCPCodeModeBindingLevel,
388-
MCPToolSyncInterval: dbConfig.MCPToolSyncInterval,
389-
MCPDisableAutoToolInject: dbConfig.MCPDisableAutoToolInject,
390-
AsyncJobResultTTL: dbConfig.AsyncJobResultTTL,
391-
RequiredHeaders: dbConfig.RequiredHeaders,
392-
LoggingHeaders: dbConfig.LoggingHeaders,
393-
WhitelistedRoutes: dbConfig.WhitelistedRoutes,
394-
HideDeletedVirtualKeysInFilters: dbConfig.HideDeletedVirtualKeysInFilters,
395-
RoutingChainMaxDepth: dbConfig.RoutingChainMaxDepth,
396-
MCPExternalBaseURL: schemas.NewEnvVar(dbConfig.MCPExternalBaseURL),
397-
HeaderFilterConfig: dbConfig.HeaderFilterConfig,
398-
ConfigHash: dbConfig.ConfigHash,
387+
MCPAgentDepth: dbConfig.MCPAgentDepth,
388+
MCPToolExecutionTimeout: dbConfig.MCPToolExecutionTimeout,
389+
MCPCodeModeBindingLevel: dbConfig.MCPCodeModeBindingLevel,
390+
MCPToolSyncInterval: dbConfig.MCPToolSyncInterval,
391+
MCPDisableAutoToolInject: dbConfig.MCPDisableAutoToolInject,
392+
AsyncJobResultTTL: dbConfig.AsyncJobResultTTL,
393+
RequiredHeaders: dbConfig.RequiredHeaders,
394+
LoggingHeaders: dbConfig.LoggingHeaders,
395+
WhitelistedRoutes: dbConfig.WhitelistedRoutes,
396+
HideDeletedVirtualKeysInFilters: dbConfig.HideDeletedVirtualKeysInFilters,
397+
RoutingChainMaxDepth: dbConfig.RoutingChainMaxDepth,
398+
MCPExternalBaseURL: schemas.NewEnvVar(dbConfig.MCPExternalBaseURL),
399+
HeaderFilterConfig: dbConfig.HeaderFilterConfig,
400+
AllowPerRequestContentStorageOverride: dbConfig.AllowPerRequestContentStorageOverride,
401+
AllowPerRequestRawOverride: dbConfig.AllowPerRequestRawOverride,
402+
ConfigHash: dbConfig.ConfigHash,
399403
}, nil
400404
}
401405

@@ -1249,7 +1253,7 @@ func (s *RDBConfigStore) GetMCPConfig(ctx context.Context) (*schemas.MCPConfig,
12491253
ClientConfigs: clientConfigs,
12501254
ToolManagerConfig: &schemas.MCPToolManagerConfig{
12511255
ToolExecutionTimeout: schemas.Duration(30 * time.Second), // default from TableClientConfig
1252-
MaxAgentDepth: 10, // default from TableClientConfig
1256+
MaxAgentDepth: 10, // default from TableClientConfig
12531257
},
12541258
}, nil
12551259
}

0 commit comments

Comments
 (0)