no-cluely-detector 0.0.1__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,412 @@
|
|
1
|
+
"""
|
2
|
+
No-Cluely Detector - Python Library
|
3
|
+
|
4
|
+
Detect Cluely employee monitoring software and its evasion techniques.
|
5
|
+
This library provides Python bindings to a fast Rust-based detection engine.
|
6
|
+
|
7
|
+
Basic Usage:
|
8
|
+
>>> from no_cluely_detector import ClueLyDetector
|
9
|
+
>>> if ClueLyDetector.is_cluely_running():
|
10
|
+
... print("⚠️ Employee monitoring detected!")
|
11
|
+
|
12
|
+
Advanced Usage:
|
13
|
+
>>> detection = ClueLyDetector.detect_cluely_detailed()
|
14
|
+
>>> print(f"Severity: {detection.severity_level}")
|
15
|
+
>>> print(f"Techniques: {', '.join(detection.evasion_techniques)}")
|
16
|
+
"""
|
17
|
+
|
18
|
+
import ctypes
|
19
|
+
import platform
|
20
|
+
import os
|
21
|
+
from pathlib import Path
|
22
|
+
from typing import List, Optional, NamedTuple, Union
|
23
|
+
from dataclasses import dataclass
|
24
|
+
from datetime import datetime
|
25
|
+
import threading
|
26
|
+
import time
|
27
|
+
|
28
|
+
# Ensure we're on macOS
|
29
|
+
if platform.system() != "Darwin":
|
30
|
+
raise RuntimeError("Cluely Detector is only supported on macOS")
|
31
|
+
|
32
|
+
|
33
|
+
# Locate the dynamic library
|
34
|
+
def _find_library() -> str:
|
35
|
+
"""Find the Rust dynamic library."""
|
36
|
+
# Try relative to this package first
|
37
|
+
current_dir = Path(__file__).parent
|
38
|
+
lib_paths = [
|
39
|
+
current_dir
|
40
|
+
/ ".."
|
41
|
+
/ ".."
|
42
|
+
/ ".."
|
43
|
+
/ "target"
|
44
|
+
/ "release"
|
45
|
+
/ "libno_cluely_driver.dylib",
|
46
|
+
current_dir / ".." / ".." / "target" / "release" / "libno_cluely_driver.dylib",
|
47
|
+
current_dir / "libno_cluely_driver.dylib",
|
48
|
+
Path("/usr/local/lib/libno_cluely_driver.dylib"),
|
49
|
+
Path("/opt/homebrew/lib/libno_cluely_driver.dylib"),
|
50
|
+
]
|
51
|
+
|
52
|
+
for lib_path in lib_paths:
|
53
|
+
lib_path = lib_path.resolve()
|
54
|
+
if lib_path.exists():
|
55
|
+
return str(lib_path)
|
56
|
+
|
57
|
+
raise FileNotFoundError(
|
58
|
+
"Could not find libno_cluely_driver.dylib. "
|
59
|
+
"Please ensure the Rust library is built: cargo build --lib --release"
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
# Load the library
|
64
|
+
_lib_path = _find_library()
|
65
|
+
_lib = ctypes.CDLL(_lib_path)
|
66
|
+
|
67
|
+
|
68
|
+
# Define C structures
|
69
|
+
class _ClueLyDetectionResult(ctypes.Structure):
|
70
|
+
"""C structure for detection results."""
|
71
|
+
|
72
|
+
_fields_ = [
|
73
|
+
("is_detected", ctypes.c_bool),
|
74
|
+
("window_count", ctypes.c_uint32),
|
75
|
+
("screen_capture_evasion_count", ctypes.c_uint32),
|
76
|
+
("elevated_layer_count", ctypes.c_uint32),
|
77
|
+
("max_layer_detected", ctypes.c_int32),
|
78
|
+
]
|
79
|
+
|
80
|
+
|
81
|
+
# Define function signatures
|
82
|
+
_lib.is_cluely_running.argtypes = []
|
83
|
+
_lib.is_cluely_running.restype = ctypes.c_int
|
84
|
+
|
85
|
+
_lib.detect_cluely.argtypes = []
|
86
|
+
_lib.detect_cluely.restype = _ClueLyDetectionResult
|
87
|
+
|
88
|
+
_lib.get_cluely_report.argtypes = []
|
89
|
+
_lib.get_cluely_report.restype = ctypes.c_char_p
|
90
|
+
|
91
|
+
_lib.free_cluely_report.argtypes = [ctypes.c_char_p]
|
92
|
+
_lib.free_cluely_report.restype = None
|
93
|
+
|
94
|
+
_lib.get_cluely_window_count.argtypes = []
|
95
|
+
_lib.get_cluely_window_count.restype = ctypes.c_uint32
|
96
|
+
|
97
|
+
|
98
|
+
@dataclass(frozen=True)
|
99
|
+
class ClueLyDetection:
|
100
|
+
"""
|
101
|
+
Detailed information about Cluely detection.
|
102
|
+
|
103
|
+
Attributes:
|
104
|
+
is_detected: True if Cluely monitoring software is detected
|
105
|
+
window_count: Total number of Cluely windows found
|
106
|
+
screen_capture_evasion_count: Number of windows using screen capture evasion
|
107
|
+
elevated_layer_count: Number of windows using elevated layer positioning
|
108
|
+
max_layer_detected: Highest layer number detected
|
109
|
+
severity_level: Human-readable severity level ('None', 'Low', 'Medium', 'High')
|
110
|
+
evasion_techniques: List of detected evasion techniques
|
111
|
+
report: Detailed text report
|
112
|
+
timestamp: Detection timestamp
|
113
|
+
"""
|
114
|
+
|
115
|
+
is_detected: bool
|
116
|
+
window_count: int
|
117
|
+
screen_capture_evasion_count: int
|
118
|
+
elevated_layer_count: int
|
119
|
+
max_layer_detected: int
|
120
|
+
severity_level: str
|
121
|
+
evasion_techniques: List[str]
|
122
|
+
report: str
|
123
|
+
timestamp: datetime
|
124
|
+
|
125
|
+
|
126
|
+
class ClueLyDetector:
|
127
|
+
"""
|
128
|
+
Cluely Detection Library
|
129
|
+
|
130
|
+
Detects Cluely employee monitoring software and its specific evasion techniques.
|
131
|
+
All methods are thread-safe and can be called from any thread.
|
132
|
+
"""
|
133
|
+
|
134
|
+
@staticmethod
|
135
|
+
def is_cluely_running() -> bool:
|
136
|
+
"""
|
137
|
+
Simple check if Cluely monitoring software is running.
|
138
|
+
|
139
|
+
Returns:
|
140
|
+
True if Cluely is detected, False otherwise
|
141
|
+
|
142
|
+
Example:
|
143
|
+
>>> if ClueLyDetector.is_cluely_running():
|
144
|
+
... print("⚠️ Employee monitoring detected!")
|
145
|
+
"""
|
146
|
+
return _lib.is_cluely_running() != 0
|
147
|
+
|
148
|
+
@staticmethod
|
149
|
+
def detect_cluely() -> tuple[bool, int]:
|
150
|
+
"""
|
151
|
+
Basic detection with window count.
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
Tuple of (is_detected, window_count)
|
155
|
+
|
156
|
+
Example:
|
157
|
+
>>> is_detected, window_count = ClueLyDetector.detect_cluely()
|
158
|
+
>>> print(f"Detected: {is_detected}, Windows: {window_count}")
|
159
|
+
"""
|
160
|
+
result = _lib.detect_cluely()
|
161
|
+
return result.is_detected, result.window_count
|
162
|
+
|
163
|
+
@staticmethod
|
164
|
+
def detect_cluely_detailed() -> ClueLyDetection:
|
165
|
+
"""
|
166
|
+
Detailed detection with evasion technique analysis.
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
ClueLyDetection object with comprehensive information
|
170
|
+
|
171
|
+
Example:
|
172
|
+
>>> detection = ClueLyDetector.detect_cluely_detailed()
|
173
|
+
>>> if detection.is_detected:
|
174
|
+
... print(f"Severity: {detection.severity_level}")
|
175
|
+
... print(f"Techniques: {', '.join(detection.evasion_techniques)}")
|
176
|
+
"""
|
177
|
+
result = _lib.detect_cluely()
|
178
|
+
|
179
|
+
# Get the detailed report
|
180
|
+
report_ptr = _lib.get_cluely_report()
|
181
|
+
try:
|
182
|
+
if report_ptr:
|
183
|
+
report = report_ptr.decode("utf-8")
|
184
|
+
else:
|
185
|
+
report = "No detailed report available"
|
186
|
+
finally:
|
187
|
+
if report_ptr:
|
188
|
+
_lib.free_cluely_report(report_ptr)
|
189
|
+
|
190
|
+
# Calculate severity level
|
191
|
+
technique_count = 0
|
192
|
+
if result.screen_capture_evasion_count > 0:
|
193
|
+
technique_count += 1
|
194
|
+
if result.elevated_layer_count > 0:
|
195
|
+
technique_count += 1
|
196
|
+
|
197
|
+
if not result.is_detected:
|
198
|
+
severity_level = "None"
|
199
|
+
elif technique_count == 0:
|
200
|
+
severity_level = "Low"
|
201
|
+
elif technique_count == 1:
|
202
|
+
severity_level = "Medium"
|
203
|
+
else:
|
204
|
+
severity_level = "High"
|
205
|
+
|
206
|
+
# Build evasion techniques list
|
207
|
+
evasion_techniques = []
|
208
|
+
if result.screen_capture_evasion_count > 0:
|
209
|
+
evasion_techniques.append(
|
210
|
+
f"Screen capture evasion ({result.screen_capture_evasion_count} windows)"
|
211
|
+
)
|
212
|
+
if result.elevated_layer_count > 0:
|
213
|
+
evasion_techniques.append(
|
214
|
+
f"Elevated layer positioning ({result.elevated_layer_count} windows)"
|
215
|
+
)
|
216
|
+
|
217
|
+
return ClueLyDetection(
|
218
|
+
is_detected=result.is_detected,
|
219
|
+
window_count=result.window_count,
|
220
|
+
screen_capture_evasion_count=result.screen_capture_evasion_count,
|
221
|
+
elevated_layer_count=result.elevated_layer_count,
|
222
|
+
max_layer_detected=result.max_layer_detected,
|
223
|
+
severity_level=severity_level,
|
224
|
+
evasion_techniques=evasion_techniques,
|
225
|
+
report=report,
|
226
|
+
timestamp=datetime.now(),
|
227
|
+
)
|
228
|
+
|
229
|
+
@staticmethod
|
230
|
+
def get_cluely_report() -> str:
|
231
|
+
"""
|
232
|
+
Get detailed text report of detection results.
|
233
|
+
|
234
|
+
Returns:
|
235
|
+
Detailed text report explaining what was found
|
236
|
+
|
237
|
+
Example:
|
238
|
+
>>> report = ClueLyDetector.get_cluely_report()
|
239
|
+
>>> print(report)
|
240
|
+
"""
|
241
|
+
report_ptr = _lib.get_cluely_report()
|
242
|
+
try:
|
243
|
+
if report_ptr:
|
244
|
+
return report_ptr.decode("utf-8")
|
245
|
+
else:
|
246
|
+
return "No report available"
|
247
|
+
finally:
|
248
|
+
if report_ptr:
|
249
|
+
_lib.free_cluely_report(report_ptr)
|
250
|
+
|
251
|
+
@staticmethod
|
252
|
+
def get_cluely_window_count() -> int:
|
253
|
+
"""
|
254
|
+
Get the number of Cluely windows detected.
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
Number of Cluely windows
|
258
|
+
|
259
|
+
Example:
|
260
|
+
>>> count = ClueLyDetector.get_cluely_window_count()
|
261
|
+
>>> print(f"Found {count} Cluely windows")
|
262
|
+
"""
|
263
|
+
return _lib.get_cluely_window_count()
|
264
|
+
|
265
|
+
|
266
|
+
class ClueLyMonitor:
|
267
|
+
"""
|
268
|
+
Monitor for Cluely detection changes with event callbacks.
|
269
|
+
|
270
|
+
This class provides continuous monitoring with callbacks for detection events.
|
271
|
+
It runs in a separate thread to avoid blocking the main application.
|
272
|
+
"""
|
273
|
+
|
274
|
+
def __init__(self):
|
275
|
+
self._running = False
|
276
|
+
self._thread: Optional[threading.Thread] = None
|
277
|
+
self._last_detection: Optional[ClueLyDetection] = None
|
278
|
+
self._callbacks = {}
|
279
|
+
|
280
|
+
def start(
|
281
|
+
self,
|
282
|
+
interval: float = 10.0,
|
283
|
+
on_detected: Optional[callable] = None,
|
284
|
+
on_removed: Optional[callable] = None,
|
285
|
+
on_change: Optional[callable] = None,
|
286
|
+
) -> None:
|
287
|
+
"""
|
288
|
+
Start monitoring for Cluely detection changes.
|
289
|
+
|
290
|
+
Args:
|
291
|
+
interval: Check interval in seconds (default: 10.0)
|
292
|
+
on_detected: Callback when Cluely is first detected
|
293
|
+
on_removed: Callback when Cluely monitoring stops
|
294
|
+
on_change: Callback on every detection check
|
295
|
+
|
296
|
+
Example:
|
297
|
+
>>> def alert(detection):
|
298
|
+
... print(f"🚨 Cluely detected! Severity: {detection.severity_level}")
|
299
|
+
>>>
|
300
|
+
>>> monitor = ClueLyMonitor()
|
301
|
+
>>> monitor.start(interval=5.0, on_detected=alert)
|
302
|
+
"""
|
303
|
+
if self._running:
|
304
|
+
raise RuntimeError("Monitor is already running")
|
305
|
+
|
306
|
+
self._callbacks = {
|
307
|
+
"on_detected": on_detected,
|
308
|
+
"on_removed": on_removed,
|
309
|
+
"on_change": on_change,
|
310
|
+
}
|
311
|
+
|
312
|
+
self._running = True
|
313
|
+
self._thread = threading.Thread(target=self._monitor_loop, args=(interval,))
|
314
|
+
self._thread.daemon = True
|
315
|
+
self._thread.start()
|
316
|
+
|
317
|
+
def stop(self) -> None:
|
318
|
+
"""
|
319
|
+
Stop monitoring.
|
320
|
+
|
321
|
+
Example:
|
322
|
+
>>> monitor.stop()
|
323
|
+
"""
|
324
|
+
self._running = False
|
325
|
+
if self._thread:
|
326
|
+
self._thread.join(timeout=5.0)
|
327
|
+
self._thread = None
|
328
|
+
|
329
|
+
def get_last_detection(self) -> Optional[ClueLyDetection]:
|
330
|
+
"""
|
331
|
+
Get the last detection result.
|
332
|
+
|
333
|
+
Returns:
|
334
|
+
Last ClueLyDetection result or None if no detection has been performed
|
335
|
+
|
336
|
+
Example:
|
337
|
+
>>> last = monitor.get_last_detection()
|
338
|
+
>>> if last and last.is_detected:
|
339
|
+
... print("Still detected")
|
340
|
+
"""
|
341
|
+
return self._last_detection
|
342
|
+
|
343
|
+
def _monitor_loop(self, interval: float) -> None:
|
344
|
+
"""Internal monitoring loop."""
|
345
|
+
while self._running:
|
346
|
+
try:
|
347
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
348
|
+
|
349
|
+
# Check for state changes
|
350
|
+
if self._last_detection:
|
351
|
+
if detection.is_detected and not self._last_detection.is_detected:
|
352
|
+
# Just detected
|
353
|
+
if self._callbacks["on_detected"]:
|
354
|
+
self._callbacks["on_detected"](detection)
|
355
|
+
elif not detection.is_detected and self._last_detection.is_detected:
|
356
|
+
# Just stopped
|
357
|
+
if self._callbacks["on_removed"]:
|
358
|
+
self._callbacks["on_removed"]()
|
359
|
+
else:
|
360
|
+
# First check
|
361
|
+
if detection.is_detected and self._callbacks["on_detected"]:
|
362
|
+
self._callbacks["on_detected"](detection)
|
363
|
+
|
364
|
+
# Always call on_change if provided
|
365
|
+
if self._callbacks["on_change"]:
|
366
|
+
self._callbacks["on_change"](detection)
|
367
|
+
|
368
|
+
self._last_detection = detection
|
369
|
+
|
370
|
+
except Exception as e:
|
371
|
+
# Log error but continue monitoring
|
372
|
+
print(f"Monitoring error: {e}")
|
373
|
+
|
374
|
+
if self._running:
|
375
|
+
time.sleep(interval)
|
376
|
+
|
377
|
+
|
378
|
+
# Convenience functions for quick access
|
379
|
+
def is_cluely_running() -> bool:
|
380
|
+
"""Convenience function: Check if Cluely is running."""
|
381
|
+
return ClueLyDetector.is_cluely_running()
|
382
|
+
|
383
|
+
|
384
|
+
def detect_cluely() -> tuple[bool, int]:
|
385
|
+
"""Convenience function: Basic detection with window count."""
|
386
|
+
return ClueLyDetector.detect_cluely()
|
387
|
+
|
388
|
+
|
389
|
+
def detect_cluely_detailed() -> ClueLyDetection:
|
390
|
+
"""Convenience function: Detailed detection with evasion analysis."""
|
391
|
+
return ClueLyDetector.detect_cluely_detailed()
|
392
|
+
|
393
|
+
|
394
|
+
def get_cluely_report() -> str:
|
395
|
+
"""Convenience function: Get detailed text report."""
|
396
|
+
return ClueLyDetector.get_cluely_report()
|
397
|
+
|
398
|
+
|
399
|
+
# Export public API
|
400
|
+
__all__ = [
|
401
|
+
"ClueLyDetector",
|
402
|
+
"ClueLyDetection",
|
403
|
+
"ClueLyMonitor",
|
404
|
+
"is_cluely_running",
|
405
|
+
"detect_cluely",
|
406
|
+
"detect_cluely_detailed",
|
407
|
+
"get_cluely_report",
|
408
|
+
]
|
409
|
+
|
410
|
+
# Version information
|
411
|
+
__version__ = "1.0.0"
|
412
|
+
__author__ = "No-Cluely Team"
|
@@ -0,0 +1 @@
|
|
1
|
+
# This file indicates that this package supports type hints (PEP 561)
|
@@ -0,0 +1,586 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: no-cluely-detector
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: Detect Cluely employee monitoring software and its evasion techniques
|
5
|
+
Author: No-Cluely Team
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/your-org/no-cluely-driver
|
8
|
+
Project-URL: Bug Reports, https://github.com/your-org/no-cluely-driver/issues
|
9
|
+
Project-URL: Source, https://github.com/your-org/no-cluely-driver
|
10
|
+
Project-URL: Documentation, https://github.com/your-org/no-cluely-driver#readme
|
11
|
+
Keywords: privacy,monitoring,detection,cluely,employee,screen-sharing,evasion,macos
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
16
|
+
Classifier: Operating System :: MacOS
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
23
|
+
Classifier: Topic :: Security
|
24
|
+
Classifier: Topic :: System :: Monitoring
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
26
|
+
Requires-Python: >=3.8
|
27
|
+
Description-Content-Type: text/markdown
|
28
|
+
Requires-Dist: typing-extensions>=4.0.0
|
29
|
+
Provides-Extra: dev
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
32
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
34
|
+
Requires-Dist: flake8>=5.0.0; extra == "dev"
|
35
|
+
|
36
|
+
# no-cluely-detector 🎯
|
37
|
+
|
38
|
+
Python library for detecting Cluely employee monitoring software and its evasion techniques.
|
39
|
+
|
40
|
+
## Features
|
41
|
+
|
42
|
+
- **Fast Detection**: Built on a high-performance Rust engine
|
43
|
+
- **Thread-Safe**: Use from any thread in your Python application
|
44
|
+
- **Type-Safe**: Full type hints support with mypy compatibility
|
45
|
+
- **Event Monitoring**: Real-time callbacks for detection changes
|
46
|
+
- **Detailed Analysis**: Comprehensive evasion technique reporting
|
47
|
+
- **Easy Integration**: Simple API for any Python project
|
48
|
+
|
49
|
+
## Installation
|
50
|
+
|
51
|
+
```bash
|
52
|
+
pip install no-cluely-detector
|
53
|
+
```
|
54
|
+
|
55
|
+
### Requirements
|
56
|
+
|
57
|
+
- **Python**: 3.8 or later
|
58
|
+
- **Platform**: macOS only (Cluely is macOS-specific)
|
59
|
+
- **Architecture**: x64 (Intel/Apple Silicon)
|
60
|
+
|
61
|
+
## Quick Start
|
62
|
+
|
63
|
+
### Simple Detection
|
64
|
+
```python
|
65
|
+
from no_cluely_detector import ClueLyDetector
|
66
|
+
|
67
|
+
# Quick check
|
68
|
+
if ClueLyDetector.is_cluely_running():
|
69
|
+
print("⚠️ Employee monitoring detected!")
|
70
|
+
else:
|
71
|
+
print("✅ No monitoring software found")
|
72
|
+
```
|
73
|
+
|
74
|
+
### Detailed Analysis
|
75
|
+
```python
|
76
|
+
from no_cluely_detector import ClueLyDetector
|
77
|
+
|
78
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
79
|
+
|
80
|
+
if detection.is_detected:
|
81
|
+
print(f"🚨 Cluely Detected!")
|
82
|
+
print(f" Severity: {detection.severity_level}")
|
83
|
+
print(f" Windows: {detection.window_count}")
|
84
|
+
print(f" Techniques: {', '.join(detection.evasion_techniques)}")
|
85
|
+
else:
|
86
|
+
print("✅ System clean")
|
87
|
+
```
|
88
|
+
|
89
|
+
### Real-time Monitoring
|
90
|
+
```python
|
91
|
+
from no_cluely_detector import ClueLyMonitor
|
92
|
+
|
93
|
+
def on_detected(detection):
|
94
|
+
print(f"🚨 Monitoring started! Severity: {detection.severity_level}")
|
95
|
+
|
96
|
+
def on_removed():
|
97
|
+
print("✅ Monitoring stopped")
|
98
|
+
|
99
|
+
monitor = ClueLyMonitor()
|
100
|
+
monitor.start(
|
101
|
+
interval=5.0, # Check every 5 seconds
|
102
|
+
on_detected=on_detected,
|
103
|
+
on_removed=on_removed
|
104
|
+
)
|
105
|
+
|
106
|
+
# Your application continues...
|
107
|
+
# Call monitor.stop() when done
|
108
|
+
```
|
109
|
+
|
110
|
+
## API Reference
|
111
|
+
|
112
|
+
### ClueLyDetector
|
113
|
+
|
114
|
+
Main detection class with static methods for detecting Cluely monitoring software.
|
115
|
+
|
116
|
+
#### Methods
|
117
|
+
|
118
|
+
##### `ClueLyDetector.is_cluely_running() -> bool`
|
119
|
+
Simple boolean check for Cluely presence.
|
120
|
+
|
121
|
+
```python
|
122
|
+
detected = ClueLyDetector.is_cluely_running()
|
123
|
+
```
|
124
|
+
|
125
|
+
##### `ClueLyDetector.detect_cluely() -> tuple[bool, int]`
|
126
|
+
Basic detection returning status and window count.
|
127
|
+
|
128
|
+
```python
|
129
|
+
is_detected, window_count = ClueLyDetector.detect_cluely()
|
130
|
+
print(f"Detected: {is_detected}, Windows: {window_count}")
|
131
|
+
```
|
132
|
+
|
133
|
+
##### `ClueLyDetector.detect_cluely_detailed() -> ClueLyDetection`
|
134
|
+
Comprehensive detection with full analysis.
|
135
|
+
|
136
|
+
```python
|
137
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
138
|
+
# Returns ClueLyDetection object with all details
|
139
|
+
```
|
140
|
+
|
141
|
+
##### `ClueLyDetector.get_cluely_report() -> str`
|
142
|
+
Detailed text report of findings.
|
143
|
+
|
144
|
+
```python
|
145
|
+
report = ClueLyDetector.get_cluely_report()
|
146
|
+
print(report)
|
147
|
+
```
|
148
|
+
|
149
|
+
##### `ClueLyDetector.get_cluely_window_count() -> int`
|
150
|
+
Number of Cluely windows detected.
|
151
|
+
|
152
|
+
```python
|
153
|
+
count = ClueLyDetector.get_cluely_window_count()
|
154
|
+
```
|
155
|
+
|
156
|
+
### ClueLyDetection
|
157
|
+
|
158
|
+
Data class containing detailed detection information.
|
159
|
+
|
160
|
+
#### Attributes
|
161
|
+
|
162
|
+
- `is_detected: bool` - True if Cluely is detected
|
163
|
+
- `window_count: int` - Total number of Cluely windows
|
164
|
+
- `screen_capture_evasion_count: int` - Windows using screen capture evasion
|
165
|
+
- `elevated_layer_count: int` - Windows using elevated layer positioning
|
166
|
+
- `max_layer_detected: int` - Highest layer number found
|
167
|
+
- `severity_level: str` - Severity ('None', 'Low', 'Medium', 'High')
|
168
|
+
- `evasion_techniques: List[str]` - List of detected techniques
|
169
|
+
- `report: str` - Detailed text report
|
170
|
+
- `timestamp: datetime` - Detection timestamp
|
171
|
+
|
172
|
+
```python
|
173
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
174
|
+
|
175
|
+
print(f"Detected: {detection.is_detected}")
|
176
|
+
print(f"Severity: {detection.severity_level}")
|
177
|
+
print(f"Evasion Techniques:")
|
178
|
+
for technique in detection.evasion_techniques:
|
179
|
+
print(f" - {technique}")
|
180
|
+
```
|
181
|
+
|
182
|
+
### ClueLyMonitor
|
183
|
+
|
184
|
+
Event-based monitoring for detection changes.
|
185
|
+
|
186
|
+
#### Methods
|
187
|
+
|
188
|
+
##### `start(interval=10.0, on_detected=None, on_removed=None, on_change=None)`
|
189
|
+
Start monitoring with event callbacks.
|
190
|
+
|
191
|
+
- `interval: float` - Check interval in seconds
|
192
|
+
- `on_detected: callable` - Called when Cluely is first detected
|
193
|
+
- `on_removed: callable` - Called when Cluely monitoring stops
|
194
|
+
- `on_change: callable` - Called on every check
|
195
|
+
|
196
|
+
##### `stop()`
|
197
|
+
Stop monitoring and cleanup resources.
|
198
|
+
|
199
|
+
##### `get_last_detection() -> Optional[ClueLyDetection]`
|
200
|
+
Get the most recent detection result.
|
201
|
+
|
202
|
+
```python
|
203
|
+
monitor = ClueLyMonitor()
|
204
|
+
|
205
|
+
def alert(detection):
|
206
|
+
send_email(f"Security Alert: {detection.severity_level}")
|
207
|
+
|
208
|
+
monitor.start(interval=30.0, on_detected=alert)
|
209
|
+
```
|
210
|
+
|
211
|
+
## Usage Examples
|
212
|
+
|
213
|
+
### Flask Web Application
|
214
|
+
```python
|
215
|
+
from flask import Flask, jsonify
|
216
|
+
from no_cluely_detector import ClueLyDetector
|
217
|
+
|
218
|
+
app = Flask(__name__)
|
219
|
+
|
220
|
+
@app.route('/api/security/check')
|
221
|
+
def security_check():
|
222
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
223
|
+
|
224
|
+
return jsonify({
|
225
|
+
'monitoring_detected': detection.is_detected,
|
226
|
+
'severity': detection.severity_level,
|
227
|
+
'evasion_techniques': detection.evasion_techniques,
|
228
|
+
'window_count': detection.window_count,
|
229
|
+
'timestamp': detection.timestamp.isoformat()
|
230
|
+
})
|
231
|
+
|
232
|
+
if __name__ == '__main__':
|
233
|
+
app.run(debug=True)
|
234
|
+
```
|
235
|
+
|
236
|
+
### Django Integration
|
237
|
+
```python
|
238
|
+
# views.py
|
239
|
+
from django.http import JsonResponse
|
240
|
+
from django.views.decorators.http import require_http_methods
|
241
|
+
from no_cluely_detector import ClueLyDetector
|
242
|
+
|
243
|
+
@require_http_methods(["GET"])
|
244
|
+
def check_monitoring(request):
|
245
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
246
|
+
|
247
|
+
return JsonResponse({
|
248
|
+
'detected': detection.is_detected,
|
249
|
+
'severity': detection.severity_level,
|
250
|
+
'techniques': detection.evasion_techniques,
|
251
|
+
'report': detection.report
|
252
|
+
})
|
253
|
+
|
254
|
+
# urls.py
|
255
|
+
from django.urls import path
|
256
|
+
from . import views
|
257
|
+
|
258
|
+
urlpatterns = [
|
259
|
+
path('security/check/', views.check_monitoring, name='check_monitoring'),
|
260
|
+
]
|
261
|
+
```
|
262
|
+
|
263
|
+
### FastAPI Application
|
264
|
+
```python
|
265
|
+
from fastapi import FastAPI
|
266
|
+
from pydantic import BaseModel
|
267
|
+
from typing import List
|
268
|
+
from datetime import datetime
|
269
|
+
from no_cluely_detector import ClueLyDetector, ClueLyDetection
|
270
|
+
|
271
|
+
app = FastAPI(title="Security Monitor API")
|
272
|
+
|
273
|
+
class SecurityStatus(BaseModel):
|
274
|
+
detected: bool
|
275
|
+
severity: str
|
276
|
+
evasion_techniques: List[str]
|
277
|
+
window_count: int
|
278
|
+
timestamp: datetime
|
279
|
+
|
280
|
+
@app.get("/security/check", response_model=SecurityStatus)
|
281
|
+
async def check_security():
|
282
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
283
|
+
|
284
|
+
return SecurityStatus(
|
285
|
+
detected=detection.is_detected,
|
286
|
+
severity=detection.severity_level,
|
287
|
+
evasion_techniques=detection.evasion_techniques,
|
288
|
+
window_count=detection.window_count,
|
289
|
+
timestamp=detection.timestamp
|
290
|
+
)
|
291
|
+
```
|
292
|
+
|
293
|
+
### Background Monitoring Service
|
294
|
+
```python
|
295
|
+
import time
|
296
|
+
import logging
|
297
|
+
from no_cluely_detector import ClueLyMonitor
|
298
|
+
from email.mime.text import MimeText
|
299
|
+
import smtplib
|
300
|
+
|
301
|
+
logging.basicConfig(level=logging.INFO)
|
302
|
+
logger = logging.getLogger(__name__)
|
303
|
+
|
304
|
+
class SecurityMonitorService:
|
305
|
+
def __init__(self, email_alerts=True):
|
306
|
+
self.monitor = ClueLyMonitor()
|
307
|
+
self.email_alerts = email_alerts
|
308
|
+
|
309
|
+
def start(self):
|
310
|
+
logger.info("Starting security monitoring service...")
|
311
|
+
|
312
|
+
self.monitor.start(
|
313
|
+
interval=60.0, # Check every minute
|
314
|
+
on_detected=self._on_threat_detected,
|
315
|
+
on_removed=self._on_threat_removed,
|
316
|
+
on_change=self._on_status_change
|
317
|
+
)
|
318
|
+
|
319
|
+
def stop(self):
|
320
|
+
logger.info("Stopping security monitoring service...")
|
321
|
+
self.monitor.stop()
|
322
|
+
|
323
|
+
def _on_threat_detected(self, detection):
|
324
|
+
logger.warning(f"🚨 SECURITY THREAT DETECTED - Severity: {detection.severity_level}")
|
325
|
+
|
326
|
+
if self.email_alerts:
|
327
|
+
self._send_alert_email(detection)
|
328
|
+
|
329
|
+
# Log to security system
|
330
|
+
self._log_security_event("THREAT_DETECTED", detection)
|
331
|
+
|
332
|
+
def _on_threat_removed(self):
|
333
|
+
logger.info("✅ Security threat removed")
|
334
|
+
self._log_security_event("THREAT_REMOVED", None)
|
335
|
+
|
336
|
+
def _on_status_change(self, detection):
|
337
|
+
# Log periodic status (every 10 minutes)
|
338
|
+
if int(time.time()) % 600 == 0:
|
339
|
+
status = "DETECTED" if detection.is_detected else "CLEAN"
|
340
|
+
logger.info(f"Security status: {status}")
|
341
|
+
|
342
|
+
def _send_alert_email(self, detection):
|
343
|
+
# Implementation depends on your email setup
|
344
|
+
subject = f"Security Alert: Employee Monitoring Detected ({detection.severity_level})"
|
345
|
+
body = f"""
|
346
|
+
Security Alert: Cluely employee monitoring software detected
|
347
|
+
|
348
|
+
Severity: {detection.severity_level}
|
349
|
+
Evasion Techniques: {', '.join(detection.evasion_techniques)}
|
350
|
+
Window Count: {detection.window_count}
|
351
|
+
Time: {detection.timestamp}
|
352
|
+
|
353
|
+
Report:
|
354
|
+
{detection.report}
|
355
|
+
"""
|
356
|
+
# Send email implementation here
|
357
|
+
|
358
|
+
def _log_security_event(self, event_type, detection):
|
359
|
+
# Log to your security information system
|
360
|
+
event_data = {
|
361
|
+
'event_type': event_type,
|
362
|
+
'timestamp': time.time(),
|
363
|
+
'detection': detection.__dict__ if detection else None
|
364
|
+
}
|
365
|
+
logger.info(f"Security event: {event_data}")
|
366
|
+
|
367
|
+
# Usage
|
368
|
+
if __name__ == "__main__":
|
369
|
+
service = SecurityMonitorService()
|
370
|
+
service.start()
|
371
|
+
|
372
|
+
try:
|
373
|
+
# Keep the service running
|
374
|
+
while True:
|
375
|
+
time.sleep(60)
|
376
|
+
except KeyboardInterrupt:
|
377
|
+
service.stop()
|
378
|
+
```
|
379
|
+
|
380
|
+
### Jupyter Notebook Integration
|
381
|
+
```python
|
382
|
+
# Cell 1: Setup
|
383
|
+
from no_cluely_detector import ClueLyDetector, ClueLyMonitor
|
384
|
+
import pandas as pd
|
385
|
+
from datetime import datetime
|
386
|
+
import matplotlib.pyplot as plt
|
387
|
+
|
388
|
+
# Cell 2: Single Check
|
389
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
390
|
+
|
391
|
+
print(f"🔍 Security Check Results")
|
392
|
+
print(f"========================")
|
393
|
+
print(f"Detected: {'🚨 YES' if detection.is_detected else '✅ NO'}")
|
394
|
+
if detection.is_detected:
|
395
|
+
print(f"Severity: {detection.severity_level}")
|
396
|
+
print(f"Techniques: {', '.join(detection.evasion_techniques)}")
|
397
|
+
|
398
|
+
# Cell 3: Historical Analysis
|
399
|
+
detection_history = []
|
400
|
+
|
401
|
+
def record_detection(detection):
|
402
|
+
detection_history.append({
|
403
|
+
'timestamp': detection.timestamp,
|
404
|
+
'detected': detection.is_detected,
|
405
|
+
'severity': detection.severity_level,
|
406
|
+
'window_count': detection.window_count
|
407
|
+
})
|
408
|
+
|
409
|
+
monitor = ClueLyMonitor()
|
410
|
+
monitor.start(interval=30.0, on_change=record_detection)
|
411
|
+
|
412
|
+
# Let it run for a while, then analyze
|
413
|
+
# monitor.stop()
|
414
|
+
|
415
|
+
# Cell 4: Visualization
|
416
|
+
df = pd.DataFrame(detection_history)
|
417
|
+
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
418
|
+
|
419
|
+
plt.figure(figsize=(12, 6))
|
420
|
+
plt.subplot(2, 1, 1)
|
421
|
+
plt.plot(df['timestamp'], df['detected'].astype(int))
|
422
|
+
plt.title('Detection Status Over Time')
|
423
|
+
plt.ylabel('Detected (1=Yes, 0=No)')
|
424
|
+
|
425
|
+
plt.subplot(2, 1, 2)
|
426
|
+
plt.plot(df['timestamp'], df['window_count'])
|
427
|
+
plt.title('Window Count Over Time')
|
428
|
+
plt.ylabel('Window Count')
|
429
|
+
plt.xlabel('Time')
|
430
|
+
|
431
|
+
plt.tight_layout()
|
432
|
+
plt.show()
|
433
|
+
```
|
434
|
+
|
435
|
+
### Automation Script
|
436
|
+
```python
|
437
|
+
#!/usr/bin/env python3
|
438
|
+
"""
|
439
|
+
Security monitoring automation script
|
440
|
+
Usage: python security_monitor.py [--interval SECONDS] [--log-file PATH]
|
441
|
+
"""
|
442
|
+
|
443
|
+
import argparse
|
444
|
+
import sys
|
445
|
+
import time
|
446
|
+
import json
|
447
|
+
from pathlib import Path
|
448
|
+
from no_cluely_detector import ClueLyMonitor
|
449
|
+
|
450
|
+
def main():
|
451
|
+
parser = argparse.ArgumentParser(description='Monitor for Cluely employee monitoring software')
|
452
|
+
parser.add_argument('--interval', type=float, default=60.0,
|
453
|
+
help='Check interval in seconds (default: 60)')
|
454
|
+
parser.add_argument('--log-file', type=Path,
|
455
|
+
help='JSON log file path')
|
456
|
+
parser.add_argument('--alert-command', type=str,
|
457
|
+
help='Command to run when threat detected')
|
458
|
+
|
459
|
+
args = parser.parse_args()
|
460
|
+
|
461
|
+
def log_detection(detection):
|
462
|
+
log_entry = {
|
463
|
+
'timestamp': detection.timestamp.isoformat(),
|
464
|
+
'detected': detection.is_detected,
|
465
|
+
'severity': detection.severity_level,
|
466
|
+
'evasion_techniques': detection.evasion_techniques,
|
467
|
+
'window_count': detection.window_count
|
468
|
+
}
|
469
|
+
|
470
|
+
if args.log_file:
|
471
|
+
with open(args.log_file, 'a') as f:
|
472
|
+
f.write(json.dumps(log_entry) + '\n')
|
473
|
+
|
474
|
+
# Print to console
|
475
|
+
status = f"🚨 DETECTED ({detection.severity_level})" if detection.is_detected else "✅ CLEAN"
|
476
|
+
print(f"[{detection.timestamp}] {status}")
|
477
|
+
|
478
|
+
if detection.is_detected and detection.evasion_techniques:
|
479
|
+
print(f" Techniques: {', '.join(detection.evasion_techniques)}")
|
480
|
+
|
481
|
+
def on_detected(detection):
|
482
|
+
print(f"🚨 ALERT: Employee monitoring detected! Severity: {detection.severity_level}")
|
483
|
+
|
484
|
+
if args.alert_command:
|
485
|
+
import subprocess
|
486
|
+
try:
|
487
|
+
subprocess.run(args.alert_command.split(), check=True)
|
488
|
+
except subprocess.CalledProcessError as e:
|
489
|
+
print(f"Alert command failed: {e}")
|
490
|
+
|
491
|
+
print(f"Starting security monitor (interval: {args.interval}s)")
|
492
|
+
if args.log_file:
|
493
|
+
print(f"Logging to: {args.log_file}")
|
494
|
+
|
495
|
+
monitor = ClueLyMonitor()
|
496
|
+
monitor.start(
|
497
|
+
interval=args.interval,
|
498
|
+
on_detected=on_detected,
|
499
|
+
on_change=log_detection
|
500
|
+
)
|
501
|
+
|
502
|
+
try:
|
503
|
+
while True:
|
504
|
+
time.sleep(1)
|
505
|
+
except KeyboardInterrupt:
|
506
|
+
print("\nStopping monitor...")
|
507
|
+
monitor.stop()
|
508
|
+
|
509
|
+
if __name__ == '__main__':
|
510
|
+
main()
|
511
|
+
```
|
512
|
+
|
513
|
+
## Error Handling
|
514
|
+
|
515
|
+
```python
|
516
|
+
from no_cluely_detector import ClueLyDetector
|
517
|
+
import platform
|
518
|
+
|
519
|
+
try:
|
520
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
521
|
+
# Process detection...
|
522
|
+
except RuntimeError as e:
|
523
|
+
if "only supported on macOS" in str(e):
|
524
|
+
print("This library only works on macOS")
|
525
|
+
else:
|
526
|
+
print(f"Detection error: {e}")
|
527
|
+
except FileNotFoundError as e:
|
528
|
+
print("Rust library not found. Please install properly.")
|
529
|
+
except Exception as e:
|
530
|
+
print(f"Unexpected error: {e}")
|
531
|
+
```
|
532
|
+
|
533
|
+
## Performance
|
534
|
+
|
535
|
+
- **Detection Speed**: < 50ms per check
|
536
|
+
- **Memory Usage**: < 2MB resident memory
|
537
|
+
- **Thread Safety**: All methods are thread-safe
|
538
|
+
- **CPU Usage**: Minimal (< 0.1% during checks)
|
539
|
+
|
540
|
+
## Testing
|
541
|
+
|
542
|
+
```python
|
543
|
+
# test_detection.py
|
544
|
+
import pytest
|
545
|
+
from no_cluely_detector import ClueLyDetector, ClueLyMonitor
|
546
|
+
|
547
|
+
def test_basic_detection():
|
548
|
+
"""Test basic detection functionality."""
|
549
|
+
result = ClueLyDetector.is_cluely_running()
|
550
|
+
assert isinstance(result, bool)
|
551
|
+
|
552
|
+
def test_detailed_detection():
|
553
|
+
"""Test detailed detection."""
|
554
|
+
detection = ClueLyDetector.detect_cluely_detailed()
|
555
|
+
|
556
|
+
assert hasattr(detection, 'is_detected')
|
557
|
+
assert hasattr(detection, 'severity_level')
|
558
|
+
assert isinstance(detection.evasion_techniques, list)
|
559
|
+
assert detection.severity_level in ['None', 'Low', 'Medium', 'High']
|
560
|
+
|
561
|
+
def test_monitor():
|
562
|
+
"""Test monitoring functionality."""
|
563
|
+
monitor = ClueLyMonitor()
|
564
|
+
|
565
|
+
# Should start and stop without issues
|
566
|
+
monitor.start(interval=1.0)
|
567
|
+
time.sleep(2)
|
568
|
+
monitor.stop()
|
569
|
+
|
570
|
+
if __name__ == '__main__':
|
571
|
+
pytest.main([__file__])
|
572
|
+
```
|
573
|
+
|
574
|
+
Run tests:
|
575
|
+
```bash
|
576
|
+
pip install pytest
|
577
|
+
python -m pytest test_detection.py -v
|
578
|
+
```
|
579
|
+
|
580
|
+
## License
|
581
|
+
|
582
|
+
MIT License
|
583
|
+
|
584
|
+
## Contributing
|
585
|
+
|
586
|
+
Issues and pull requests welcome at: https://github.com/your-org/no-cluely-driver
|
@@ -0,0 +1,6 @@
|
|
1
|
+
no_cluely_detector/__init__.py,sha256=CpJrFN-by68C1zJ3O_XAXLqya4TrZ40-EBOENLwYOPY,12836
|
2
|
+
no_cluely_detector/py.typed,sha256=ly_OTohiB3OSMv5IvZcW13LkzgSEk-ieT7d3zlEXpdc,70
|
3
|
+
no_cluely_detector-0.0.1.dist-info/METADATA,sha256=n8N2ceZnzSNPdJhvsim43sgLweTfh_1HyigRHfvvqUk,16899
|
4
|
+
no_cluely_detector-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
5
|
+
no_cluely_detector-0.0.1.dist-info/top_level.txt,sha256=BCY0tJTlX9NWuKdsapwC3nY9tnYQ_iymXuZlFpWoeAc,19
|
6
|
+
no_cluely_detector-0.0.1.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
no_cluely_detector
|