Skip to content

fix(proxy): Bedrock guardrail spend logs - hook mode, match redaction, streaming request_data - #26266

Merged
yuneng-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_bedrock_guardrail_spend_logging_reapply
Apr 22, 2026
Merged

fix(proxy): Bedrock guardrail spend logs - hook mode, match redaction, streaming request_data#26266
yuneng-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_bedrock_guardrail_spend_logging_reapply

Conversation

@milan-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes:

  • LIT-2407: incorrect guardrail_mode / spend-log labels for Bedrock when proxy hooks are during_call / post_call but Bedrock uses INPUT/OUTPUT
  • LIT-2405: sensitive match / regex values in spend/compliance metadata,

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit (CI will confirm; locally: test_bedrock_guardrails.py, test_custom_guardrail.py, test_core_helpers.py, test_litellm/proxy/test_proxy_utils.py, and targeted proxy_unit_tests/test_proxy_utils.py guardrail/during tests were run green.)
  • My PR's scope is as isolated as possible, it only solves 1 specific problem (bundles related proxy logging fixes; can split if maintainers prefer.)
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

Hook mode: Spend / evaluation UI should show PRE-CALL / DURING-CALL / POST-CALL aligned with proxy hooks, not inferred only from Bedrock INPUT/OUTPUT.
image

Match redaction: Compliance exports / metadata should not contain raw Bedrock match spans; "[REDACTED]" where applicable.
image
image
image

Type

🐛 Bug Fix
✅ Test

Changes

Problem

  1. Wrong guardrail_mode in spend logs: Bedrock ApplyGuardrail INPUT/OUTPUT was used to infer logging hook phase, so during_call (and some post_call) runs could appear as PRE-CALL in spend/UI.

  2. Sensitive match / regex in logs: Raw match-like fields could appear in standard guardrail logging (spend/compliance) and related paths.

Fix

  1. logging_event_type on make_bedrock_api_request — When provided, drives event_type for add_standard_logging_guardrail_information_to_request_data. If omitted, keep legacy mapping from source.

  2. BedrockGuardrail.use_native_during_call_hookduring_call uses native async_moderation_hook so spend logs record during_call instead of unified apply_guardrail always looking like pre_call. ProxyLogging / _execute_guardrail_hook respect the flag (CustomGuardrail default False).

  3. Centralized redactionredact_nested_match_and_regex_keys in litellm_core_utils/core_helpers.py; applied in CustomGuardrail.add_standard_logging_guardrail_information_to_request_data. Bedrock passes raw JSON into that path (single redaction pass). Bedrock _redact_pii_matches / HTTP detail["assessments"] helpers delegate to the same logic.

Tests

  • tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py: hook / logging_event_type / redaction / streaming request_data assertions.
  • tests/test_litellm/integrations/test_custom_guardrail.py: standard logging redacts match / regex.
  • tests/test_litellm/litellm_core_utils/test_core_helpers.py: redact_nested_match_and_regex_keys unit tests.

Files (high level)

  • litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py
  • litellm/proxy/utils.py
  • litellm/integrations/custom_guardrail.py
  • litellm/litellm_core_utils/core_helpers.py
  • tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py
  • tests/test_litellm/integrations/test_custom_guardrail.py
  • tests/test_litellm/litellm_core_utils/test_core_helpers.py

Restore guardrail spend/UI event_type wiring, request_data on streaming
OUTPUT paths, and centralized match redaction after the upstream revert.

Made-with: Cursor
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:02 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:02 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:02 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:02 — with GitHub Actions Inactive
@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two proxy-level guardrail logging bugs for Bedrock: (1) during_call and post_call hooks were always recorded as PRE-CALL in spend logs because Bedrock's INPUT/OUTPUT API parameter was used to infer the hook phase instead of the actual proxy event type; (2) raw matched spans (match/regex fields) from Bedrock's sensitive-information policy were persisted into spend/compliance logs.

The fix introduces logging_event_type on make_bedrock_api_request, a use_native_during_call_hook class flag on BedrockGuardrail to bypass the unified apply_guardrail path for during_call, and centralized redaction via redact_nested_match_and_regex_keys applied once in CustomGuardrail.add_standard_logging_guardrail_information_to_request_data. A deduplication guard is also added to add_guardrail_to_applied_guardrails_header.

Confidence Score: 5/5

Safe to merge — all remaining findings are minor P2 suggestions with no correctness impact on the primary fix paths.

The three core problems (wrong guardrail_mode in spend logs, raw match/regex in compliance exports, missing request_data in streaming OUTPUT calls) are all addressed with focused, well-tested changes. The use_native_during_call_hook flag and logging_event_type parameter are backward-compatible additions. Test coverage for new behaviour is thorough and mock-only. The only open P2 concern is that the traversal-failure fallback in redact_nested_match_and_regex_keys returns the unredacted original instead of the already-deep-copied partial result — an extremely unlikely edge case that does not affect normal operation.

No files require special attention; litellm/litellm_core_utils/core_helpers.py has the minor fallback concern noted above.

Important Files Changed

