From 323334dac6bf19a527900a8a0cdb1e27ff9a28be Mon Sep 17 00:00:00 2001 From: Rip&Tear <84775494+theCyberTech@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:28:47 +0800 Subject: [PATCH] fix: clear CodeQL incomplete URL substring sanitization alerts (#6804) * fix: clear CodeQL incomplete URL substring sanitization alerts Replace hostname substring checks with urlparse hostname matching in RAG DataType classification, and assert the full mocked Stagehand navigate result instead of searching for a URL substring. Co-authored-by: Rip&Tear * test: call DataTypes.from_content in GitHub hostname tests from_content lives on DataTypes, not the DataType enum. Co-authored-by: Rip&Tear * test: harden tool-call streaming emit mock against instance shadowing CI failed when class-level CrewAIEventsBus.emit patches were shadowed by the singleton instance. Patch both the class and crewai_event_bus.emit, and read events from kwargs/args explicitly. Co-authored-by: Rip&Tear --------- Co-authored-by: Cursor Agent Co-authored-by: Rip&Tear --- .../src/crewai_tools/rag/data_types.py | 3 +- lib/crewai-tools/tests/rag/test_data_types.py | 32 +++++++++++++++++++ .../tests/tools/stagehand_tool_test.py | 10 ++++-- .../tests/llms/test_tool_call_streaming.py | 27 ++++++++++++---- 4 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 lib/crewai-tools/tests/rag/test_data_types.py diff --git a/lib/crewai-tools/src/crewai_tools/rag/data_types.py b/lib/crewai-tools/src/crewai_tools/rag/data_types.py index 27ee48abb..0fcf07e12 100644 --- a/lib/crewai-tools/src/crewai_tools/rag/data_types.py +++ b/lib/crewai-tools/src/crewai_tools/rag/data_types.py @@ -135,7 +135,8 @@ class DataTypes: if "docs" in url.netloc or ("docs" in url.path and url.scheme != "file"): return DataType.DOCS_SITE - if "github.com" in url.netloc: + hostname = (url.hostname or "").lower() + if hostname == "github.com" or hostname.endswith(".github.com"): return DataType.GITHUB return DataType.WEBSITE diff --git a/lib/crewai-tools/tests/rag/test_data_types.py b/lib/crewai-tools/tests/rag/test_data_types.py new file mode 100644 index 000000000..edb398801 --- /dev/null +++ b/lib/crewai-tools/tests/rag/test_data_types.py @@ -0,0 +1,32 @@ +"""Tests for DataTypes content classification.""" + +from crewai_tools.rag.data_types import DataType, DataTypes + + +class TestDataTypesFromContentGitHub: + """GitHub URL detection must use hostname matching, not substrings.""" + + def test_github_com_url(self) -> None: + assert ( + DataTypes.from_content("https://github.com/crewai/crewai") + == DataType.GITHUB + ) + + def test_github_subdomain_url(self) -> None: + assert ( + DataTypes.from_content("https://gist.github.com/user/abc") + == DataType.GITHUB + ) + + def test_spoofed_github_hostname_is_website(self) -> None: + # Substring checks like `"github.com" in netloc` would misclassify this. + assert ( + DataTypes.from_content("https://github.com.evil.example/crewai") + == DataType.WEBSITE + ) + + def test_github_in_path_is_not_github(self) -> None: + assert ( + DataTypes.from_content("https://example.com/github.com/repo") + == DataType.WEBSITE + ) diff --git a/lib/crewai-tools/tests/tools/stagehand_tool_test.py b/lib/crewai-tools/tests/tools/stagehand_tool_test.py index 508b1357e..eefe1340e 100644 --- a/lib/crewai-tools/tests/tools/stagehand_tool_test.py +++ b/lib/crewai-tools/tests/tools/stagehand_tool_test.py @@ -163,8 +163,14 @@ def test_navigate_command(mock_run, stagehand_tool): command_type="navigate", ) - # Assertions - assert "https://example.com" in result + # Assertions — compare the full mocked result (avoid URL substring checks) + assert result == "Successfully navigated to https://example.com" + mock_run.assert_called_once_with( + stagehand_tool, + instruction="Go to example.com", + url="https://example.com", + command_type="navigate", + ) @patch( diff --git a/lib/crewai/tests/llms/test_tool_call_streaming.py b/lib/crewai/tests/llms/test_tool_call_streaming.py index 7985aecca..03bc0fd9b 100644 --- a/lib/crewai/tests/llms/test_tool_call_streaming.py +++ b/lib/crewai/tests/llms/test_tool_call_streaming.py @@ -38,18 +38,33 @@ def get_temperature_tool_schema() -> dict[str, Any]: @pytest.fixture def mock_emit() -> MagicMock: - """Mock the event bus emit function.""" - from crewai.events.event_bus import CrewAIEventsBus + """Mock the singleton event bus emit used by LLM providers. - with patch.object(CrewAIEventsBus, "emit") as mock: - yield mock + Patch the singleton instance (not only the class) so a leftover + instance-level ``emit`` from other tests cannot shadow the mock. + """ + from crewai.events.event_bus import CrewAIEventsBus, crewai_event_bus + + with ( + patch.object(CrewAIEventsBus, "emit") as class_mock, + patch.object(crewai_event_bus, "emit", new=class_mock), + ): + yield class_mock + + +def _event_from_emit_call(call: Any) -> Any: + """Return the event argument from an emit mock call.""" + event = call.kwargs.get("event") + if event is None and len(call.args) >= 2: + event = call.args[1] + return event def get_tool_call_events(mock_emit: MagicMock) -> list[LLMStreamChunkEvent]: """Extract tool call streaming events from mock emit calls.""" tool_call_events = [] for call in mock_emit.call_args_list: - event = call[1].get("event") if len(call) > 1 else None + event = _event_from_emit_call(call) if isinstance(event, LLMStreamChunkEvent) and event.call_type == LLMCallType.TOOL_CALL: tool_call_events.append(event) return tool_call_events @@ -59,7 +74,7 @@ def get_all_stream_events(mock_emit: MagicMock) -> list[LLMStreamChunkEvent]: """Extract all streaming events from mock emit calls.""" stream_events = [] for call in mock_emit.call_args_list: - event = call[1].get("event") if len(call) > 1 else None + event = _event_from_emit_call(call) if isinstance(event, LLMStreamChunkEvent): stream_events.append(event) return stream_events