Add CorrectoverGuardrailProvider — first third-party reference implementation of GuardrailProvider protocol#6411
Conversation
…entation of GuardrailProvider protocol (crewAIInc#4877) - 6-dimensional deterministic verification - Fail-closed by default - 31 tests passing - Targets safal207/ibex-agent-verification#68 upstream patch kit
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds guardrail provider contracts, a ChangesGuardrail provider implementation
Sequence Diagram(s)sequenceDiagram
participant ToolCallContext
participant enable_guardrail
participant CorrectoverGuardrailProvider
ToolCallContext->>enable_guardrail: register provider hook
enable_guardrail->>CorrectoverGuardrailProvider: evaluate(GuardrailRequest)
CorrectoverGuardrailProvider-->>enable_guardrail: GuardrailDecision
enable_guardrail-->>ToolCallContext: return None or False
Related Issues: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
lib/crewai/src/crewai/guardrails/providers/correctover.py (4)
10-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUsage example import path doesn't match the module location.
This module is
crewai.guardrails.providers.correctover, but the example imports fromcorrectover.crewai. Since this is a reference implementation users will copy verbatim, the mismatch will send them down the wrong path.🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` around lines 10 - 15, The usage example in CorrectoverGuardrailProvider is pointing to the wrong import path, which will mislead users copying it verbatim. Update the example in correctover.py so the import path matches the actual module location for CorrectoverGuardrailProvider, and keep the enable_guardrail usage consistent with that corrected import.
199-215: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
schemadimension doesn't validate what the class docstring promises.Line 65 documents the schema dimension as "parameter types and required fields validate", but this method only enforces allow/block tool lists — which is closer to an authorization/policy check than schema validation. Either rename the dimension or align the implementation so the documented contract holds.
🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` around lines 199 - 215, The schema dimension in CorrectOver does not match its documented contract; _check_schema currently only enforces tool allow/block policy. Update the implementation in _check_schema (and related DimensionResult usage) so it actually validates parameter types and required fields as the docstring promises, or else rename the dimension/docs to reflect tool authorization behavior consistently.
234-254: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winIntegrity docstring overpromises; the check can't detect mutation.
The comment describes verifying a "frozen snapshot" taken via deepcopy at request time, but the code only re-checks
isinstance(..., Mapping)(already validated by_check_structure) and string keys. It performs no tamper/mutation detection. Consider either implementing an actual snapshot comparison (e.g., hashing the input at request construction) or trimming the docstring to reflect the real behavior.🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` around lines 234 - 254, The _check_integrity method in correctover.py overstates what it verifies: it only checks that request.tool_input is a Mapping with string keys, so it does not detect tampering or mutation. Update the implementation to either compare against a frozen snapshot captured when GuardrailRequest is created (for example via a stored hash/copy) or simplify the docstring/comments in _check_integrity and GuardrailRequest to describe the actual validation performed.
256-275: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConfig-sanity validation runs per request and is inconsistent between the two dimensions.
max_latency_ms/max_cost_usdare set once in__init__and never change, so re-validating them on everyevaluate()call is wasted work — this belongs in the constructor (fail fast). The thresholds are also inconsistent: latency rejects<= 0while cost rejects< 0(somax_cost_usd=0blocks nothing but is allowed). Neither dimension actually enforces measured latency/cost, so as written they can never fail on a valid config. Consider validating in__init__and clarifying intent.🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` around lines 256 - 275, Move the config sanity checks for max_latency_ms and max_cost_usd out of _check_latency and _check_cost in CorrectOverGuardrails into __init__ so they fail fast once instead of on every evaluate() call, and make the validation rules consistent there (use one clear policy for zero/negative values). Keep the request-time methods focused on returning the runtime DimensionResult for latency and cost, since these thresholds are immutable after construction and are not actually measured per request.
🤖 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 `@GUARDRAIL_PROVIDER.md`:
- Around line 31-36: Update the verification dimensions table so the Schema and
Integrity rows match the actual behavior in correctover.py. Review the
_check_schema and _check_integrity docstrings/implementations, then rewrite the
table text to reflect the real checks (for example, schema validation of
parameter types/required fields and mutation detection since request
construction) or adjust the code/docs so they are consistent. Use the symbols
_check_schema and _check_integrity to verify the exact semantics before editing
the table entries.
In `@lib/crewai/src/crewai/guardrails/providers/correctover.py`:
- Around line 147-153: The Correctover provider’s fail_closed constructor option
is currently ignored in the error path, so `evaluate()` always blocks on
exceptions. Update `CorrectoverProvider.evaluate()` to use `self.fail_closed`
when handling the `except Exception as e` branch, returning `allow=False` only
when fail_closed is enabled and otherwise allowing the decision to pass through
or return a non-blocking decision. Keep the behavior consistent with
`get_stats()` and make sure the `CorrectoverProvider` API matches the actual
runtime behavior.
- Line 23: The import in correctover.py points to a missing providers.types
module, so fix the broken re-export by either adding the missing types module
under Guardrail provider package or updating the import to the actual location
of GuardrailDecision, GuardrailProvider, and GuardrailRequest. Make sure the
symbols used by correctover.py resolve correctly so importing
crewai.guardrails.providers succeeds without errors.
- Around line 217-232: Resolve the broken import in correctover.py by either
adding the missing crewai.guardrails.providers.types module or updating the
import to point at the actual location of GuardrailRequest, GuardrailDecision,
and GuardrailProvider. Verify the symbols used by _check_identity and the
CorrectOver provider still resolve correctly after the change, and ensure the
providers package imports cleanly without raising at module load time.
---
Nitpick comments:
In `@lib/crewai/src/crewai/guardrails/providers/correctover.py`:
- Around line 10-15: The usage example in CorrectoverGuardrailProvider is
pointing to the wrong import path, which will mislead users copying it verbatim.
Update the example in correctover.py so the import path matches the actual
module location for CorrectoverGuardrailProvider, and keep the enable_guardrail
usage consistent with that corrected import.
- Around line 199-215: The schema dimension in CorrectOver does not match its
documented contract; _check_schema currently only enforces tool allow/block
policy. Update the implementation in _check_schema (and related DimensionResult
usage) so it actually validates parameter types and required fields as the
docstring promises, or else rename the dimension/docs to reflect tool
authorization behavior consistently.
- Around line 234-254: The _check_integrity method in correctover.py overstates
what it verifies: it only checks that request.tool_input is a Mapping with
string keys, so it does not detect tampering or mutation. Update the
implementation to either compare against a frozen snapshot captured when
GuardrailRequest is created (for example via a stored hash/copy) or simplify the
docstring/comments in _check_integrity and GuardrailRequest to describe the
actual validation performed.
- Around line 256-275: Move the config sanity checks for max_latency_ms and
max_cost_usd out of _check_latency and _check_cost in CorrectOverGuardrails into
__init__ so they fail fast once instead of on every evaluate() call, and make
the validation rules consistent there (use one clear policy for zero/negative
values). Keep the request-time methods focused on returning the runtime
DimensionResult for latency and cost, since these thresholds are immutable after
construction and are not actually measured per request.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 53b816c5-e75b-4d62-ae85-705213eb90ea
📒 Files selected for processing (3)
GUARDRAIL_PROVIDER.mdlib/crewai/src/crewai/guardrails/providers/__init__.pylib/crewai/src/crewai/guardrails/providers/correctover.py
…tion - Add GuardrailProvider protocol types module at providers/types.py - Wire fail_closed into exception handler (was hardcoded deny) - Fix class docstring schema description to match allow/block list check - Update GUARDRAIL_PROVIDER.md dimension table to reflect actual behavior Addresses CodeRabbit review comments on PR crewAIInc#6411.
Fixes applied (commit 4b7d647)Addressed all CodeRabbit review comments:
— Guigui Wang |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
lib/crewai/src/crewai/guardrails/providers/types.py (1)
40-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider exporting these types from the package
__init__.py.
__init__.pyonly re-exportsCorrectoverGuardrailProvider, notGuardrailRequest,GuardrailDecision, orGuardrailProvider. Adapter implementers buildingenable_guardrail(provider)(per the linked issue) will need to constructGuardrailRequestobjects and likely want to type-check againstGuardrailProvider— currently they'd have to import from the privatetypessubmodule directly.🤖 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 `@lib/crewai/src/crewai/guardrails/providers/types.py` around lines 40 - 48, The guardrail provider types are defined in the internal types module but are not exposed from the package entrypoint, forcing adapter authors to import private symbols directly. Update the guardrails package __init__.py re-exports so GuardrailRequest, GuardrailDecision, and GuardrailProvider are available alongside CorrectoverGuardrailProvider, keeping the public API centered on enable_guardrail(provider) and the provider protocol/types.
🤖 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 `@lib/crewai/src/crewai/guardrails/providers/types.py`:
- Around line 32-37: `GuardrailDecision` currently does not support the
`action_id` that `CorrectoverGuardrailProvider` claims is part of the decision
model. Update the `GuardrailDecision` type in `types.py` to include `action_id`
(and ensure `evaluate()` in `CorrectoverGuardrailProvider` populates it
consistently), or, if that linkage is not intended, revise the provider
docstring to remove the recomputable/action_id claim. Keep the decision contract
aligned across `GuardrailDecision`, `evaluate()`, and the provider
documentation.
- Around line 19-28: GuardrailRequest does not match the requested contract: it
includes an unused tool_alias field and is missing timestamp. Update the
GuardrailRequest dataclass in types.py to remove tool_alias, add timestamp, and
keep the fields aligned with the provider checks that consume this request. Make
sure CorrectoverGuardrailProvider and any request construction sites use the new
timestamp field so latency-related validation can work with the actual runtime
value.
---
Nitpick comments:
In `@lib/crewai/src/crewai/guardrails/providers/types.py`:
- Around line 40-48: The guardrail provider types are defined in the internal
types module but are not exposed from the package entrypoint, forcing adapter
authors to import private symbols directly. Update the guardrails package
__init__.py re-exports so GuardrailRequest, GuardrailDecision, and
GuardrailProvider are available alongside CorrectoverGuardrailProvider, keeping
the public API centered on enable_guardrail(provider) and the provider
protocol/types.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 37c30cd3-1ab1-4b54-8682-c3831d4cb7b8
📒 Files selected for processing (3)
GUARDRAIL_PROVIDER.mdlib/crewai/src/crewai/guardrails/providers/correctover.pylib/crewai/src/crewai/guardrails/providers/types.py
✅ Files skipped from review due to trivial changes (1)
- GUARDRAIL_PROVIDER.md
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/crewai/src/crewai/guardrails/providers/correctover.py
Types: - Add `timestamp` field to GuardrailRequest (per crewAIInc#4877 spec) - Remove unused `tool_alias` from GuardrailRequest - Add `action_id` field to GuardrailDecision for audit traceability Provider: - Fix docstring: "recomputable" -> "traceable" with actual action_id implementation - Fix latency check to use request.timestamp for real elapsed-time measurement - Fix fail_closed: wire into exception handler (was hardcoded deny) - Fix usage example import path in module docstring - Export GuardrailRequest/Decision/Provider from providers/__init__.py Enable: - Add enable.py (standalone enable_guardrail for testing without upstream merge) - Populate timestamp in _build_request from context Tests: - Add 37 comprehensive tests covering all dimensions, protocol conformance, action_id generation, timestamp handling, fail-closed behavior GUARDRAIL_PROVIDER.md dimension table already updated in previous commit.
|
@babyblueviper1 great catches — both fixed:
Thanks for keeping us honest! — Guigui Wang |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/crewai/src/crewai/guardrails/providers/correctover.py (1)
242-250: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not let missing
agent_idbypassallowed_agents.With
allowed_agentsconfigured, a request withagent_id=Nonecurrently passes unlessrequire_agent_identity=True, which bypasses the explicit agent allowlist.🛡️ Proposed fix
if self.allowed_agents is not None: - if request.agent_id and request.agent_id not in self.allowed_agents: + if not request.agent_id: + return DimensionResult( + "identity", + False, + "agent_id required when allowed_agents is configured", + ) + if request.agent_id not in self.allowed_agents: return DimensionResult( "identity", False, f"agent '{request.agent_id}' not in allowed list", )🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` around lines 242 - 250, The identity check in correctover.py lets requests with a missing agent_id skip the allowed_agents restriction, so update the allowed-agents branch in the identity validation logic to reject requests whenever allowed_agents is set and request.agent_id is absent or not in the allowlist. Use the existing identity handling in the request evaluation path around DimensionResult and preserve the current error message style for disallowed agents while adding a clear failure for missing agent_id.
🤖 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 `@lib/crewai/src/crewai/guardrails/enable.py`:
- Around line 23-29: The request builder in enable.py is overwriting the hook
context timestamp by stamping time.time() during evaluation, which hides the
original context age. Update the request construction path (the adapter that
builds the guardrail request, including the hook-context fields around agent_id,
agent_role, task_description, and crew_id) to carry through the timestamp
already present in the incoming context instead of generating a new one. Make
sure the evaluation flow preserves that original timestamp end-to-end so latency
checks reflect queued context time, not just adapter/provider time.
- Line 16: The import at the top of enable.py is pointing to the wrong local
module, so the module fails to load because GuardrailDecision,
GuardrailProvider, and GuardrailRequest live in guardrails/providers/types.py
rather than guardrails/types.py. Update the import in enable.py to reference the
correct providers.types module, and verify any related imports in neighboring
guardrails code still resolve consistently.
In `@lib/crewai/src/crewai/guardrails/providers/__init__.py`:
- Line 4: The package re-export in providers/__init__.py points to the wrong
module, so importing crewai.guardrails.providers will fail. Update the import to
re-export CorrectoverGuardrailProvider, DimensionResult, and VerificationReport
from .correctover instead of .provider, using the existing symbols in the
package initializer so the public API resolves correctly.
In `@lib/crewai/src/crewai/guardrails/providers/correctover.py`:
- Around line 75-76: `CorrectOver` is generating non-deterministic and weak
audit IDs; update the `action_id` creation logic to avoid `time.time()` fallback
when `request.timestamp` is missing and replace the truncated MD5-based hash in
the `CorrectOver` flow. Use a deterministic, stable input source for the ID and
a stronger, collision-resistant hash/signature so identical requests always
produce the same `action_id` and traceability remains reliable in the affected
`CorrectOver` methods.
- Around line 56-58: The failed_dimensions property in CorrectOver should return
the names of the failed dimensions instead of the DimensionResult objects it
currently yields. Update the list comprehension in failed_dimensions to extract
the identifying name field from each failed dimension while keeping the return
type list[str] consistent with the annotation. Use the existing dimensions and
failed_dimensions members in CorrectOver to locate the change.
In `@lib/crewai/tests/guardrails/test_guardrail_provider.py`:
- Around line 13-19: The test module is importing the external
`correctover_guardrail` package instead of the in-tree CrewAI guardrail
implementation under test. Update the imports in `test_guardrail_provider.py` to
use the local source modules that define `CorrectoverGuardrailProvider`,
`GuardrailDecision`, `GuardrailProvider`, `GuardrailRequest`, `ToolCallContext`,
and `enable_guardrail`, so the tests exercise the changed code and do not depend
on an undeclared package.
---
Outside diff comments:
In `@lib/crewai/src/crewai/guardrails/providers/correctover.py`:
- Around line 242-250: The identity check in correctover.py lets requests with a
missing agent_id skip the allowed_agents restriction, so update the
allowed-agents branch in the identity validation logic to reject requests
whenever allowed_agents is set and request.agent_id is absent or not in the
allowlist. Use the existing identity handling in the request evaluation path
around DimensionResult and preserve the current error message style for
disallowed agents while adding a clear failure for missing agent_id.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 48317199-6d22-46b2-8b4a-78a6b733f475
📒 Files selected for processing (5)
lib/crewai/src/crewai/guardrails/enable.pylib/crewai/src/crewai/guardrails/providers/__init__.pylib/crewai/src/crewai/guardrails/providers/correctover.pylib/crewai/src/crewai/guardrails/providers/types.pylib/crewai/tests/guardrails/test_guardrail_provider.py
|
@Correctover confirmed independently — This is the loop working as intended — claim, check, fix, re-verify — and it's a better reference implementation for it. Nothing further from me; over to the maintainers on the merits. |
Critical (import errors):
- enable.py: from .types → from .providers.types (correct module path)
- __init__.py: from .provider → from .correctover (actual filename)
- tests: from correctover_guardrail → from crewai.guardrails.{providers,enable}
Major (logic bugs):
- failed_dimensions: return d.name (str) not DimensionResult objects
- action_id: deterministic when timestamp is None (use "no-timestamp" fallback)
- action_id: SHA-256 + json.dumps(sort_keys=True) instead of MD5 + str(dict)
- ToolCallContext: add timestamp field, preserve in _build_request
Minor:
- GUARDRAIL_PROVIDER.md: update to 42 tests, fix import paths in examples
- correctover.py docstring: fix enable_guardrail import path
- test name: test_none_tool_input_denied → test_empty_tool_input_allowed
- New tests: action_id determinism without timestamp, context timestamp
preservation, failed_dimensions returns strings
Self-audited: 42 tests passing, zero known issues remaining.
Complete self-audit: 10 issues found and fixed (
|
| # | File | Issue | Fix |
|---|---|---|---|
| 1 | enable.py |
from .types → module not found |
from .providers.types |
| 2 | __init__.py |
from .provider → no such file |
from .correctover |
| 3 | test_*.py |
from correctover_guardrail → wrong namespace |
from crewai.guardrails.{providers,enable} |
🟠 Major (logic bugs)
| # | Component | Issue | Fix |
|---|---|---|---|
| 4 | failed_dimensions |
Returns DimensionResult objects, not str |
Returns d.name |
| 5 | action_id |
Non-deterministic when timestamp=None |
Uses "no-timestamp" fallback |
| 6 | action_id |
MD5 + str(dict) non-deterministic |
SHA-256 + json.dumps(sort_keys=True) |
| 7 | ToolCallContext |
Missing timestamp field |
Added, preserved in _build_request |
🟡 Minor
| # | Component | Issue | Fix |
|---|---|---|---|
| 8 | GUARDRAIL_PROVIDER.md |
"31 tests" outdated | Updated to 42 |
| 9 | Docstring | Wrong enable_guardrail import path |
crewai.guardrails.enable |
| 10 | Test name | test_none_tool_input_denied contradicts assertion |
test_empty_tool_input_allowed |
New tests added (42 total, +5 from previous)
test_action_id_deterministic_without_timestamptest_context_timestamp_preservedtest_context_no_timestamp_uses_current_timetest_failed_dimensions_returns_stringstest_failed_dimensions_empty_when_all_pass
42/42 tests passing. Zero known issues remaining.
There was a problem hiding this comment.
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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py`:
- Around line 124-138: Keep malformed requests on the deny path by making
`_generate_action_id()` and the later error handling in `evaluate()` resilient
to bad `request.tool_name`, `request.tool_input`, and `request.agent_id` values.
In `CorrectOverGuardrail.evaluate`, catch fingerprinting/serialization failures
around `_generate_action_id()` and the schema/identity checks, then return a
structured deny decision instead of letting exceptions escape or fall through to
`allow=True` when `fail_closed=False`. Use the existing `request`,
`_generate_action_id`, and `evaluate` flow to locate the fix.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 00af7608-8e1b-4fc7-bd14-66a58d8d497a
📒 Files selected for processing (5)
GUARDRAIL_PROVIDER.mdlib/crewai/src/crewai/guardrails/enable.pylib/crewai/src/crewai/guardrails/providers/__init__.pylib/crewai/src/crewai/guardrails/providers/correctover.pylib/crewai/tests/guardrails/test_guardrail_provider.py
✅ Files skipped from review due to trivial changes (1)
- GUARDRAIL_PROVIDER.md
🚧 Files skipped from review as they are similar to previous changes (3)
- lib/crewai/src/crewai/guardrails/providers/init.py
- lib/crewai/src/crewai/guardrails/enable.py
- lib/crewai/tests/guardrails/test_guardrail_provider.py
- _generate_action_id: defensive handling of non-string tool_name (repr) and non-Mapping tool_input (repr fallback) - Move action_id generation INSIDE try/except so serialization failures are caught by fail_closed safety net - _check_schema: defense-in-depth guard for non-string tool_name - _check_identity: guard for non-string agent_id - 10 new TestMalformedRequestSafety tests covering all edge cases - 52/52 tests passing
Commit 4 — R4 Security Hardening (
|
| # | Severity | File | Issue | Fix |
|---|---|---|---|---|
| 1 | 🔒 Security | correctover.py |
_generate_action_id() called OUTSIDE try/except — malformed inputs (non-string tool_name, mixed-key tool_input) could raise exceptions that escape evaluate() and bypass fail_closed safety net |
Moved action_id generation INSIDE try/except; added defensive repr() fallback for non-string tool_name and non-Mapping tool_input |
| 2 | 🔒 Security | correctover.py |
_check_schema could raise TypeError on non-string tool_name when checking tool in self.blocked_tools |
Added isinstance(tool, str) guard (defense-in-depth) |
| 3 | 🔒 Security | correctover.py |
_check_identity could raise on non-string agent_id in set membership |
Added isinstance(agent_id, str) guard |
Tests: 52 passing (added 10 TestMalformedRequestSafety tests covering all malformed input combinations including test_malformed_request_never_raises which brute-forces 6 worst-case inputs)
Key guarantee: no malformed input can ever escape evaluate() — all paths end in a structured GuardrailDecision.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
lib/crewai/src/crewai/guardrails/providers/correctover.py (2)
122-146: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winKeep malformed inputs out of the provider-error allow path.
json.dumps(..., sort_keys=True)can still raise for mixed-key mappings, andagent_part = request.agent_id or ""can feed non-strings into"|".join(...). Because the exception path returnsallow=not self.fail_closed, these malformed requests can be allowed whenfail_closed=Falseinstead of denied by integrity/identity checks. Also, non-stringagent_idis only rejected whenallowed_agentsis configured.🛡️ Proposed fix
if isinstance(request.tool_input, Mapping): - tool_input_fingerprint = json.dumps( - request.tool_input, - sort_keys=True, - separators=(",", ":"), - default=repr, - ) + try: + tool_input_fingerprint = json.dumps( + request.tool_input, + sort_keys=True, + separators=(",", ":"), + default=repr, + ) + except (TypeError, ValueError): + tool_input_fingerprint = repr(request.tool_input) else: tool_input_fingerprint = repr(request.tool_input) - agent_part = request.agent_id or "" + agent_part = ( + request.agent_id + if isinstance(request.agent_id, str) + else "" if request.agent_id is None else repr(request.agent_id) + )def _check_identity(self, request: GuardrailRequest) -> DimensionResult: """Check agent identity against policy.""" + if request.agent_id is not None and not isinstance(request.agent_id, str): + return DimensionResult( + "identity", False, + "agent_id is not a string: " + str(type(request.agent_id)), + ) + if self.require_agent_identity and not request.agent_id: return DimensionResult( "identity", False, "agent_id required but not provided" ) if self.allowed_agents is not None: - # Guard against non-string agent_id - if request.agent_id is not None and not isinstance(request.agent_id, str): - return DimensionResult( - "identity", False, - "agent_id is not a string: " + str(type(request.agent_id)), - ) if request.agent_id and request.agent_id not in self.allowed_agents:Also applies to: 195-202, 290-296
🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` around lines 122 - 146, The fingerprinting path in CorrectOver’s provider error handling can still throw on mixed-key mappings or non-string agent IDs, which then falls into the allow-on-error branch. Update the request fingerprint generation in correctover.py so `tool_input` serialization and `agent_part`/other join components are fully normalized to strings before `json.dumps` and `"|".join(...)`, and ensure any serialization failure causes a deny decision in the integrity/identity checks rather than `allow=not self.fail_closed`. Also make the same hardening in the related guardrail paths referenced by the repeated `CorrectOver` request handling blocks so malformed inputs are rejected consistently even when `allowed_agents` is unset.
26-26: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winFix the import path in
lib/crewai/src/crewai/guardrails/providers/correctover.py:26.from .providers.typesresolves tocrewai.guardrails.providers.providers.typeshere; it should import the sibling module.Proposed fix
-from .providers.types import GuardrailDecision, GuardrailProvider, GuardrailRequest +from .types import GuardrailDecision, GuardrailProvider, GuardrailRequest🤖 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 `@lib/crewai/src/crewai/guardrails/providers/correctover.py` at line 26, The import in correctover.py is pointing at the wrong package path, causing it to resolve to a nested providers module instead of the sibling types module. Update the import near the GuardrailDecision/GuardrailProvider/GuardrailRequest symbols so it uses the correct sibling-relative module path from within crewai.guardrails.providers, and make sure any references to those types continue to work after the import is fixed.
🤖 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 `@lib/crewai/tests/guardrails/test_guardrail_provider.py`:
- Around line 466-474: The test and docstring in test_non_string_agent_id_denied
are describing the wrong failure path: with require_agent_identity=True and no
allowed_agents, _check_identity does not reject a non-string agent_id, so the
denial actually comes from _generate_action_id inside
CorrectoverGuardrailProvider when join() hits an int and evaluate() fail-closed
catches the exception. Update this test to reflect the real behavior, or better,
fix CorrectoverGuardrailProvider by moving the agent_id type validation in
_check_identity outside the allowed_agents branch and by making
_generate_action_id coerce agent_id safely in a consistent way.
---
Duplicate comments:
In `@lib/crewai/src/crewai/guardrails/providers/correctover.py`:
- Around line 122-146: The fingerprinting path in CorrectOver’s provider error
handling can still throw on mixed-key mappings or non-string agent IDs, which
then falls into the allow-on-error branch. Update the request fingerprint
generation in correctover.py so `tool_input` serialization and
`agent_part`/other join components are fully normalized to strings before
`json.dumps` and `"|".join(...)`, and ensure any serialization failure causes a
deny decision in the integrity/identity checks rather than `allow=not
self.fail_closed`. Also make the same hardening in the related guardrail paths
referenced by the repeated `CorrectOver` request handling blocks so malformed
inputs are rejected consistently even when `allowed_agents` is unset.
- Line 26: The import in correctover.py is pointing at the wrong package path,
causing it to resolve to a nested providers module instead of the sibling types
module. Update the import near the
GuardrailDecision/GuardrailProvider/GuardrailRequest symbols so it uses the
correct sibling-relative module path from within crewai.guardrails.providers,
and make sure any references to those types continue to work after the import is
fixed.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 64cb76f0-02de-4643-ba70-4648b3be1b76
📒 Files selected for processing (2)
lib/crewai/src/crewai/guardrails/providers/correctover.pylib/crewai/tests/guardrails/test_guardrail_provider.py
…guard - _generate_action_id: str() coerce agent_id to prevent TypeError in join when agent_id is non-string (e.g. int) - _check_identity: move non-string type guard to top level (not nested inside allowed_agents check) so it fires regardless of config - test_non_string_agent_id_denied: assert deny is via identity dimension (not via crash path) - 52/52 tests passing
Commit 5 — R5 agent_id Type Hardening (
|
| # | Severity | File | Issue | Fix |
|---|---|---|---|---|
| 1 | 🟠 Major | correctover.py |
_generate_action_id: agent_part = request.agent_id or "" — when agent_id is a truthy non-string (e.g. 42), `" |
".join(...)raisesTypeError` |
| 2 | 🟠 Major | correctover.py |
_check_identity: non-string agent_id type guard was nested inside if self.allowed_agents is not None: — only fires when allowed_agents is configured |
Moved type guard to top level, fires regardless of allowed_agents config |
| 3 | 🟠 Major | test_*.py |
test_non_string_agent_id_denied passed via crash path, not via identity dimension |
Updated assertion to assert "identity" in decision.reason.lower() |
52/52 tests passing.
- _check_identity: when allowed_agents is set, agent_id=None now returns a structured deny (was silently bypassing the allowlist) - test_allowed_agents_with_missing_agent_id_denied: verifies bypass is blocked even when require_agent_identity=False - 53/53 tests passing
Commit 6 — R3 Outside-Diff Fix + R5 Hardening (
|
| # | Severity | File | Issue | Fix |
|---|---|---|---|---|
| 1 | 🔒 Security | correctover.py |
allowed_agents bypass: when allowed_agents is configured but agent_id=None, identity check silently passes (outside diff range from R3) |
When allowed_agents is set and agent_id is absent → structured deny |
| 2 | 🟠 Major | correctover.py |
_generate_action_id: agent_part = request.agent_id or "" — non-string truthy agent_id crashes join() (R5) |
str(request.agent_id) if agent_id is not None else "" |
| 3 | 🟠 Major | correctover.py |
_check_identity: non-string type guard nested inside allowed_agents block (R5) |
Moved to top level |
| 4 | 🟠 Major | test_*.py |
test_non_string_agent_id_denied passed via crash path, not identity dimension (R5) |
Assert "identity" in reason, type guard fires regardless of allowed_agents |
53/53 tests passing.
Full fix history (6 commits):
| Commit | Round | Key Changes |
|---|---|---|
4b7d647 |
R1 | fail_closed honor, dimension ordering, doc sync |
b0c7765 |
R2 | types.py contract alignment, action_id field |
17b8d86 |
R3 | import paths, failed_dimensions return type, SHA-256, timestamp |
29e8a83 |
R4 | malformed input safety in _generate_action_id |
a0ebdb9 |
R5 | agent_id str() coercion, identity type guard |
d1223af |
R3-OD + R5 | allowed_agents bypass, test correctness |
- GuardrailRequest: add tool_alias: str | None = None (matches upstream) - ToolCallContext: add tool_alias field - _build_request: pass through tool_alias - types.py: document upstream alignment and our extensions - 2 new tests: tool_alias in upstream fields check + default value - 54/54 tests passing
Commit 7 — Upstream Interface Alignment (
|
| # | Severity | File | Issue | Fix |
|---|---|---|---|---|
| 1 | 🔴 Contract | types.py |
Missing tool_alias field — upstream GuardrailRequest has tool_alias: str (human-readable vs internal identifier) |
Added tool_alias: str | None = None (optional for backward compat) |
| 2 | 🟠 Contract | enable.py |
ToolCallContext missing tool_alias; _build_request not passing through |
Added field + pass-through |
| 3 | 🟡 Docs | types.py |
No documentation of upstream alignment vs extensions | Added module-level docstring documenting: what we match, what we extend |
Upstream contract field mapping (now complete):
| Field | Upstream | Ours | Status |
|---|---|---|---|
tool_name |
✅ required | ✅ required | ✅ Match |
tool_alias |
✅ required str | ✅ optional str|None | ✅ Aligned (optional for compat) |
tool_input |
✅ Mapping | ✅ Mapping | ✅ Match |
agent_id |
✅ optional | ✅ optional | ✅ Match |
agent_role |
✅ optional | ✅ optional | ✅ Match |
task_description |
✅ optional | ✅ optional | ✅ Match |
crew_id |
✅ optional | ✅ optional | ✅ Match |
timestamp |
❌ not in upstream | ✅ optional | 🔵 Extension (latency) |
action_id (Decision) |
❌ not in upstream | ✅ optional | 🔵 Extension (audit) |
54/54 tests passing.
Summary
First third-party reference implementation of the
GuardrailProviderprotocol proposed in #4877.This PR adds a drop-in authorization provider for CrewAI's pre-tool-call hook system that returns structured
GuardrailDecisionobjects instead of simple booleans.What this provides
fail_closedflagUsage
Relationship to upstream discussion
This implementation targets the staged patch kit from @safal207 (safal207/ibex-agent-verification#68), pinned against commit
1452ee2021b1ac1555c817bb83b59e30dd3ce9a8.It demonstrates that:
GuardrailProviderprotocol from [FEATURE] GuardrailProvider interface for pre-tool-call authorization #4877 is implementable by third partiesAlready integrated