Coverage for src/local_deep_research/security/__init__.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-06 15:42 +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 validate_url, 

34) 

35from .url_validator import URLValidator 

36from .account_lockout import AccountLockoutManager, get_account_lockout_manager 

37from .password_validator import PasswordValidator 

38from .log_sanitizer import ( 

39 redact_secrets, 

40 sanitize_error_details, 

41 sanitize_error_for_client, 

42 sanitize_error_message, 

43 sanitize_for_log, 

44 scrub_error, 

45 strip_control_chars, 

46) 

47from .filename_sanitizer import sanitize_filename, UnsafeFilenameError 

48from .module_whitelist import ( 

49 get_safe_module_class, 

50 ModuleNotAllowedError, 

51 ALLOWED_MODULES, 

52) 

53 

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

55try: 

56 from .path_validator import PathValidator 

57 

58 _has_path_validator = True 

59except ImportError: 

60 PathValidator = None # type: ignore 

61 _has_path_validator = False 

62 

63# FileUploadValidator requires pdfplumber, import conditionally 

64try: 

65 from .file_upload_validator import FileUploadValidator 

66 

67 _has_file_upload_validator = True 

68except ImportError: 

69 FileUploadValidator = None # type: ignore 

70 _has_file_upload_validator = False 

71 

72__all__ = [ 

73 "PathValidator", 

74 "DataSanitizer", 

75 "active_egress_context", 

76 "clear_active_context", 

77 "get_active_context", 

78 "install_audit_hook", 

79 "is_audit_hook_installed", 

80 "set_active_context", 

81 "sanitize_data", 

82 "redact_data", 

83 "filter_research_metadata", 

84 "strip_settings_snapshot", 

85 "get_security_default", 

86 "FileIntegrityManager", 

87 "FAISSIndexVerifier", 

88 "FileUploadValidator", 

89 "NotificationURLValidator", 

90 "NotificationURLValidationError", 

91 "URLValidator", 

92 "safe_get", 

93 "safe_post", 

94 "SafeSession", 

95 "validate_url", 

96 "get_safe_url", 

97 "is_ip_blocked", 

98 "assert_base_url_safe", 

99 "redact_url_for_log", 

100 "get_safe_module_class", 

101 "ModuleNotAllowedError", 

102 "ALLOWED_MODULES", 

103 "AccountLockoutManager", 

104 "get_account_lockout_manager", 

105 "PasswordValidator", 

106 "redact_secrets", 

107 "sanitize_error_details", 

108 "sanitize_error_for_client", 

109 "sanitize_error_message", 

110 "sanitize_for_log", 

111 "scrub_error", 

112 "strip_control_chars", 

113 "sanitize_filename", 

114 "UnsafeFilenameError", 

115] 

116 

117 

118def __getattr__(name: str): 

119 """Lazily provide file-integrity exports without a package import cycle.""" 

120 if name == "FileIntegrityManager": 

121 from .file_integrity.integrity_manager import FileIntegrityManager 

122 

123 return FileIntegrityManager 

124 if name == "FAISSIndexVerifier": 

125 from .file_integrity.verifiers.faiss_verifier import FAISSIndexVerifier 

126 

127 return FAISSIndexVerifier 

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

129 

130 

131# Install the process-wide socket.connect audit hook after all imports 

132# resolve. The hook is a no-op until a worker thread calls 

133# ``set_active_context(ctx)`` — importing this module has zero 

134# behavioral effect on code that does not opt in. Idempotent: subsequent 

135# imports do not re-install. 

136install_audit_hook()