Coverage for src/local_deep_research/security/__init__.py: 100%
35 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
1"""Security utilities for Local Deep Research."""
3from .data_sanitizer import (
4 DataSanitizer,
5 filter_research_metadata,
6 redact_data,
7 sanitize_data,
8 strip_settings_snapshot,
9)
10from .egress.audit_hook import (
11 active_egress_context,
12 clear_active_context,
13 get_active_context,
14 install_audit_hook,
15 is_installed as is_audit_hook_installed,
16 set_active_context,
17)
18from .security_settings import get_security_default
20# FileIntegrityManager / FAISSIndexVerifier are re-exported lazily in
21# ``__getattr__`` below. Eager imports form a cycle through database.encrypted_db
22# when the security package is imported during database bootstrap.
23from .notification_validator import (
24 NotificationURLValidator,
25 NotificationURLValidationError,
26)
27from .safe_requests import safe_get, safe_post, SafeSession
28from .ssrf_validator import (
29 assert_base_url_safe,
30 get_safe_url,
31 is_ip_blocked,
32 redact_url_for_log,
33 url_authority_without_userinfo,
34 validate_url,
35)
36from .url_validator import URLValidator
37from .account_lockout import AccountLockoutManager, get_account_lockout_manager
38from .password_validator import PasswordValidator
39from .log_sanitizer import (
40 failure_reason_for_log,
41 redact_secrets,
42 sanitize_error_details,
43 sanitize_error_for_agent,
44 sanitize_error_for_client,
45 sanitize_error_message,
46 sanitize_for_log,
47 scrub_error,
48 strip_control_chars,
49)
50from .filename_sanitizer import sanitize_filename, UnsafeFilenameError
51from .client_safe_errors import (
52 CLIENT_SAFE_DOWNLOAD_MESSAGES,
53 client_safe_download_message,
54)
55from .module_whitelist import (
56 get_safe_module_class,
57 ModuleNotAllowedError,
58 ALLOWED_MODULES,
59)
61# PathValidator requires werkzeug (Flask dependency), import conditionally
62try:
63 from .path_validator import PathValidator
65 _has_path_validator = True
66except ImportError:
67 PathValidator = None # type: ignore
68 _has_path_validator = False
70# FileUploadValidator requires pdfplumber, import conditionally
71try:
72 from .file_upload_validator import FileUploadValidator
74 _has_file_upload_validator = True
75except ImportError:
76 FileUploadValidator = None # type: ignore
77 _has_file_upload_validator = False
79__all__ = [
80 "PathValidator",
81 "DataSanitizer",
82 "active_egress_context",
83 "clear_active_context",
84 "get_active_context",
85 "install_audit_hook",
86 "is_audit_hook_installed",
87 "set_active_context",
88 "sanitize_data",
89 "redact_data",
90 "filter_research_metadata",
91 "strip_settings_snapshot",
92 "get_security_default",
93 "FileIntegrityManager",
94 "FAISSIndexVerifier",
95 "FileUploadValidator",
96 "NotificationURLValidator",
97 "NotificationURLValidationError",
98 "URLValidator",
99 "safe_get",
100 "safe_post",
101 "SafeSession",
102 "validate_url",
103 "get_safe_url",
104 "is_ip_blocked",
105 "assert_base_url_safe",
106 "redact_url_for_log",
107 "url_authority_without_userinfo",
108 "get_safe_module_class",
109 "ModuleNotAllowedError",
110 "ALLOWED_MODULES",
111 "AccountLockoutManager",
112 "get_account_lockout_manager",
113 "PasswordValidator",
114 "failure_reason_for_log",
115 "redact_secrets",
116 "sanitize_error_details",
117 "sanitize_error_for_agent",
118 "sanitize_error_for_client",
119 "sanitize_error_message",
120 "sanitize_for_log",
121 "scrub_error",
122 "strip_control_chars",
123 "CLIENT_SAFE_DOWNLOAD_MESSAGES",
124 "client_safe_download_message",
125 "sanitize_filename",
126 "UnsafeFilenameError",
127]
130def __getattr__(name: str):
131 """Lazily provide file-integrity exports without a package import cycle."""
132 if name == "FileIntegrityManager":
133 from .file_integrity.integrity_manager import FileIntegrityManager
135 return FileIntegrityManager
136 if name == "FAISSIndexVerifier":
137 from .file_integrity.verifiers.faiss_verifier import FAISSIndexVerifier
139 return FAISSIndexVerifier
140 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
143# Install the process-wide socket.connect audit hook after all imports
144# resolve. The hook is a no-op until a worker thread calls
145# ``set_active_context(ctx)`` — importing this module has zero
146# behavioral effect on code that does not opt in. Idempotent: subsequent
147# imports do not re-install.
148install_audit_hook()