diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 79f6613cb..58621f8a4 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -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: diff --git a/src/crewai/security/__init__.py b/src/crewai/security/__init__.py index 6dc97ea18..91602970f 100644 --- a/src/crewai/security/__init__.py +++ b/src/crewai/security/__init__.py @@ -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"] \ No newline at end of file +__all__ = ["Fingerprint", "SecurityConfig"] diff --git a/src/crewai/security/fingerprint.py b/src/crewai/security/fingerprint.py index ee3aab586..982c62492 100644 --- a/src/crewai/security/fingerprint.py +++ b/src/crewai/security/fingerprint.py @@ -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 diff --git a/src/crewai/security/security_config.py b/src/crewai/security/security_config.py index c1ed5c50d..9f680de42 100644 --- a/src/crewai/security/security_config.py +++ b/src/crewai/security/security_config.py @@ -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