Files
learningcircuit--local-deep…/tests/security/test_module_whitelist_high_value.py
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

237 lines
7.5 KiB
Python

"""High-value tests for security/module_whitelist.py - Module Import Security."""
from unittest.mock import patch, MagicMock
import pytest
from local_deep_research.security.module_whitelist import (
validate_module_import,
get_safe_module_class,
ALLOWED_MODULE_PATHS,
ALLOWED_CLASS_NAMES,
ALLOWED_MODULES,
SecurityError,
ModuleNotAllowedError,
)
# ---------------------------------------------------------------------------
# ALLOWED_MODULE_PATHS constraints
# ---------------------------------------------------------------------------
class TestAllowedModulePaths:
"""Whitelist structure validation."""
def test_is_frozenset(self):
assert isinstance(ALLOWED_MODULE_PATHS, frozenset)
def test_all_start_with_dot(self):
for path in ALLOWED_MODULE_PATHS:
assert path.startswith("."), (
f"Module path {path!r} must start with '.'"
)
def test_legacy_alias_same_object(self):
assert ALLOWED_MODULES is ALLOWED_MODULE_PATHS
def test_known_engine_paths_present(self):
expected = [
".engines.search_engine_brave",
".engines.search_engine_ddg",
".engines.search_engine_arxiv",
".engines.search_engine_wikipedia",
".engines.search_engine_tavily",
]
for path in expected:
assert path in ALLOWED_MODULE_PATHS
def test_not_empty(self):
assert len(ALLOWED_MODULE_PATHS) > 0
# ---------------------------------------------------------------------------
# ALLOWED_CLASS_NAMES constraints
# ---------------------------------------------------------------------------
class TestAllowedClassNames:
"""Class name whitelist validation."""
def test_is_frozenset(self):
assert isinstance(ALLOWED_CLASS_NAMES, frozenset)
def test_known_classes_present(self):
expected = [
"BraveSearchEngine",
"DuckDuckGoSearchEngine",
"ArXivSearchEngine",
"WikipediaSearchEngine",
"TavilySearchEngine",
"BaseSearchEngine",
]
for cls_name in expected:
assert cls_name in ALLOWED_CLASS_NAMES
def test_not_empty(self):
assert len(ALLOWED_CLASS_NAMES) > 0
# ---------------------------------------------------------------------------
# SecurityError / ModuleNotAllowedError
# ---------------------------------------------------------------------------
class TestSecurityError:
def test_is_exception(self):
assert issubclass(SecurityError, Exception)
def test_alias_is_same_class(self):
assert ModuleNotAllowedError is SecurityError
def test_can_raise_and_catch(self):
with pytest.raises(SecurityError):
raise SecurityError("blocked")
# ---------------------------------------------------------------------------
# validate_module_import()
# ---------------------------------------------------------------------------
class TestValidateModuleImport:
"""Module + class pair validation."""
def test_valid_pair_accepted(self):
assert (
validate_module_import(
".engines.search_engine_brave", "BraveSearchEngine"
)
is True
)
def test_non_relative_path_rejected(self):
assert validate_module_import("os", "system") is False
def test_absolute_dotted_path_rejected(self):
assert (
validate_module_import(
"local_deep_research.web_search_engines.engines.search_engine_brave",
"BraveSearchEngine",
)
is False
)
def test_empty_module_rejected(self):
assert validate_module_import("", "BraveSearchEngine") is False
def test_empty_class_rejected(self):
assert (
validate_module_import(".engines.search_engine_brave", "") is False
)
def test_both_empty_rejected(self):
assert validate_module_import("", "") is False
def test_unlisted_module_rejected(self):
assert (
validate_module_import(
".engines.search_engine_evil", "BraveSearchEngine"
)
is False
)
def test_unlisted_class_rejected(self):
assert (
validate_module_import(".engines.search_engine_brave", "EvilEngine")
is False
)
def test_both_unlisted_rejected(self):
assert validate_module_import(".evil_module", "EvilClass") is False
# ---------------------------------------------------------------------------
# get_safe_module_class()
# ---------------------------------------------------------------------------
class TestGetSafeModuleClass:
"""Safe dynamic import."""
def test_invalid_module_raises_security_error(self):
with pytest.raises(
SecurityError, match="not in the security whitelist"
):
get_safe_module_class("os", "system")
def test_invalid_class_raises_security_error(self):
with pytest.raises(
SecurityError, match="not in the security whitelist"
):
get_safe_module_class(".engines.search_engine_brave", "EvilClass")
def test_whitelisted_class_non_whitelisted_module_rejected(self):
"""Whitelisted class name with non-whitelisted module still raises."""
with pytest.raises(
SecurityError, match="not in the security whitelist"
):
get_safe_module_class(
".engines.search_engine_evil", "BraveSearchEngine"
)
@patch(
"local_deep_research.security.module_whitelist.importlib.import_module"
)
def test_valid_pair_imports_and_returns_class(self, mock_import):
mock_module = MagicMock()
mock_module.BraveSearchEngine = type("BraveSearchEngine", (), {})
mock_import.return_value = mock_module
cls = get_safe_module_class(
".engines.search_engine_brave", "BraveSearchEngine"
)
assert cls is mock_module.BraveSearchEngine
mock_import.assert_called_once_with(
".engines.search_engine_brave",
package="local_deep_research.web_search_engines",
)
@patch(
"local_deep_research.security.module_whitelist.importlib.import_module"
)
def test_custom_package_used(self, mock_import):
mock_module = MagicMock()
mock_module.BraveSearchEngine = type("BraveSearchEngine", (), {})
mock_import.return_value = mock_module
get_safe_module_class(
".engines.search_engine_brave",
"BraveSearchEngine",
package="custom.package",
)
mock_import.assert_called_once_with(
".engines.search_engine_brave", package="custom.package"
)
@patch(
"local_deep_research.security.module_whitelist.importlib.import_module"
)
def test_module_not_found_propagates(self, mock_import):
mock_import.side_effect = ModuleNotFoundError("No module")
with pytest.raises(ModuleNotFoundError):
get_safe_module_class(
".engines.search_engine_brave", "BraveSearchEngine"
)
@patch(
"local_deep_research.security.module_whitelist.importlib.import_module"
)
def test_attribute_error_propagates(self, mock_import):
mock_module = MagicMock(spec=[]) # no attributes
mock_import.return_value = mock_module
with pytest.raises(AttributeError):
get_safe_module_class(
".engines.search_engine_brave", "BraveSearchEngine"
)