fix: clear CodeQL incomplete URL substring sanitization alerts (#6804)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled

* 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 <theCyberTech@users.noreply.github.com>

* test: call DataTypes.from_content in GitHub hostname tests

from_content lives on DataTypes, not the DataType enum.

Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>

* 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 <theCyberTech@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
This commit is contained in:
Rip&Tear
2026-08-05 13:28:47 +08:00
committed by GitHub
parent 7accafbaf4
commit 323334dac6
4 changed files with 63 additions and 9 deletions

View File

@@ -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

View File

@@ -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
)

View File

@@ -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(

View File

@@ -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