Skip to content

fix(actions): ip_filter validator crashes on dict responses#1950

Open
vidigoat wants to merge 2 commits into
NVIDIA-NeMo:developfrom
vidigoat:fix-ip-filter-dict-iteration
Open

fix(actions): ip_filter validator crashes on dict responses#1950
vidigoat wants to merge 2 commits into
NVIDIA-NeMo:developfrom
vidigoat:fix-ip-filter-dict-iteration

Conversation

@vidigoat

@vidigoat vidigoat commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Problem

@validate_response(validators=["ip_filter"]) crashes with ValueError: too many values to unpack (expected 2) whenever the decorated action returns a dict.

nemoguardrails/actions/validation/base.py:

elif isinstance(response_value, dict):
    for key, value in response_value:          # iterates KEYS, not items
        response_value[key] = filter_ip(value)

Iterating a dict yields its keys (strings). Unpacking each key into (key, value) raises ValueError for any key longer than two characters. This validator is applied to the LangChain safetool actions in nemoguardrails/integrations/langchain/actions/safetools.py, so any of them returning a dict response hits the crash on otherwise-valid input. The dict branch had no test coverage, so the bug shipped undetected.

Fix

elif isinstance(response_value, dict):
    for key, value in response_value.items():
        response_value[key] = filter_ip(value)

Verification

before:  pytest tests/test_actions_validation.py::test_ip_filter_on_dict_response
         ValueError: too many values to unpack (expected 2)   (base.py:102)
after :  3 passed   (full tests/test_actions_validation.py, incl. new regression test)

