Skip to content

fix(adapters): replace NodeInterrupt with FirewallBlocked exception - #61

Open
Pranjal0410 wants to merge 3 commits into
c2siorg:mainfrom
Pranjal0410:feat/firewall-blocked-exception
Open

fix(adapters): replace NodeInterrupt with FirewallBlocked exception#61
Pranjal0410 wants to merge 3 commits into
c2siorg:mainfrom
Pranjal0410:feat/firewall-blocked-exception

Conversation

@Pranjal0410

@Pranjal0410 Pranjal0410 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

NodeInterrupt is deprecated in LangGraph V1. Worse - it doesn't actually raise. It returns {'__interrupt__': [...]} in the result dict and the graph keeps going. So the adapter's BLOCK decisions were silently becoming pauses. The attack text stayed in state.

Found this while writing examples for the adapter after PR #51 merged.

What changed

sdk/python/acf/models.py

  • Added FirewallBlocked(FirewallError) - a proper exception that propagates out of invoke(). Callers catch it with normal try/except.

sdk/python/acf/adapters/langgraph.py

  • Replaced NodeInterrupt with FirewallBlocked on BLOCK decisions. No more silent failures.
  • Removed the langgraph.errors import entirely - adapter no longer depends on a deprecated API.

sdk/python/tests/adapters/test_langgraph.py

  • Updated to test FirewallBlocked instead of NodeInterrupt. 22/22 passing. Full suite 110/110.

Why this matters

A firewall that reports BLOCK but doesn't actually stop the request is worse than no firewall. At least without a firewall you know you're unprotected. With the old behaviour you think you're safe and you're not.

Examples

Two runnable examples in examples/adapters/:

  • langgraph_prompt_guard.py - clean prompt reaches the model, jailbreak gets stopped at the guard via FirewallBlocked
  • langgraph_context_filter.py - 3 RAG chunks retrieved, one poisoned. Poisoned chunk filtered out, model runs on the clean two.

NodeInterrupt is deprecated in LangGraph V1 and silently fails — returns a dict instead of raising. BLOCK decisions were turning into pauses. FirewallBlocked(FirewallError) actually propagates so callers can catch it. 22/22 adapter tests, full suite 110/110.
Two runnable examples showing the adapter in action: prompt_guard.py (BLOCK halts the graph via FirewallBlocked) and context_filter.py (per-chunk RAG filtering, poisoned chunk dropped, clean ones pass through).
@tharindupr

Copy link
Copy Markdown
Collaborator

Few things to be fixed:

FirewallBlocked is not exported from the top-level acf package. acf/__init__.py exports FirewallError and FirewallConnectionError, but FirewallBlocked is missing from both the imports and __all__. It should be available via from acf import FirewallBlocked but right now that would raise ImportError. The example file works around this by using from acf.models import FirewallBlocked directly, which is why it wasn't caught.

The fix is two lines in __init__.py, add FirewallBlocked to the import and to __all__.

Two minor things also worth noting:

langgraph_prompt_guard.py has duplicate imports, from acf import Firewall and from acf.adapters.langgraph import FirewallNode each appear twice (lines 20–21 and 25–26).

Added FirewallBlocked to acf/__init__.py exports. Removed duplicate imports in langgraph_prompt_guard.py. Reported by @tharindupr.
@Pranjal0410

Copy link
Copy Markdown
Contributor Author

both fixed @tharindupr. FirewallBlocked added to init.py exports and all. duplicate imports in the example cleaned up. 110/110 passing.

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