diff --git a/lib/crewai-tools/src/crewai_tools/tools/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/__init__.py index 6337c88ec3..352052bddf 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/tools/__init__.py @@ -40,6 +40,9 @@ from crewai_tools.tools.couchbase_tool.couchbase_tool import ( CouchbaseFTSVectorSearchTool, ) +from crewai_tools.tools.creduent_verification_tool.creduent_verification_tool import ( + CreduentVerificationTool, +) from crewai_tools.tools.crewai_platform_tools.crewai_platform_tools import ( CrewaiPlatformTools, ) @@ -234,6 +237,7 @@ "ContextualAIQueryTool", "ContextualAIRerankTool", "CouchbaseFTSVectorSearchTool", + "CreduentVerificationTool", "CrewaiPlatformTools", "DOCXSearchTool", "DallETool", diff --git a/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/README.md b/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/README.md new file mode 100644 index 0000000000..98e9e4e227 --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/README.md @@ -0,0 +1,23 @@ +# Creduent Verification Tool + +Verify external agent identities and cryptographic attestations locally before delegating tasks in CrewAI workflows. + +## Installation + +```bash +pip install creduent crewai-tools +``` + +## Usage + +```python +from crewai_tools import CreduentVerificationTool + +tool = CreduentVerificationTool() +result = tool.run(agent_uri="agent://assistant.dev/agent") +print(result) +``` + +## Protocol Specifications + +Creduent performs local Ed25519 signature checks and canonical JSON (JCS RFC 8785) verification on agent URIs (`agent:///`) in under 5ms. diff --git a/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/__init__.py new file mode 100644 index 0000000000..57d89aba75 --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/__init__.py @@ -0,0 +1 @@ +# Creduent Verification Tool diff --git a/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/creduent_verification_tool.py b/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/creduent_verification_tool.py new file mode 100644 index 0000000000..fcd91010ee --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/creduent_verification_tool/creduent_verification_tool.py @@ -0,0 +1,75 @@ +import logging + +from crewai.tools import BaseTool +from pydantic import BaseModel, Field + +logger = logging.getLogger(__name__) + + +class CreduentVerificationSchema(BaseModel): + """Input schema for Creduent verification tool. + + Attributes: + agent_uri: Target agent URI to verify, formatted as agent:///. + """ + + agent_uri: str = Field( + ..., + description="Target agent URI to verify, formatted as agent:///", + ) + + +class CreduentVerificationTool(BaseTool): + """Tool for verifying agent identity using the Creduent protocol. + + Performs local cryptographic signature verification (Ed25519) and canonical JCS RFC 8785 + attestation checks on target agent URIs. + """ + + name: str = "Creduent Agent Identity Verification" + description: str = ( + "Verifies the cryptographic identity, signature, and attestations of a target AI agent " + "using the Creduent open protocol before delegating tasks." + ) + args_schema: type[BaseModel] = CreduentVerificationSchema + strict: bool = True + + def _run(self, agent_uri: str) -> str: + """Execute verification of the target agent URI. + + Args: + agent_uri: Target agent URI formatted as agent:///. + + Returns: + String containing verification status message or error details. + + Raises: + ValueError: If strict mode is enabled and verification fails. + """ + try: + from creduent.verify import verify + except ImportError: + return ( + "Error: creduent package is not installed. " + "Install it using: pip install creduent" + ) + + logger.info(f"Verifying target agent identity: {agent_uri}") + try: + result = verify(agent_uri) + if result.valid: + return ( + f"Verification SUCCESS for {agent_uri}. " + "Agent identity and cryptographic attestations are trusted." + ) + error_msg = f"Verification FAILED for {agent_uri}: {result.error}" + logger.warning(error_msg) + if self.strict: + raise ValueError(error_msg) + return error_msg + except Exception as err: + error_msg = f"Verification failure for {agent_uri}: {str(err)}" + logger.error(error_msg) + if self.strict: + raise ValueError(error_msg) from err + return error_msg diff --git a/lib/crewai-tools/tests/tools/test_creduent_verification_tool.py b/lib/crewai-tools/tests/tools/test_creduent_verification_tool.py new file mode 100644 index 0000000000..e0c67a331d --- /dev/null +++ b/lib/crewai-tools/tests/tools/test_creduent_verification_tool.py @@ -0,0 +1,50 @@ +from unittest.mock import MagicMock, patch + +import pytest +from crewai_tools.tools.creduent_verification_tool.creduent_verification_tool import ( + CreduentVerificationSchema, + CreduentVerificationTool, +) + + +def test_schema_validation() -> None: + """Test schema validation for agent URI parameter.""" + schema = CreduentVerificationSchema(agent_uri="agent://assistant.dev/planner") + assert schema.agent_uri == "agent://assistant.dev/planner" + + +def test_verification_tool_initialization() -> None: + """Test tool attributes and default strict configuration.""" + tool = CreduentVerificationTool() + assert tool.name == "Creduent Agent Identity Verification" + assert tool.strict is True + + +@patch("creduent.verify.verify") +def test_successful_verification(mock_verify: MagicMock) -> None: + """Test successful verification flow when protocol returns valid result.""" + mock_result = MagicMock() + mock_result.valid = True + mock_verify.return_value = mock_result + + tool = CreduentVerificationTool(strict=False) + output = tool._run(agent_uri="agent://assistant.dev/planner") + + assert "Verification SUCCESS" in output + mock_verify.assert_called_once_with("agent://assistant.dev/planner") + + +@patch("creduent.verify.verify") +def test_failed_verification_strict(mock_verify: MagicMock) -> None: + """Test verification failure when strict mode is active.""" + mock_result = MagicMock() + mock_result.valid = False + mock_result.error = "Invalid signature" + mock_verify.return_value = mock_result + + tool = CreduentVerificationTool(strict=True) + + with pytest.raises(ValueError) as exc_info: + tool._run(agent_uri="agent://untrusted.dev/hacker") + + assert "Invalid signature" in str(exc_info.value)