Skip to content

feat: add GuardrailProvider interface for pre-tool-call authorization (#4877)#4878

Open
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1773511879-guardrail-provider-interface
Open

feat: add GuardrailProvider interface for pre-tool-call authorization (#4877)#4878
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1773511879-guardrail-provider-interface

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Mar 14, 2026

feat: add GuardrailProvider interface for pre-tool-call authorization

Closes #4877

Summary

Adds a GuardrailProvider protocol that standardizes pluggable tool-call authorization on top of CrewAI's existing BeforeToolCallHook system. No changes to the tool execution pipeline.

New types in crewai.hooks:

  • GuardrailRequest – dataclass with tool call context (tool_name, tool_input, agent_role, task_description, crew_id, timestamp)
  • GuardrailDecision – dataclass with allow/deny verdict, reason, and arbitrary metadata
  • GuardrailProvider@runtime_checkable protocol requiring name, evaluate(), and health_check()
  • enable_guardrail(provider, *, fail_closed=True) – adapter that registers a BeforeToolCallHook delegating to the provider; returns a disable() callable

29 new tests covering dataclass defaults, protocol conformance, context mapping, allow/deny/exception paths, fail_closed vs fail_open, multiple providers, and disable lifecycle.

Review & Testing Checklist for Human

  • Verify GuardrailDecision.reason behavior: The docstring says reason is "surfaced to the agent when blocked," but the hook returns False only — the reason is logged via logger.info but not passed back to the agent. Confirm this is acceptable or if the reason should be propagated (would require changes to BeforeToolCallHook contract).
  • Verify health_check() intent: The protocol declares health_check() but enable_guardrail never calls it. It's for provider-side use only. Confirm this is the desired design vs. checking health before registering or on each call.
  • Verify @runtime_checkable protocol behavior: Python's runtime_checkable only checks method presence, not class-level attribute name. Test test_partial_implementation_is_not_guardrail_provider passes because it lacks health_check, but an object with evaluate + health_check but no name attribute might still pass isinstance. Verify this edge case is acceptable.
  • Run the full hook test suite (pytest lib/crewai/tests/hooks/ -vv) to confirm no regressions in existing hooks.

Notes


Note

Low Risk
Low risk: adds new opt-in guardrail adapter and types without changing existing tool/LLM hook execution unless enable_guardrail() is used.

Overview
Adds an opt-in guardrail layer on top of existing before_tool_call hooks by introducing GuardrailProvider, GuardrailRequest, and GuardrailDecision, plus enable_guardrail() to register/unregister a provider-backed authorization hook (with configurable fail-closed vs fail-open behavior).

Exports the new API via crewai.hooks.__init__ and adds a comprehensive test suite covering request construction, protocol conformance, allow/deny paths, exception handling, multiple providers, and hook disable lifecycle.

Written by Cursor Bugbot for commit d5100a5. This will update automatically on new commits. Configure here.

…#4877)

- Add GuardrailRequest dataclass for tool call context
- Add GuardrailDecision dataclass for allow/deny verdicts
- Add GuardrailProvider runtime-checkable protocol
- Add enable_guardrail() adapter wiring providers into BeforeToolCallHook
- Add disable() callable returned by enable_guardrail for cleanup
- Support fail_closed (default) and fail_open exception handling
- Export new types from crewai.hooks
- Add 29 comprehensive tests covering all scenarios

Co-Authored-By: João <joao@crewai.com>
@devin-ai-integration
Copy link
Contributor Author

Prompt hidden (unlisted session)

@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

A :class:`GuardrailDecision`. If ``allow`` is ``False``, the tool
call is blocked and ``reason`` is surfaced to the agent.
"""
...
``True`` if the provider is healthy and ready, ``False`` otherwise.
The default expectation is ``True``.
"""
...
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

)
return False # block tool execution

return None # allow tool execution
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail_closed bypassed when provider returns malformed result

Medium Severity

The decision.allow access on line 274 sits outside the try/except block that handles provider errors. If evaluate() returns a non-GuardrailDecision object (e.g. None), the attribute access raises an AttributeError that escapes _hook(). The executor's outer try/except catches it, hook_blocked stays False, and the tool silently executes — effectively fail-open even when fail_closed=True. For a security/authorization feature, the fail_closed contract is broken for this class of provider errors.

Additional Locations (1)
Fix in Cursor Fix in Web

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.

[FEATURE] GuardrailProvider interface for pre-tool-call authorization

0 participants