Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

949 lines
31 KiB
Python

"""
Behavioral tests for url_validator module.
Tests the URLValidator class which validates URLs for security.
"""
import pytest
class TestIsUnsafeScheme:
"""Tests for detecting unsafe URL schemes."""
def test_javascript_scheme_is_unsafe(self):
"""javascript: scheme is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("javascript:alert(1)") is True
def test_data_scheme_is_unsafe(self):
"""data: scheme is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("data:text/html,<script>") is True
def test_vbscript_scheme_is_unsafe(self):
"""vbscript: scheme is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("vbscript:msgbox") is True
def test_about_scheme_is_unsafe(self):
"""about: scheme is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("about:blank") is True
def test_blob_scheme_is_unsafe(self):
"""blob: scheme is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_unsafe_scheme("blob:http://example.com/id") is True
)
def test_file_scheme_is_unsafe(self):
"""file: scheme is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("file:///etc/passwd") is True
def test_http_scheme_is_safe(self):
"""http: scheme is safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("http://example.com") is False
def test_https_scheme_is_safe(self):
"""https: scheme is safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("https://example.com") is False
def test_empty_url_is_safe(self):
"""Empty URL returns False (not unsafe)."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("") is False
def test_none_is_safe(self):
"""None returns False (not unsafe)."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme(None) is False
def test_case_insensitive_javascript(self):
"""JAVASCRIPT: (uppercase) is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("JAVASCRIPT:alert(1)") is True
def test_case_insensitive_mixed(self):
"""JaVaScRiPt: (mixed case) is unsafe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme("JaVaScRiPt:alert(1)") is True
def test_whitespace_trimmed(self):
"""Whitespace is trimmed before checking."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_unsafe_scheme(" javascript:alert(1) ") is True
class TestIsSafeUrl:
"""Tests for URL safety validation."""
def test_https_url_is_safe(self):
"""Standard HTTPS URL is safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("https://example.com") is True
def test_http_url_is_safe(self):
"""Standard HTTP URL is safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("http://example.com") is True
def test_ftp_url_is_safe(self):
"""FTP URL is safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("ftp://files.example.com") is True
def test_ftps_url_is_safe(self):
"""FTPS URL is safe."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url("ftps://secure.files.example.com") is True
)
def test_javascript_url_is_not_safe(self):
"""JavaScript URL is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("javascript:alert(1)") is False
def test_data_url_is_not_safe(self):
"""Data URL is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("data:text/html,<script>") is False
def test_empty_url_is_not_safe(self):
"""Empty URL is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("") is False
def test_none_is_not_safe(self):
"""None is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url(None) is False
def test_non_string_is_not_safe(self):
"""Non-string is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url(123) is False
def test_fragment_only_allowed_by_default(self):
"""Fragment-only URL is allowed by default."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("#section") is True
def test_fragment_only_can_be_disallowed(self):
"""Fragment-only URL can be disallowed."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url("#section", allow_fragments=False) is False
)
class TestIsSafeUrlSchemeRequirement:
"""Tests for scheme requirement option."""
def test_requires_scheme_by_default(self):
"""Scheme is required by default."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url("example.com", require_scheme=True)
is False
)
def test_allows_no_scheme_when_disabled(self):
"""No scheme allowed when requirement disabled."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url("example.com", require_scheme=False)
is True
)
class TestIsSafeUrlMailto:
"""Tests for mailto URL handling."""
def test_mailto_disallowed_by_default(self):
"""Mailto is disallowed by default."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("mailto:test@example.com") is False
def test_mailto_allowed_when_enabled(self):
"""Mailto is allowed when enabled."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url(
"mailto:test@example.com", allow_mailto=True
)
is True
)
class TestIsSafeUrlTrustedDomains:
"""Tests for trusted domain validation."""
def test_trusted_domain_passes(self):
"""URL from trusted domain passes."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url(
"https://trusted.com/page", trusted_domains=["trusted.com"]
)
is True
)
def test_untrusted_domain_fails(self):
"""URL from untrusted domain fails."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url(
"https://evil.com/page", trusted_domains=["trusted.com"]
)
is False
)
def test_subdomain_of_trusted_passes(self):
"""Subdomain of trusted domain passes."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_url(
"https://sub.trusted.com/page", trusted_domains=["trusted.com"]
)
is True
)
def test_no_trusted_domains_allows_all(self):
"""No trusted domains list allows all domains."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("https://any-domain.com/page") is True
class TestSuspiciousPatterns:
"""Tests for suspicious URL pattern detection."""
def test_double_encoding_is_suspicious(self):
"""Double URL encoding is suspicious."""
from local_deep_research.security.url_validator import URLValidator
# %252F is double-encoded /
assert URLValidator.is_safe_url("https://example.com/%252F") is False
def test_null_bytes_are_suspicious(self):
"""Null bytes in URL are suspicious."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_safe_url("https://example.com/%00file") is False
def test_normal_encoding_is_fine(self):
"""Normal URL encoding is fine."""
from local_deep_research.security.url_validator import URLValidator
# %20 is normal space encoding
assert (
URLValidator.is_safe_url("https://example.com/hello%20world")
is True
)
class TestSanitizeUrl:
"""Tests for URL sanitization."""
def test_adds_https_by_default(self):
"""Adds https:// scheme by default."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url("example.com")
assert result == "https://example.com"
def test_adds_custom_scheme(self):
"""Adds custom scheme when specified."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url("example.com", default_scheme="http")
assert result == "http://example.com"
def test_preserves_existing_https(self):
"""Preserves existing https:// scheme."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url("https://example.com")
assert result == "https://example.com"
def test_preserves_existing_http(self):
"""Preserves existing http:// scheme."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url("http://example.com")
assert result == "http://example.com"
def test_returns_none_for_unsafe(self):
"""Returns None for unsafe URL."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url("javascript:alert(1)")
assert result is None
def test_returns_none_for_empty(self):
"""Returns None for empty URL."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url("")
assert result is None
def test_strips_whitespace(self):
"""Strips whitespace from URL."""
from local_deep_research.security.url_validator import URLValidator
result = URLValidator.sanitize_url(" example.com ")
assert result == "https://example.com"
class TestIsAcademicUrl:
"""Tests for academic URL detection."""
def test_arxiv_is_academic(self):
"""arxiv.org is academic."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_academic_url("https://arxiv.org/abs/1234.5678")
is True
)
def test_pubmed_is_academic(self):
"""pubmed is academic."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_academic_url(
"https://pubmed.ncbi.nlm.nih.gov/12345678/"
)
is True
)
def test_doi_org_is_academic(self):
"""doi.org is academic."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_academic_url("https://doi.org/10.1000/test") is True
)
def test_nature_is_academic(self):
"""nature.com is academic."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_academic_url("https://www.nature.com/articles/test")
is True
)
def test_science_is_academic(self):
"""science.org is academic."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_academic_url("https://www.science.org/article")
is True
)
def test_ieee_is_academic(self):
"""ieee.org is academic."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_academic_url("https://ieeexplore.ieee.org/document")
is True
)
def test_random_site_not_academic(self):
"""Random site is not academic."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_academic_url("https://example.com") is False
def test_google_not_academic(self):
"""google.com is not academic."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.is_academic_url("https://google.com") is False
class TestExtractDoi:
"""Tests for DOI extraction from URLs."""
def test_extracts_doi_from_doi_org(self):
"""Extracts DOI from doi.org URL."""
from local_deep_research.security.url_validator import URLValidator
url = "https://doi.org/10.1000/test.123"
doi = URLValidator.extract_doi(url)
assert doi is not None
assert "10.1000" in doi
def test_extracts_direct_doi(self):
"""Extracts direct DOI pattern."""
from local_deep_research.security.url_validator import URLValidator
url = "10.1000/test.456"
doi = URLValidator.extract_doi(url)
assert doi is not None
assert "10.1000" in doi
def test_returns_none_for_no_doi(self):
"""Returns None when no DOI found."""
from local_deep_research.security.url_validator import URLValidator
url = "https://example.com/no-doi-here"
doi = URLValidator.extract_doi(url)
assert doi is None
def test_extracts_doi_with_special_chars(self):
"""Extracts DOI with special characters."""
from local_deep_research.security.url_validator import URLValidator
url = "https://doi.org/10.1234/journal.test-123_456"
doi = URLValidator.extract_doi(url)
assert doi is not None
class TestValidateHttpUrl:
"""Tests for HTTP URL validation."""
def test_valid_https_url(self):
"""Valid HTTPS URL passes."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.validate_http_url("https://example.com/path") is True
)
def test_valid_http_url(self):
"""Valid HTTP URL passes."""
from local_deep_research.security.url_validator import URLValidator
assert URLValidator.validate_http_url("http://example.com/path") is True
def test_rejects_empty_url(self):
"""Empty URL raises error."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url("")
def test_rejects_none_url(self):
"""None URL raises error."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url(None)
def test_rejects_no_scheme(self):
"""URL without scheme raises error."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url("example.com")
def test_rejects_ftp_scheme(self):
"""FTP scheme raises error for HTTP callback."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url("ftp://files.example.com")
def test_rejects_no_hostname(self):
"""URL without hostname raises error."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url("https:///path")
def test_rejects_invalid_hostname_prefix(self):
"""Hostname starting with dot raises error."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url("https://.example.com")
def test_rejects_invalid_hostname_suffix(self):
"""Hostname ending with dot raises error."""
from local_deep_research.security.url_validator import (
URLValidator,
URLValidationError,
)
with pytest.raises(URLValidationError):
URLValidator.validate_http_url("https://example.com.")
class TestIsSafeRedirectUrl:
"""Tests for safe redirect URL validation."""
def test_relative_url_is_safe(self):
"""Relative URL is safe."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url("/path", "http://example.com/")
is True
)
def test_same_host_absolute_is_safe(self):
"""Absolute URL to same host is safe."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"http://example.com/path", "http://example.com/"
)
is True
)
def test_different_host_is_not_safe(self):
"""Absolute URL to different host is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"http://evil.com/path", "http://example.com/"
)
is False
)
def test_empty_target_is_not_safe(self):
"""Empty target URL is not safe."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url("", "http://example.com/")
is False
)
def test_crlf_injection_blocked(self):
"""CRLF injection in URL is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/path\r\nSet-Cookie: evil", "http://example.com/"
)
is False
)
def test_newline_injection_blocked(self):
"""Newline injection is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/path\nHeader: value", "http://example.com/"
)
is False
)
def test_carriage_return_blocked(self):
"""Carriage return is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/path\rHeader: value", "http://example.com/"
)
is False
)
def test_path_traversal_blocked(self):
"""Path traversal in redirect URL path is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/../../../etc/passwd", "http://example.com/"
)
is False
)
def test_relative_path_traversal_blocked(self):
"""Relative path traversal is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url("../admin", "http://example.com/")
is False
)
def test_encoded_path_traversal_blocked(self):
"""URL-encoded path traversal (%2e%2e) is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"%2e%2e/admin", "http://example.com/"
)
is False
)
def test_mid_path_traversal_blocked(self):
"""Mid-path traversal (/foo/../bar) is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/foo/../bar", "http://example.com/"
)
is False
)
def test_dotdot_in_query_param_not_blocked(self):
"""Double dots in query parameters should NOT trigger path traversal blocking."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/search?q=foo..bar", "http://example.com/"
)
is True
)
class TestIsSafeRedirectUrlBypassVectors:
"""Tests for bypass vectors in redirect URL validation."""
def test_triple_slash_bypass_blocked(self):
"""Triple-slash bypass (///evil.com) is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"///evil.com", "http://example.com/"
)
is False
)
def test_protocol_relative_bypass_blocked(self):
"""Protocol-relative URL (//evil.com) is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"//evil.com", "http://example.com/"
)
is False
)
def test_encoded_protocol_relative_bypass_blocked(self):
"""URL-encoded protocol-relative URL is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"%2f%2fevil.com", "http://example.com/"
)
is False
)
def test_backslash_bypass_blocked(self):
"""Backslash bypass is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"\\\\evil.com", "http://example.com/"
)
is False
)
def test_single_backslash_blocked(self):
"""Single backslash bypass is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"\\evil.com", "http://example.com/"
)
is False
)
def test_encoded_backslash_blocked(self):
"""URL-encoded backslash is blocked."""
from local_deep_research.security.url_validator import URLValidator
# %5c is encoded backslash
assert (
URLValidator.is_safe_redirect_url(
"%5cevil.com", "http://example.com/"
)
is False
)
def test_valid_relative_path_still_works(self):
"""Valid relative paths still work after security fixes."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/dashboard", "http://example.com/"
)
is True
)
def test_valid_relative_path_with_query(self):
"""Valid relative paths with query params still work."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/search?q=test", "http://example.com/"
)
is True
)
def test_valid_same_host_absolute_url(self):
"""Valid same-host absolute URL still works."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"http://example.com/page", "http://example.com/"
)
is True
)
def test_different_host_blocked(self):
"""Different host is still blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"http://evil.com/page", "http://example.com/"
)
is False
)
def test_https_different_host_blocked(self):
"""HTTPS to different host is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"https://evil.com/page", "http://example.com/"
)
is False
)
def test_path_traversal_blocked(self):
"""Path traversal attempt with /../admin is blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/../admin", "http://example.com/"
)
is False
)
def test_encoded_crlf_blocked(self):
"""Encoded CRLF injection (%0d%0a) is blocked after URL decoding."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/path%0d%0aSet-Cookie:evil", "http://example.com/"
)
is False
)
def test_double_dot_in_filename_allowed(self):
"""Legitimate filenames containing '..' are not treated as path traversal."""
from local_deep_research.security.url_validator import URLValidator
# '..' in a filename is not a traversal pattern
assert (
URLValidator.is_safe_redirect_url(
"/my..page", "http://example.com/"
)
is True
)
def test_double_dot_in_query_string_allowed(self):
"""Query strings containing '..' are not treated as path traversal."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/search?q=range..10", "http://example.com/"
)
is True
)
def test_null_byte_blocked(self):
"""Null bytes in redirect URLs are blocked."""
from local_deep_research.security.url_validator import URLValidator
assert (
URLValidator.is_safe_redirect_url(
"/path%00evil", "http://example.com/"
)
is False
)
class TestURLValidationErrorClass:
"""Tests for URLValidationError exception."""
def test_is_value_error_subclass(self):
"""URLValidationError is ValueError subclass."""
from local_deep_research.security.url_validator import (
URLValidationError,
)
assert issubclass(URLValidationError, ValueError)
def test_can_be_raised(self):
"""URLValidationError can be raised."""
from local_deep_research.security.url_validator import (
URLValidationError,
)
with pytest.raises(URLValidationError) as excinfo:
raise URLValidationError("test error")
assert "test error" in str(excinfo.value)
class TestTrustedAcademicDomains:
"""Tests for trusted academic domains constant."""
def test_contains_arxiv(self):
"""Trusted domains include arxiv.org."""
from local_deep_research.security.url_validator import URLValidator
assert "arxiv.org" in URLValidator.TRUSTED_ACADEMIC_DOMAINS
def test_contains_pubmed(self):
"""Trusted domains include pubmed.ncbi.nlm.nih.gov."""
from local_deep_research.security.url_validator import URLValidator
assert (
"pubmed.ncbi.nlm.nih.gov" in URLValidator.TRUSTED_ACADEMIC_DOMAINS
)
def test_contains_doi(self):
"""Trusted domains include doi.org."""
from local_deep_research.security.url_validator import URLValidator
assert "doi.org" in URLValidator.TRUSTED_ACADEMIC_DOMAINS
def test_is_tuple(self):
"""Trusted domains is a tuple."""
from local_deep_research.security.url_validator import URLValidator
assert isinstance(URLValidator.TRUSTED_ACADEMIC_DOMAINS, tuple)
class TestUnsafeSchemes:
"""Tests for unsafe schemes constant."""
def test_contains_javascript(self):
"""Unsafe schemes include javascript."""
from local_deep_research.security.url_validator import URLValidator
assert "javascript" in URLValidator.UNSAFE_SCHEMES
def test_contains_data(self):
"""Unsafe schemes include data."""
from local_deep_research.security.url_validator import URLValidator
assert "data" in URLValidator.UNSAFE_SCHEMES
def test_contains_file(self):
"""Unsafe schemes include file."""
from local_deep_research.security.url_validator import URLValidator
assert "file" in URLValidator.UNSAFE_SCHEMES
def test_is_tuple(self):
"""Unsafe schemes is a tuple."""
from local_deep_research.security.url_validator import URLValidator
assert isinstance(URLValidator.UNSAFE_SCHEMES, tuple)
class TestSafeSchemes:
"""Tests for safe schemes constant."""
def test_contains_http(self):
"""Safe schemes include http."""
from local_deep_research.security.url_validator import URLValidator
assert "http" in URLValidator.SAFE_SCHEMES
def test_contains_https(self):
"""Safe schemes include https."""
from local_deep_research.security.url_validator import URLValidator
assert "https" in URLValidator.SAFE_SCHEMES
def test_contains_ftp(self):
"""Safe schemes include ftp."""
from local_deep_research.security.url_validator import URLValidator
assert "ftp" in URLValidator.SAFE_SCHEMES
def test_is_tuple(self):
"""Safe schemes is a tuple."""
from local_deep_research.security.url_validator import URLValidator
assert isinstance(URLValidator.SAFE_SCHEMES, tuple)