From 04007dd28a6eea2c3d4eedb1e060fb75cea18685 Mon Sep 17 00:00:00 2001 From: etserend Date: Wed, 21 Oct 2026 11:39:03 -0500 Subject: [PATCH 1/4] fix(crewai): replace MD5 with SHA-256 for deterministic run IDs (VULN-115244) Co-Authored-By: Claude Code --- src/splunk_ao/handlers/crewai/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/splunk_ao/handlers/crewai/handler.py b/src/splunk_ao/handlers/crewai/handler.py index 6adc870c..0a0909c6 100644 --- a/src/splunk_ao/handlers/crewai/handler.py +++ b/src/splunk_ao/handlers/crewai/handler.py @@ -287,7 +287,7 @@ def on_memory_retrieval_failed(source: Any, event: MemoryRetrievalFailedEvent) - @staticmethod def _hash_to_uuid(value: str) -> UUID: """Hash a string to a deterministic UUID.""" - return UUID(hashlib.md5(value.encode()).hexdigest()) + return UUID(bytes=hashlib.sha256(value.encode()).digest()[:16]) def _generate_run_id(self, source: Any, event: Any) -> UUID: """Generate a consistent UUID for event tracing.""" From d5dc509a02051708ca54ecaac6d253a3754a7078 Mon Sep 17 00:00:00 2001 From: etserend Date: Wed, 21 Oct 2026 13:36:20 -0500 Subject: [PATCH 2/4] fix(crewai): set UUID version bits in _hash_to_uuid (VULN-115244) Co-Authored-By: Claude Code --- src/splunk_ao/handlers/crewai/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/splunk_ao/handlers/crewai/handler.py b/src/splunk_ao/handlers/crewai/handler.py index 0a0909c6..e37d86ee 100644 --- a/src/splunk_ao/handlers/crewai/handler.py +++ b/src/splunk_ao/handlers/crewai/handler.py @@ -287,7 +287,7 @@ def on_memory_retrieval_failed(source: Any, event: MemoryRetrievalFailedEvent) - @staticmethod def _hash_to_uuid(value: str) -> UUID: """Hash a string to a deterministic UUID.""" - return UUID(bytes=hashlib.sha256(value.encode()).digest()[:16]) + return UUID(bytes=hashlib.sha256(value.encode()).digest()[:16], version=4) def _generate_run_id(self, source: Any, event: Any) -> UUID: """Generate a consistent UUID for event tracing.""" From 6efd50d4458678929a8b5e1849070b65a955cc16 Mon Sep 17 00:00:00 2001 From: etserend Date: Wed, 21 Oct 2026 13:52:41 -0500 Subject: [PATCH 3/4] refactor(uuid): share deterministic_uuid4 and add regression test (VULN-115244) Co-Authored-By: Claude Code --- src/splunk_ao/handlers/crewai/handler.py | 4 ++-- src/splunk_ao/utils/uuid_utils.py | 26 +++++++++++++++++++----- tests/test_crewai_handler.py | 15 ++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/splunk_ao/handlers/crewai/handler.py b/src/splunk_ao/handlers/crewai/handler.py index e37d86ee..bea714ff 100644 --- a/src/splunk_ao/handlers/crewai/handler.py +++ b/src/splunk_ao/handlers/crewai/handler.py @@ -1,4 +1,3 @@ -import hashlib import json import logging from datetime import datetime @@ -11,6 +10,7 @@ from splunk_ao.logger import SplunkAOLogger from splunk_ao.schema.handlers import NodeType from splunk_ao.utils.serialization import serialize_to_str +from splunk_ao.utils.uuid_utils import deterministic_uuid4 _logger = logging.getLogger(__name__) @@ -287,7 +287,7 @@ def on_memory_retrieval_failed(source: Any, event: MemoryRetrievalFailedEvent) - @staticmethod def _hash_to_uuid(value: str) -> UUID: """Hash a string to a deterministic UUID.""" - return UUID(bytes=hashlib.sha256(value.encode()).digest()[:16], version=4) + return deterministic_uuid4(value) def _generate_run_id(self, source: Any, event: Any) -> UUID: """Generate a consistent UUID for event tracing.""" diff --git a/src/splunk_ao/utils/uuid_utils.py b/src/splunk_ao/utils/uuid_utils.py index 460a5eda..62d8c640 100644 --- a/src/splunk_ao/utils/uuid_utils.py +++ b/src/splunk_ao/utils/uuid_utils.py @@ -34,6 +34,26 @@ def is_uuid7(uuid_obj: UUID | str) -> bool: return False +def deterministic_uuid4(value: str) -> UUID: + """ + Derive a deterministic UUID4 from a string. + + The first 16 bytes of the SHA-256 digest of the UTF-8 encoded string are used, + with the RFC 4122 version and variant bits set. + + Parameters + ---------- + value : str + The string to derive the UUID from. + + Returns + ------- + UUID + A version 4 UUID that is stable for a given input. + """ + return UUID(bytes=hashlib.sha256(value.encode()).digest()[:16], version=4) + + def uuid7_to_uuid4(uuid_obj: UUID | str) -> UUID: """ Convert a UUID7 to a UUID4 by hashing its string representation. @@ -67,11 +87,7 @@ def uuid7_to_uuid4(uuid_obj: UUID | str) -> UUID: if uuid_obj.version != 7: raise ValueError(f"Input UUID is version {uuid_obj.version}, not version 7") - # Hash the UUID string using SHA-256 - hash_digest = hashlib.sha256(uuid_str.encode()).digest() - - # Use the first 16 bytes of the hash to create a version 4 UUID - new_uuid = UUID(bytes=hash_digest[:16], version=4) + new_uuid = deterministic_uuid4(uuid_str) _logger.debug(f"Converted UUID7 {uuid_str} to UUID4 {new_uuid}") return new_uuid diff --git a/tests/test_crewai_handler.py b/tests/test_crewai_handler.py index 96d36e10..5545d423 100644 --- a/tests/test_crewai_handler.py +++ b/tests/test_crewai_handler.py @@ -1,3 +1,4 @@ +import hashlib import sys import uuid from datetime import datetime @@ -211,6 +212,20 @@ def test_generate_run_id_with_source_id(crewai_callback) -> None: assert result == source_id +def test_hash_to_uuid_is_sha256_v4() -> None: + # Given: a fixed input string + value = "agent_task" + + # When: it is hashed to a run ID + result = CrewAIEventListener._hash_to_uuid(value) + + # Then: the ID is the stable SHA-256-derived UUIDv4 + assert result == uuid.UUID("6b088e07-2934-42ca-8982-7184d89520ca") + assert result == uuid.UUID(bytes=hashlib.sha256(value.encode()).digest()[:16], version=4) + assert result.version == 4 + assert result == CrewAIEventListener._hash_to_uuid(value) + + def test_generate_run_id_with_messages(crewai_callback) -> None: """Test UUID generation from event messages.""" messages = [{"role": "user", "content": "test"}] From a66ec13c3f408e0defa90157867f0aa572834bfb Mon Sep 17 00:00:00 2001 From: etserend Date: Wed, 21 Oct 2026 14:24:34 -0500 Subject: [PATCH 4/4] docs(changelog): note CrewAI run ID hash change (VULN-115244) Co-Authored-By: Claude Code --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72556781..b61e43c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Image and file parts in OpenAI Agents user messages, on both the Responses and Chat Completions paths, are now recorded as content blocks instead of a JSON dump of the whole message, including through `SplunkAOLogger(ingestion_hook=...)`. +- CrewAI run IDs are now derived from SHA-256 (as RFC 4122 version 4 UUIDs) instead of MD5, resolving a + SAST finding. IDs for the same inputs differ from earlier releases. ## [0.5.0]