Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/crewai-tools/src/crewai_tools/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -234,6 +237,7 @@
"ContextualAIQueryTool",
"ContextualAIRerankTool",
"CouchbaseFTSVectorSearchTool",
"CreduentVerificationTool",
"CrewaiPlatformTools",
"DOCXSearchTool",
"DallETool",
Expand Down
Original file line number Diff line number Diff line change
@@ -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://<namespace>/<name>`) in under 5ms.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Creduent Verification Tool
Original file line number Diff line number Diff line change
@@ -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://<namespace>/<name>.
"""

agent_uri: str = Field(
...,
description="Target agent URI to verify, formatted as agent://<namespace>/<name>",
)


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://<namespace>/<name>.

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"
)
Comment on lines +22 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check pyproject.toml for a creduent extra and confirm no other tool exposes package_dependencies for reference.
set -euo pipefail

fd -a pyproject.toml lib/crewai-tools | xargs -I{} sh -c 'echo "== {} =="; rg -n -A3 "creduent" {}'
rg -n "package_dependencies" lib/crewai-tools/src/crewai_tools/tools --iglob '*.py' -A2 | head -50

Repository: crewAIInc/crewAI

Length of output: 215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pyproject creduent/package dependencies =="
rg -n -C 6 'creduent|optional-dependencies|dependencies|package_dependencies' lib/crewai-tools/pyproject.toml || true

echo
echo "== creduent references in pyproject and tool code ==" | sed -n '1p'
rg -n 'creduent|package_dependencies' lib/crewai-tools \
  -g 'pyproject.toml' \
  -g '*.py' | sed -n '1,200p'

echo
echo "== BUILDING_TOOLS dependency documentation ==" 
fd -a "BUILDING_TOOLS.md" . | while read -r f; do
  echo "--- $f ---"
  rg -n -C 5 'package_dependencies|optional-dependencies|additional dependencies|dependencies' "$f" || true
done

echo
echo "== target files ==" 
fd -a 'creduent_verification_tool.py|test_creduent_verification_tool.py' . | while read -r f; do
  echo "--- $f ---"
  wc -l "$f"
done

Repository: crewAIInc/crewAI

Length of output: 10931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pyproject creduent/package dependencies =="
rg -n -C 6 'creduent|optional-dependencies|dependencies|package_dependencies' lib/crewai-tools/pyproject.toml || true

echo
echo "== creduent references in pyproject and tool code ==" | sed -n '1p'
rg -n 'creduent|package_dependencies' lib/crewai-tools \
  -g 'pyproject.toml' \
  -g '*.py' | sed -n '1,200p'

echo
echo "== BUILDING_TOOLS dependency documentation ==" 
fd -a "BUILDING_TOOLS.md" . | while read -r f; do
  echo "--- $f ---"
  rg -n -C 5 'package_dependencies|optional-dependencies|dependencies|additional dependencies' "$f" || true
done

echo
echo "== target files ==" 
fd -a 'creduent_verification_tool.py|test_creduent_verification_tool.py' . | while read -r f; do
  echo "--- $f ---"
  wc -l "$f"
done

Repository: crewAIInc/crewAI

Length of output: 10931


Wire creduent as an optional dependency.

CreduentVerificationTool imports creduent.verify.verify, but it does not set package_dependencies: list[str] = ["creduent"], and lib/crewai-tools/pyproject.toml only declares extras for scrapfly-sdk and sqlalchemy. Add a creduent extra and reference it in the tool docs so installers can opt in.

test_creduent_verification_tool.py patches creduent.verify.verify, so the test/dev environment must make creduent importable even if the installed package lacks the actual package. Add a test-only dependency/fixture boundary or avoid patching the missing upstream library 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-tools/src/crewai_tools/tools/creduent_verification_tool/creduent_verification_tool.py`
around lines 22 - 55, Wire the optional creduent dependency through
CreduentVerificationTool by declaring its package dependency, adding a
corresponding pyproject extra, and referencing that extra in the tool
documentation. Update test_creduent_verification_tool.py to provide a test-only
importable boundary or patch a local abstraction instead of requiring the
upstream creduent package to exist.


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
Comment on lines +58 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix double-wrapped error message on strict-mode failed verification.

The raise ValueError(error_msg) at Line 68 sits inside the same try block that Line 70's except Exception as err guards. When result.valid is False and self.strict is True, this raised ValueError is immediately re-caught by the outer except. The code then builds a second error_msg wrapping the first one, logs it again at error level (in addition to the warning already logged at Line 66), and raises a new ValueError chained from err.

The final message becomes "Verification failure for {agent_uri}: Verification FAILED for {agent_uri}: {result.error}" instead of the intended "Verification FAILED for {agent_uri}: {result.error}". This mislabels a normal failed-verification result as an unexpected exception, duplicates logging, and corrupts the message any caller inspects. The existing test only asserts "Invalid signature" in str(exc_info.value), which still passes despite the corrupted message, so this bug is not caught.

Separate the "call verify()" exception handling from the "handle an invalid result" logic so a deliberate raise ValueError for a failed result is not re-caught by the generic exception handler.

🐛 Proposed fix to separate exception handling from failed-result handling
-        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
+        logger.info(f"Verifying target agent identity: {agent_uri}")
+        try:
+            result = verify(agent_uri)
+        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
+
+        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
🤖 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-tools/src/crewai_tools/tools/creduent_verification_tool/creduent_verification_tool.py`
around lines 58 - 75, Separate the verify(agent_uri) call from the
invalid-result handling in the surrounding method so exceptions raised by verify
are handled by the generic error path, while the deliberate strict-mode
ValueError for result.valid == False bypasses it. Preserve the existing warning
and exact “Verification FAILED…” message for invalid results, and keep
unexpected exception logging and chaining unchanged.

50 changes: 50 additions & 0 deletions lib/crewai-tools/tests/tools/test_creduent_verification_tool.py
Original file line number Diff line number Diff line change
@@ -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)