mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-02 05:38:12 +00:00
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (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
* Validate redirects for scraping URL fetches * Prevent credential forwarding across redirects
178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
"""Tests for redirect-aware safe HTTP helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import socket
|
|
from io import BytesIO
|
|
from typing import Any
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
from crewai_tools.security.safe_requests import safe_get
|
|
|
|
|
|
def _response(url: str, status_code: int, *, location: str | None = None) -> requests.Response:
|
|
response = requests.Response()
|
|
response.status_code = status_code
|
|
response.url = url
|
|
response._content = b"ok"
|
|
response.raw = BytesIO()
|
|
if location is not None:
|
|
response.headers["Location"] = location
|
|
return response
|
|
|
|
|
|
@pytest.fixture
|
|
def public_dns(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
original_getaddrinfo = socket.getaddrinfo
|
|
|
|
def fake_getaddrinfo(
|
|
host: str, port: int, *args: Any, **kwargs: Any
|
|
) -> list[tuple[Any, ...]]:
|
|
if host in {"public.example", "safe.example"}:
|
|
return [
|
|
(
|
|
socket.AF_INET,
|
|
socket.SOCK_STREAM,
|
|
6,
|
|
"",
|
|
("93.184.216.34", port),
|
|
)
|
|
]
|
|
return original_getaddrinfo(host, port, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
|
|
|
|
|
|
def test_safe_get_blocks_direct_internal_url() -> None:
|
|
with pytest.raises(ValueError, match="private/reserved IP"):
|
|
safe_get("http://127.0.0.1/admin", timeout=15)
|
|
|
|
|
|
def _mock_get(monkeypatch: pytest.MonkeyPatch, get_response: Any) -> None:
|
|
monkeypatch.setattr(
|
|
"crewai_tools.security.safe_requests.requests.get",
|
|
get_response,
|
|
)
|
|
|
|
|
|
def test_safe_get_blocks_redirect_to_internal_url(
|
|
monkeypatch: pytest.MonkeyPatch, public_dns: None
|
|
) -> None:
|
|
requested_urls: list[str] = []
|
|
|
|
def fake_get(url: str, **kwargs: Any) -> requests.Response:
|
|
requested_urls.append(url)
|
|
assert kwargs["allow_redirects"] is False
|
|
return _response(url, 302, location="http://127.0.0.1/admin")
|
|
|
|
_mock_get(monkeypatch, fake_get)
|
|
|
|
with pytest.raises(ValueError, match="private/reserved IP"):
|
|
safe_get("http://public.example/start", timeout=15)
|
|
|
|
assert requested_urls == ["http://public.example/start"]
|
|
|
|
|
|
def test_safe_get_follows_safe_relative_redirect(
|
|
monkeypatch: pytest.MonkeyPatch, public_dns: None
|
|
) -> None:
|
|
requested_urls: list[str] = []
|
|
|
|
def fake_get(url: str, **kwargs: Any) -> requests.Response:
|
|
requested_urls.append(url)
|
|
assert kwargs["allow_redirects"] is False
|
|
if url == "http://public.example/start":
|
|
return _response(url, 302, location="/final")
|
|
return _response(url, 200)
|
|
|
|
_mock_get(monkeypatch, fake_get)
|
|
|
|
response = safe_get("http://public.example/start", timeout=15)
|
|
|
|
assert response.status_code == 200
|
|
assert response.url == "http://public.example/final"
|
|
assert requested_urls == [
|
|
"http://public.example/start",
|
|
"http://public.example/final",
|
|
]
|
|
assert len(response.history) == 1
|
|
|
|
|
|
def test_safe_get_fails_closed_after_too_many_redirects(
|
|
monkeypatch: pytest.MonkeyPatch, public_dns: None
|
|
) -> None:
|
|
def fake_get(url: str, **kwargs: Any) -> requests.Response:
|
|
return _response(url, 302, location="http://safe.example/again")
|
|
|
|
_mock_get(monkeypatch, fake_get)
|
|
|
|
with pytest.raises(ValueError, match="Too many redirects"):
|
|
safe_get("http://public.example/start", max_redirects=1, timeout=15)
|
|
|
|
|
|
def test_safe_get_strips_credentials_on_cross_origin_redirect(
|
|
monkeypatch: pytest.MonkeyPatch, public_dns: None
|
|
) -> None:
|
|
requests_made: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def fake_get(url: str, **kwargs: Any) -> requests.Response:
|
|
requests_made.append((url, kwargs))
|
|
if url == "http://public.example/start":
|
|
return _response(url, 302, location="http://safe.example/final")
|
|
return _response(url, 200)
|
|
|
|
_mock_get(monkeypatch, fake_get)
|
|
|
|
response = safe_get(
|
|
"http://public.example/start",
|
|
timeout=15,
|
|
headers={
|
|
"Authorization": "Bearer token",
|
|
"Authorization-Custom": "secret token",
|
|
"Cookie": "session=abc",
|
|
"X-API-Key": "api key",
|
|
"X-CrewAI-Token": "crewai token",
|
|
"User-Agent": "crewai-test",
|
|
},
|
|
cookies={"session": "abc"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert requests_made[0][1]["headers"] == {
|
|
"Authorization": "Bearer token",
|
|
"Authorization-Custom": "secret token",
|
|
"Cookie": "session=abc",
|
|
"X-API-Key": "api key",
|
|
"X-CrewAI-Token": "crewai token",
|
|
"User-Agent": "crewai-test",
|
|
}
|
|
assert requests_made[0][1]["cookies"] == {"session": "abc"}
|
|
assert requests_made[1][1]["headers"] == {"User-Agent": "crewai-test"}
|
|
assert "cookies" not in requests_made[1][1]
|
|
|
|
|
|
def test_safe_get_preserves_credentials_on_same_origin_redirect(
|
|
monkeypatch: pytest.MonkeyPatch, public_dns: None
|
|
) -> None:
|
|
requests_made: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def fake_get(url: str, **kwargs: Any) -> requests.Response:
|
|
requests_made.append((url, kwargs))
|
|
if url == "http://public.example/start":
|
|
return _response(url, 302, location="/final")
|
|
return _response(url, 200)
|
|
|
|
_mock_get(monkeypatch, fake_get)
|
|
|
|
safe_get(
|
|
"http://public.example/start",
|
|
timeout=15,
|
|
headers={"Authorization": "Bearer token"},
|
|
cookies={"session": "abc"},
|
|
)
|
|
|
|
assert requests_made[1][1]["headers"] == {"Authorization": "Bearer token"}
|
|
assert requests_made[1][1]["cookies"] == {"session": "abc"}
|