Added test_ip_filter_on_dict_response covering a dict response containing an IP. ruff check and ruff format --check pass on both changed files (matches the repo's pre-commit config, ruff v0.14.6).

Summary by CodeRabbit

  • Bug Fixes
    • Fixed IP address filtering to correctly identify and remove sensitive IP addresses from dictionary-structured responses, ensuring consistent protection across all response types.

@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a crash in the ip_filter response validator when the decorated action returns a dict. The original code iterated for key, value in response_value (iterating over dict keys, not key-value pairs), causing ValueError: too many values to unpack for any key longer than two characters. The fix switches to .items() and also adds an isinstance(value, str) guard so non-string dict values are silently skipped instead of triggering a TypeError in filter_ip's re.sub call.

  • nemoguardrails/actions/validation/base.py: One-line bug fix (response_valueresponse_value.items()) plus an isinstance(value, str) guard inside the ip_filter dict branch.
  • tests/test_actions_validation.py: Two new regression tests — one for a pure-string-value dict, one for a mixed-type dict containing ints, bools, and a nested dict — covering both the iteration bug and the non-string guard.

Confidence Score: 5/5

The change is a minimal, targeted bug fix with no side-effects on surrounding validators; safe to merge.

Both changed lines are straightforward corrections — switching dict iteration from keys to .items() and adding a type guard that was already called for by the previous review thread. The fix correctly mirrors how the string branch works, the two new regression tests exercise both the crash scenario and the mixed-type case, and no other code paths are touched.

No files require special attention.

Important Files Changed

Filename Overview
nemoguardrails/actions/validation/base.py Correct one-line fix switching dict iteration to .items(), with an added isinstance(value, str) guard to protect against non-string values; logic is sound and minimal.
tests/test_actions_validation.py Two well-scoped regression tests added — one for the original crash scenario and one for mixed-type dict values; both cover the fixed code paths directly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[validate_response wrapper invoked] --> B{ip_filter in validators?}
    B -- No --> G[Run other validators and return]
    B -- Yes --> C{response is str?}
    C -- Yes --> D[filter_ip on whole string]
    D --> G
    C -- No --> E{response is dict?}
    E -- No --> G
    E -- Yes --> F[Iterate response.items]
    F --> H{value is str?}
    H -- No --> F
    H -- Yes --> I[filter_ip on value]
    I --> F
    F -- exhausted --> G
Loading

Reviews (2): Last reviewed commit: "Guard non-string dict values in ip_filte..." | Re-trigger Greptile

Comment on lines 101 to 103
elif isinstance(response_value, dict):
for key, value in response_value:
for key, value in response_value.items():
response_value[key] = filter_ip(value)

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 Non-string dict values will still crash

filter_ip applies a regex substitution to its argument typed as str. If any dict value is not a string (e.g., int, None, a nested dict, or a list), the call on line 103 raises a TypeError at runtime. Since the dict branch was previously unreachable due to the bug, this gap wasn't visible before — but it is now exercisable in production when LangChain tool actions return mixed-type dicts. A isinstance(value, str) guard before calling filter_ip(value) would make the branch robust.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/actions/validation/base.py
Line: 101-103

Comment:
**Non-string dict values will still crash**

`filter_ip` applies a regex substitution to its argument typed as `str`. If any dict value is not a string (e.g., `int`, `None`, a nested `dict`, or a `list`), the call on line 103 raises a `TypeError` at runtime. Since the dict branch was previously unreachable due to the bug, this gap wasn't visible before — but it is now exercisable in production when LangChain tool actions return mixed-type dicts. A `isinstance(value, str)` guard before calling `filter_ip(value)` would make the branch robust.

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR fixes a bug in the ip_filter validator where dictionary responses were not being correctly processed. The iteration logic is corrected to use .items() for proper key/value pair traversal. A helper function and regression test verify that IP addresses are stripped from dictionary values without errors.

Changes

IP Filter Dictionary Iteration Fix

Layer / File(s) Summary
Dictionary iteration and regression test
nemoguardrails/actions/validation/base.py, tests/test_actions_validation.py
ip_filter now iterates dict responses via .items() to correctly access and update each value. Helper function get_record and regression test test_ip_filter_on_dict_response verify IPs are stripped from dict values without errors.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Results For Major Changes ✅ Passed Minor bug fix with documented test results: single-line dict iteration fix includes new regression test and confirmed passing test suite per PR description.
Title check ✅ Passed The title directly and accurately describes the main fix: resolving a crash in the ip_filter validator when handling dict responses.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nemoguardrails/actions/validation/base.py`:
- Around line 101-103: The loop that replaces dict values with
filter_ip(response_value[key]) can crash for non-string values; update the
branch that iterates response_value.items() to first check the value type: if
isinstance(value, str) call filter_ip(value), elif isinstance(value, dict)
iterate/recursively process that dict, elif isinstance(value, list/tuple)
iterate and process elements similarly, otherwise leave the value unchanged—use
the existing filter_ip for strings and recursion for nested structures so
response_value assignment only happens for safe types (reference response_value
and filter_ip to find the code).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f266b0cb-b2e0-40f8-97f4-469de933badb

📥 Commits

Reviewing files that changed from the base of the PR and between a6fc06f and fb41319.

📒 Files selected for processing (2)
  • nemoguardrails/actions/validation/base.py
  • tests/test_actions_validation.py

Comment on lines 101 to 103
elif isinstance(response_value, dict):
for key, value in response_value:
for key, value in response_value.items():
response_value[key] = filter_ip(value)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle non-string dict values before applying filter_ip.

This still crashes when a dict value is not a string (e.g., int/bool/nested dict), because re.sub expects a string-like input. Guard value types before filtering.

Suggested fix
                 elif isinstance(response_value, dict):
                     for key, value in response_value.items():
-                        response_value[key] = filter_ip(value)
+                        if isinstance(value, str):
+                            response_value[key] = filter_ip(value)
+                        else:
+                            response_value[key] = value
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif isinstance(response_value, dict):
for key, value in response_value:
for key, value in response_value.items():
response_value[key] = filter_ip(value)
elif isinstance(response_value, dict):
for key, value in response_value.items():
if isinstance(value, str):
response_value[key] = filter_ip(value)
else:
response_value[key] = value
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoguardrails/actions/validation/base.py` around lines 101 - 103, The loop
that replaces dict values with filter_ip(response_value[key]) can crash for
non-string values; update the branch that iterates response_value.items() to
first check the value type: if isinstance(value, str) call filter_ip(value),
elif isinstance(value, dict) iterate/recursively process that dict, elif
isinstance(value, list/tuple) iterate and process elements similarly, otherwise
leave the value unchanged—use the existing filter_ip for strings and recursion
for nested structures so response_value assignment only happens for safe types
(reference response_value and filter_ip to find the code).

@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@vidigoat

vidigoat commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @greptile-apps and @coderabbitai — good catch. filter_ip runs re.sub, which would raise TypeError on non-string dict values (int/bool/nested dict). Latest commit guards with isinstance(value, str) so only string values are filtered, and adds a mixed-value-dict regression test.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@vidigoat: Great work addressing that edge case! The isinstance(value, str) guard is the right approach — it makes the behavior explicit (silently skipping non-string values) and prevents re.sub from choking on integers, booleans, or nested dicts. The mixed-value-dict regression test is a nice addition to document the expected behavior clearly.

🐇 ✨

@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

The fix looks correct and well-guarded. The isinstance(value, str) check is the right call — filter_ip calls re.sub which would indeed raise TypeError on anything non-string.

Two minor observations for consideration:

  1. Nested dicts are silently skipped. If a value is itself a dict (e.g. {"result": {"ip": "192.168.1.1", "name": "host"}}), IP addresses inside it won't be filtered. Whether that matters depends on your use case, but it's worth being aware of. A recursive helper would handle it if needed.

  2. The fix mutates the caller's dict in-place. This is consistent with the original intent and probably fine here, but worth noting if callers ever reuse the dict after the action returns.

Both are edge cases — the core fix is solid. Thanks for the quick follow-up on the isinstance guard.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@vidigoat vidigoat changed the title Fix ip_filter response validator crashing on dict responses fix(actions): ip_filter validator crashes on dict responses Jun 1, 2026
vidigoat added 2 commits June 17, 2026 17:02
validate_response(validators=["ip_filter"]) crashed with
'ValueError: too many values to unpack (expected 2)' whenever the
decorated action returned a dict. The dict branch iterated
'for key, value in response_value' which iterates the dict's keys (str),
unpacking each key into (key, value) and raising for any key longer than
two characters.

This validator is applied to the LangChain safetool actions in
nemoguardrails/integrations/langchain/actions/safetools.py, so any of
them returning a dict response hit the crash on valid input. The dict
branch had no test coverage, which is why it shipped undetected.

Use .items() to iterate key/value pairs. Adds a regression test.

Signed-off-by: Vidit Patankar <vidit.patankar16@gmail.com>
Addresses review feedback (greptile P2, coderabbit): filter_ip runs
re.sub on its argument, which raises TypeError on a non-str. A dict
response with int/bool/nested-dict values would still crash after the
.items() fix. Only filter string values; leave others untouched. Adds a
mixed-value-dict regression test.

Signed-off-by: Vidit Patankar <vidit.patankar16@gmail.com>
@vidigoat vidigoat force-pushed the fix-ip-filter-dict-iteration branch from 2b7b931 to 8c86bde Compare June 17, 2026 11:46
@vidigoat

Copy link
Copy Markdown
Contributor Author

Thanks @Pouyanpi! Per the merge-guidance bot, I've signed both commits and force-pushed — they now show as verified on GitHub, and the signing requirement is satisfied. The force-push dismissed your earlier approval, but the diff is unchanged (commit-signing only, no content changes). Could you re-approve when you get a moment? 🙏

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