fix(proxy): Bedrock guardrail spend logs - hook mode, match redaction, streaming request_data - #25854
Conversation
…post) Bedrock ApplyGuardrail uses INPUT/OUTPUT for the API body; spend logs must use the proxy hook (pre_call, during_call, post_call). Pass logging_event_type from each hook into make_bedrock_api_request. During-call was still logged as pre_call because unified guardrails call apply_guardrail with input_type=request. BedrockGuardrail now sets use_native_during_call_hook so during_call runs async_moderation_hook instead. Includes a small test asserting the Bedrock class flag. Made-with: Cursor
…outing - Assert make_bedrock_api_request forwards logging_event_type to standard logging and legacy INPUT maps to pre_call when omitted. - Assert during_call_hook invokes Bedrock async_moderation_hook when native path is used. Made-with: Cursor
…k via patch.object Made-with: Cursor
Made-with: Cursor
- Add redact_nested_match_and_regex_keys in core_helpers for nested match/regex. - Apply in CustomGuardrail standard logging; Bedrock forwards raw JSON to avoid double redaction. - Delegate Bedrock HTTP detail assessments and _redact_pii_matches to the same helper. - Extend unit tests (core_helpers, CustomGuardrail, Bedrock spend-log mock). Made-with: Cursor
…logs Greptile: async_post_call_streaming_iterator_hook omitted request_data on OUTPUT make_bedrock_api_request calls, so standard_logging attached to a throwaway dict. - Pass request_data for parallel OUTPUT task and OUTPUT-only branch. - Add unit tests asserting OUTPUT (and parallel INPUT) receive the same request_data. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes three Bedrock guardrail logging bugs: (1) incorrect Confidence Score: 5/5Safe to merge — all three bug fixes are correctly implemented, tests are comprehensive, and no regressions were found in the guardrail logging or redaction paths. All findings are P2 or lower. The three bugs are fixed with correct logic: explicit No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/core_helpers.py | Adds redact_nested_match_and_regex_keys — a deep-copy tree-walk that replaces every match/regex key with "[REDACTED]". Two separate try/except blocks guard against deepcopy and walk failures; correctly returns the original payload on error. |
| litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py | Adds use_native_during_call_hook = True ClassVar, logging_event_type parameter to make_bedrock_api_request, simplifies _redact_pii_matches to delegate to the new utility, and adds _redact_assessment_match_fields for customer-visible HTTPException payloads. All call sites pass explicit logging_event_type matching the proxy hook phase. |
| litellm/integrations/custom_guardrail.py | Adds use_native_during_call_hook: ClassVar[bool] = False base default and wires redact_nested_match_and_regex_keys into add_standard_logging_guardrail_information_to_request_data as the single authoritative redaction point for all standard guardrail logging. |
| litellm/proxy/utils.py | Both _execute_guardrail_hook and during_call_hook now respect use_native_during_call_hook; when True the guardrail's own async_moderation_hook is invoked directly, bypassing the unified apply_guardrail path that always logged INPUT as pre_call. |
| tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py | Comprehensive new unit tests covering: logging_event_type forwarding to standard logging, use_native_during_call_hook ClassVar, during_call_hook invokes async_moderation_hook, streaming hook passes request_data to OUTPUT calls, and _get_http_exception_for_blocked_guardrail emits redacted assessment matches. |
| tests/test_litellm/integrations/test_custom_guardrail.py | New TestCustomGuardrailSpendLogMatchRedaction class verifies that add_standard_logging_guardrail_information_to_request_data redacts both match and regex keys while preserving the original payload. |
| tests/test_litellm/litellm_core_utils/test_core_helpers.py | New TestRedactNestedMatchAndRegexKeys class covers recursive redaction, passthrough for None/string inputs, and deep-copy isolation. Existing finish-reason tests are untouched. |
Reviews (2): Last reviewed commit: "Merge branch 'litellm_internal_staging' ..." | Re-trigger Greptile
| def _redact_pii_matches(response_json: dict) -> dict: | ||
| try: | ||
| # Create a deep copy to avoid modifying the original response | ||
| redacted_response = copy.deepcopy(response_json) | ||
|
|
||
| # Get assessments from the response | ||
| assessments = redacted_response.get("assessments", []) | ||
| if not assessments: | ||
| return redacted_response | ||
|
|
||
| for assessment in assessments: | ||
| # Redact PII entities in sensitive information policy | ||
| sensitive_info_policy = assessment.get("sensitiveInformationPolicy") | ||
| if sensitive_info_policy: | ||
| pii_entities = sensitive_info_policy.get("piiEntities", []) | ||
| for pii_entity in pii_entities: | ||
| if "match" in pii_entity: | ||
| pii_entity["match"] = "[REDACTED]" | ||
|
|
||
| # Redact regex matches | ||
| regexes = sensitive_info_policy.get("regexes", []) | ||
| for regex_match in regexes: | ||
| if "match" in regex_match: | ||
| regex_match["match"] = "[REDACTED]" | ||
| """ | ||
| Redact match-like fields from a Bedrock ApplyGuardrail JSON payload. | ||
|
|
||
| # Redact custom word matches in word policy | ||
| word_policy = assessment.get("wordPolicy") | ||
| if word_policy: | ||
| custom_words = word_policy.get("customWords", []) | ||
| for custom_word in custom_words: | ||
| if "match" in custom_word: | ||
| custom_word["match"] = "[REDACTED]" | ||
|
|
||
| managed_words = word_policy.get("managedWordLists", []) | ||
| for managed_word in managed_words: | ||
| if "match" in managed_word: | ||
| managed_word["match"] = "[REDACTED]" | ||
|
|
||
| return redacted_response | ||
| Delegates to :func:`redact_nested_match_and_regex_keys` (same rules as spend | ||
| logging). Kept as a Bedrock-module entry point for existing unit tests. | ||
| """ | ||
| try: | ||
| redacted = redact_nested_match_and_regex_keys(response_json) | ||
| return redacted if isinstance(redacted, dict) else response_json | ||
| except Exception as e: | ||
| # We do not want to fail in any case so this is just a warning | ||
| verbose_proxy_logger.warning("Guardrail log redaction failed: %s", str(e)) | ||
| return response_json |
There was a problem hiding this comment.
Redundant outer
try/except in _redact_pii_matches
redact_nested_match_and_regex_keys already handles all exceptions internally (two separate try/except blocks guarantee it never propagates), so the outer try/except in _redact_pii_matches and its verbose_proxy_logger.warning call can never be reached. The wrapper can be simplified:
def _redact_pii_matches(response_json: dict) -> dict:
"""
Redact match-like fields from a Bedrock ApplyGuardrail JSON payload.
Delegates to :func:`redact_nested_match_and_regex_keys` (same rules as spend
logging). Kept as a Bedrock-module entry point for existing unit tests.
"""
redacted = redact_nested_match_and_regex_keys(response_json)
return redacted if isinstance(redacted, dict) else response_json…l_spend_logging Resolve Bedrock guardrail conflicts: keep redact_nested_match_and_regex_keys in _redact_pii_matches (staging null-safety loops superseded by tree walk). Retain streaming request_data tests. Address Greptile P2: clarify malformed-response test comment; simplify _redact_pii_matches (drop redundant try/except). Made-with: Cursor
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
09ff936
into
BerriAI:litellm_yj_apr21
Restore guardrail spend/UI event_type wiring, request_data on streaming OUTPUT paths, and centralized match redaction after the upstream revert. Made-with: Cursor
Restore guardrail spend/UI event_type wiring, request_data on streaming OUTPUT paths, and centralized match redaction after the upstream revert. Made-with: Cursor
Relevant issues
Fixes:
guardrail_mode/ spend-log labels for Bedrock when proxy hooks areduring_call/post_callbut Bedrock usesINPUT/OUTPUTmatch/regexvalues in spend/compliance metadata,Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake 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 targetedproxy_unit_tests/test_proxy_utils.pyguardrail/during tests were run green.)@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewCI (LiteLLM team)
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.Match redaction: Compliance exports / metadata should not contain raw Bedrock