Filename Overview
litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py Core fix: adds logging_event_type param to make_bedrock_api_request, sets use_native_during_call_hook=True, and refactors _redact_pii_matches to delegate to the shared utility. All call sites correctly thread logging_event_type through.
litellm/integrations/custom_guardrail.py Adds use_native_during_call_hook: ClassVar[bool] = False and applies redact_nested_match_and_regex_keys on the already-deep-copied response in add_standard_logging_guardrail_information_to_request_data. Clean implementation.
litellm/litellm_core_utils/core_helpers.py New redact_nested_match_and_regex_keys function with deep-copy + iterative walk. Exception fallback returns original unredacted payload — acceptable parity with old behaviour but worth knowing.
litellm/proxy/utils.py Both _execute_guardrail_hook and during_call_hook correctly skip the unified apply_guardrail path when use_native_during_call_hook=True and hook_type=="during_call", routing to async_moderation_hook directly.
litellm/proxy/common_utils/callback_utils.py Adds deduplication guard in add_guardrail_to_applied_guardrails_header to prevent the same guardrail name appearing twice in the response header.
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py New mock-only tests covering: use_native_during_call_hook flag, logging_event_type forwarding, during_call_hook routing, streaming request_data propagation, and assessment redaction in HTTP exceptions. No real network calls.
tests/test_litellm/integrations/test_custom_guardrail.py Adds TestCustomGuardrailSpendLogMatchRedaction covering match and regex redaction via the shared logging path, and verifies the original dict is not mutated (deep-copy check).
tests/test_litellm/litellm_core_utils/test_core_helpers.py Unit tests for redact_nested_match_and_regex_keys covering recursive dicts/lists, None/string passthrough. Straightforward and correct.
tests/guardrails_tests/test_bedrock_guardrails.py Only adds logging_event_type to the mock signature so tests compile with the new parameter; assertions unchanged.

Sequence Diagram

sequenceDiagram
    participant PL as ProxyLogging
    participant BG as BedrockGuardrail
    participant AB as AWS Bedrock API
    participant CG as CustomGuardrail
    participant CH as core_helpers

    note over PL,BG: during_call (use_native_during_call_hook=True — skip unified apply_guardrail)
    PL->>BG: async_moderation_hook(data, call_type)
    BG->>AB: POST ApplyGuardrail (source=INPUT)
    AB-->>BG: response_json with raw match values
    BG->>CG: add_standard_logging_guardrail_information_to_request_data(event_type=during_call)
    CG->>CH: redact_nested_match_and_regex_keys(raw)
    CH-->>CG: redacted copy
    CG->>CG: StandardLoggingGuardrailInformation(guardrail_mode=during_call)
    CG-->>PL: spend log shows DURING-CALL

    note over PL,BG: pre_call (unified apply_guardrail path)
    PL->>BG: apply_guardrail(input_type=request)
    BG->>AB: POST ApplyGuardrail (source=INPUT)
    AB-->>BG: response_json
    BG->>CG: add_standard_logging_guardrail_information_to_request_data(event_type=pre_call)
    CG->>CH: redact_nested_match_and_regex_keys(raw)
    CH-->>CG: redacted copy
    CG-->>PL: spend log shows PRE-CALL
Loading

Reviews (3): Last reviewed commit: "fix(core_helpers): make redact_nested_ma..." | Re-trigger Greptile

Comment on lines +461 to +462
if "regex" in node:
node["regex"] = "[REDACTED]"

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.

P2 Blanket "regex" redaction may over-redact unrelated keys

_walk replaces every key literally named "regex" anywhere in the nested payload with "[REDACTED]". In the Bedrock context this is fine (the only regex key is the pattern text from sensitiveInformationPolicy.regexes[].regex). However, redact_nested_match_and_regex_keys is now called from the shared CustomGuardrail.add_standard_logging_guardrail_information_to_request_data path for all guardrail providers. If another provider's response contains a "regex" key that is not sensitive (e.g. a debug field or an OpenAI-style filter criterion), it will be silently replaced with "[REDACTED]", making logs harder to interpret.

- Dedupe names in add_guardrail_to_applied_guardrails_header (matches policies).
- Inline unified during_call condition so mypy narrows UserAPIKeyAuth.
- Extend bedrock guardrails test mock for logging_event_type.

Made-with: Cursor
@milan-berri
milan-berri force-pushed the litellm_bedrock_guardrail_spend_logging_reapply branch from 91485a0 to 9577d87 Compare April 22, 2026 20:25
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:25 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:25 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:25 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:25 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 20:25 — with GitHub Actions Inactive
Replace recursive `_walk` helper with a stack-based traversal so the
recursive_detector CI check passes without adding to the ignore list,
and avoid Python recursion limits on deeply nested payloads.

Made-with: Cursor
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 21:12 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 21:12 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 21:12 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 21:12 — with GitHub Actions Inactive
@milan-berri
milan-berri temporarily deployed to integration-postgres April 22, 2026 21:12 — with GitHub Actions Inactive
@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/litellm_core_utils/core_helpers.py 92.85% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@yuneng-berri
yuneng-berri merged commit 41145e2 into litellm_internal_staging Apr 22, 2026
101 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_bedrock_guardrail_spend_logging_reapply branch April 22, 2026 21:32
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…il_spend_logging_reapply

fix(proxy): Bedrock guardrail spend logs - hook mode, match redaction, streaming request_data
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.

2 participants