Remove redundant null check in Crew.fingerprint property (#2342)

* Remove redundant null check in Crew.fingerprint property and add security module

Co-Authored-By: Joe Moura <joao@crewai.com>

* Enhance security module with type hints, improved UUID namespace, metadata validation, and versioning

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2025-03-13 21:28:28 -03:00
committed by GitHub
parent bf1024550f
commit d66fa5fe42
4 changed files with 59 additions and 5 deletions

View File

@@ -485,10 +485,20 @@ class Crew(BaseModel):
@property
def key(self) -> str:
source = [agent.key for agent in self.agents] + [
source: List[str] = [agent.key for agent in self.agents] + [
task.key for task in self.tasks
]
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
@property
def fingerprint(self) -> Fingerprint:
"""
Get the crew's fingerprint.
Returns:
Fingerprint: The crew's fingerprint
"""
return self.security_config.fingerprint
@property
def fingerprint(self) -> Fingerprint:

View File

@@ -10,4 +10,4 @@ This module provides security-related functionality for CrewAI, including:
from crewai.security.fingerprint import Fingerprint
from crewai.security.security_config import SecurityConfig
__all__ = ["Fingerprint", "SecurityConfig"]
__all__ = ["Fingerprint", "SecurityConfig"]

View File

@@ -35,7 +35,7 @@ class Fingerprint(BaseModel):
@field_validator('metadata')
@classmethod
def validate_metadata(cls, v):
"""Validate that metadata is a dictionary with string keys."""
"""Validate that metadata is a dictionary with string keys and valid values."""
if not isinstance(v, dict):
raise ValueError("Metadata must be a dictionary")
@@ -43,6 +43,20 @@ class Fingerprint(BaseModel):
for key, value in v.items():
if not isinstance(key, str):
raise ValueError(f"Metadata keys must be strings, got {type(key)}")
# Validate nested dictionaries (prevent deeply nested structures)
if isinstance(value, dict):
# Check for nested dictionaries (limit depth to 1)
for nested_key, nested_value in value.items():
if not isinstance(nested_key, str):
raise ValueError(f"Nested metadata keys must be strings, got {type(nested_key)}")
if isinstance(nested_value, dict):
raise ValueError("Metadata can only be nested one level deep")
# Check for maximum metadata size (prevent DoS)
if len(str(v)) > 10000: # Limit metadata size to 10KB
raise ValueError("Metadata size exceeds maximum allowed (10KB)")
return v
def __init__(self, **data):
@@ -80,7 +94,9 @@ class Fingerprint(BaseModel):
# Create a deterministic UUID using v5 (SHA-1)
# Custom namespace for CrewAI to enhance security
CREW_AI_NAMESPACE = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
# Using a unique namespace specific to CrewAI to reduce collision risks
CREW_AI_NAMESPACE = uuid.UUID('f47ac10b-58cc-4372-a567-0e02b2c3d479')
return str(uuid.uuid5(CREW_AI_NAMESPACE, seed))
@classmethod

View File

@@ -28,6 +28,7 @@ class SecurityConfig(BaseModel):
- Impersonation/delegation tokens *TODO*
Attributes:
version (str): Version of the security configuration
fingerprint (Fingerprint): The unique fingerprint automatically generated for the component
"""
@@ -35,11 +36,38 @@ class SecurityConfig(BaseModel):
arbitrary_types_allowed=True
# Note: Cannot use frozen=True as existing tests modify the fingerprint property
)
version: str = Field(
default="1.0.0",
description="Version of the security configuration"
)
fingerprint: Fingerprint = Field(
default_factory=Fingerprint,
description="Unique identifier for the component"
)
def is_compatible(self, min_version: str) -> bool:
"""
Check if this security configuration is compatible with the minimum required version.
Args:
min_version (str): Minimum required version in semver format (e.g., "1.0.0")
Returns:
bool: True if this configuration is compatible, False otherwise
"""
# Simple version comparison (can be enhanced with packaging.version if needed)
current = [int(x) for x in self.version.split(".")]
minimum = [int(x) for x in min_version.split(".")]
# Compare major, minor, patch versions
for c, m in zip(current, minimum):
if c > m:
return True
if c < m:
return False
return True
@model_validator(mode='before')
@classmethod