matchspans;"[REDACTED]"where applicable.Type
🐛 Bug Fix
✅ Test
Changes
Problem
Wrong
guardrail_modein spend logs: Bedrock ApplyGuardrailINPUT/OUTPUTwas used to infer logging hook phase, soduring_call(and somepost_call) runs could appear as PRE-CALL in spend/UI.Sensitive
match/regexin logs: Raw match-like fields could appear in standard guardrail logging (spend/compliance) and related paths.Fix
logging_event_typeonmake_bedrock_api_request— When provided, drivesevent_typeforadd_standard_logging_guardrail_information_to_request_data. If omitted, keep legacy mapping fromsource.BedrockGuardrail.use_native_during_call_hook—during_calluses nativeasync_moderation_hookso spend logs recordduring_callinstead of unifiedapply_guardrailalways looking like pre_call.ProxyLogging/_execute_guardrail_hookrespect the flag (CustomGuardraildefaultFalse).Centralized redaction —
redact_nested_match_and_regex_keysinlitellm_core_utils/core_helpers.py; applied inCustomGuardrail.add_standard_logging_guardrail_information_to_request_data. Bedrock passes raw JSON into that path (single redaction pass). Bedrock_redact_pii_matches/ HTTPdetail["assessments"]helpers delegate to the same logic.Tests
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py: hook /logging_event_type/ redaction / streamingrequest_dataassertions.tests/test_litellm/integrations/test_custom_guardrail.py: standard logging redactsmatch/regex.tests/test_litellm/litellm_core_utils/test_core_helpers.py:redact_nested_match_and_regex_keysunit tests.Files (high level)
litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.pylitellm/proxy/utils.pylitellm/integrations/custom_guardrail.pylitellm/litellm_core_utils/core_helpers.pytests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.pytests/test_litellm/integrations/test_custom_guardrail.pytests/test_litellm/litellm_core_utils/test_core_helpers.py