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

1"""Security utilities for Local Deep Research.""" 

2 

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 

19 

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) 

60 

61# PathValidator requires werkzeug (Flask dependency), import conditionally 

62try: 

63 from .path_validator import PathValidator 

64 

65 _has_path_validator = True 

66except ImportError: 

67 PathValidator = None # type: ignore 

68 _has_path_validator = False 

69 

70# FileUploadValidator requires pdfplumber, import conditionally 

71try: 

72 from .file_upload_validator import FileUploadValidator 

73 

74 _has_file_upload_validator = True 

75except ImportError: 

76 FileUploadValidator = None # type: ignore 

77 _has_file_upload_validator = False 

78 

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] 

128 

129 

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 

134 

135 return FileIntegrityManager 

136 if name == "FAISSIndexVerifier": 

137 from .file_integrity.verifiers.faiss_verifier import FAISSIndexVerifier 

138 

139 return FAISSIndexVerifier 

140 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 

141 

142 

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