hyperprobe-agent 1.2.26b3__tar.gz → 1.2.27__tar.gz
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.
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/PKG-INFO +18 -3
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/README.md +17 -2
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/agent.py +41 -5
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/evaluator.py +55 -17
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/monitoring_engine.py +31 -9
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/safety.py +68 -19
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe_agent.egg-info/PKG-INFO +18 -3
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe_agent.egg-info/SOURCES.txt +1 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/setup.py +1 -1
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_agent.py +39 -0
- hyperprobe_agent-1.2.27/tests/test_evaluator.py +134 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_fork.py +5 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_metric_probes.py +29 -0
- hyperprobe_agent-1.2.27/tests/test_safety.py +149 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_startup.py +91 -2
- hyperprobe_agent-1.2.26b3/tests/test_evaluator.py +0 -65
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/LICENSE +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/__init__.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/__init__.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/broker.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/logger.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/probe_output.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/quota.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/serializer.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/core/trace_extractor.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/protos/__init__.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/protos/agent_pb2.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe/protos/agent_pb2_grpc.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe_agent.egg-info/dependency_links.txt +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe_agent.egg-info/requires.txt +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/hyperprobe_agent.egg-info/top_level.txt +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/setup.cfg +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_logger.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_multithreading_integration.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_p1_regressions.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_probe_output.py +0 -0
- {hyperprobe_agent-1.2.26b3 → hyperprobe_agent-1.2.27}/tests/test_serializer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperprobe-agent
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.27
|
|
4
4
|
Summary: Production-grade, non-breaking live debugger and telemetry agent for Python.
|
|
5
5
|
Home-page: https://www.hyperprobe.co
|
|
6
6
|
Author: Arif, Saksham, Karan
|
|
@@ -98,7 +98,8 @@ Configure the agent using the following environment variables:
|
|
|
98
98
|
| Variable | Description | Default |
|
|
99
99
|
| --- | --- | --- |
|
|
100
100
|
| `HYPERPROBE_BROKER_URL` | The URL of the HyperProbe Telemetry Broker. | *Required* |
|
|
101
|
-
| `HYPERPROBE_SERVICE_ID` |
|
|
101
|
+
| `HYPERPROBE_SERVICE_ID` | UUIDv4 service identifier provided by the HyperProbe dashboard. | *Required* |
|
|
102
|
+
| `HYPERPROBE_ALLOW_NON_UUID` | Set to `yes` only to allow a legacy non-UUID service identifier. | *(unset)* |
|
|
102
103
|
| `HYPERPROBE_ENVIRONMENT` | Deployment environment name (e.g., `production`, `staging`). | *Required* |
|
|
103
104
|
| `HYPERPROBE_COMMIT_SHA` or `GIT_COMMIT` | Git commit SHA of the running application version. | *Required* |
|
|
104
105
|
| `HYPERPROBE_DISABLED` | Set to `YES` to explicitly disable the agent. | *(unset)* |
|
|
@@ -109,7 +110,7 @@ Configure the agent using the following environment variables:
|
|
|
109
110
|
| `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Max bandwidth limit for telemetry transmissions (quota). | `1024` (1 MB) |
|
|
110
111
|
| `HYPERPROBE_RPC_TIMEOUT_SEC` | Deadline for broker RPCs. | `10` |
|
|
111
112
|
| `HYPERPROBE_COOLDOWN_SEC` | Tracing suspension duration after a RED safety state. | `10` |
|
|
112
|
-
| `HYPERPROBE_MAX_LAG_MS` |
|
|
113
|
+
| `HYPERPROBE_MAX_LAG_MS` | Per-reading scheduler-lag threshold. YELLOW requires 4/10 breaches; RED requires 7/10 breaches or 3/5 readings above 4x the threshold. | `50` |
|
|
113
114
|
| `HYPERPROBE_PAUSE_BUDGET_MS` | Per-second capture pause budget for the safety monitor. | `15` |
|
|
114
115
|
| `HYPERPROBE_REDACT_KEYS` | Comma-separated key patterns to redact. | `password,secret,token,authorization,cookie,key,signature` |
|
|
115
116
|
| `HYPERPROBE_REDACT_VALUES` | Comma-separated value patterns to redact. | *(empty)* |
|
|
@@ -118,8 +119,22 @@ Configure the agent using the following environment variables:
|
|
|
118
119
|
| `HYPERPROBE_STACK_FRAME_DEPTH` | Maximum captured stack-frame depth. | `3` |
|
|
119
120
|
| `HYPERPROBE_MAX_OBJECT_PROPERTIES` | Maximum serialized properties per object. | `50` |
|
|
120
121
|
| `HYPERPROBE_MAX_STRING_LENGTH` | Maximum serialized string length. | `1024` |
|
|
122
|
+
| `HYPERPROBE_DISABLE_SAFE_EVALUATION` | Set to `true` to explicitly disable safe evaluation restrictions and allow arbitrary method/function calls in probe expressions. | `false` |
|
|
121
123
|
| `HYPERPROBE_FORK_MODE` | Set to `worker` for preloaded, prefork servers. The parent defers agent initialization and every worker starts fresh process-local state. | `none` |
|
|
122
124
|
|
|
125
|
+
Programmatic integrations can set `allow_non_uuid=True` for the same legacy
|
|
126
|
+
bypass. Without an explicit bypass, a non-UUIDv4 service ID prevents agent
|
|
127
|
+
startup while the host application continues normally.
|
|
128
|
+
|
|
129
|
+
### Safe Evaluation & Security Considerations
|
|
130
|
+
|
|
131
|
+
By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`). In safe mode:
|
|
132
|
+
- Method and function calls are strictly restricted to a narrow allowlist of side-effect free standard operations.
|
|
133
|
+
- State mutations, assignments, and arbitrary function invocations (such as database calls, network I/O, file operations, or state modifications) are prevented.
|
|
134
|
+
|
|
135
|
+
**Disabling Safe Evaluation (`HYPERPROBE_DISABLE_SAFE_EVALUATION=true`):**
|
|
136
|
+
> ⚠️ **Security Warning**: Setting `HYPERPROBE_DISABLE_SAFE_EVALUATION=true` allows conditions, watch expressions, and log templates to execute arbitrary functions and methods in the host application process. This may lead to unintended side effects, state mutations, performance degradation, or security risks if expressions invoke mutating or blocking operations. Only disable safe evaluation in trusted environments where dynamic method evaluation is strictly required.
|
|
137
|
+
|
|
123
138
|
### Debug logging
|
|
124
139
|
|
|
125
140
|
Set `DEBUG` to a comma- or space-separated list of namespaces. Patterns accept
|
|
@@ -63,7 +63,8 @@ Configure the agent using the following environment variables:
|
|
|
63
63
|
| Variable | Description | Default |
|
|
64
64
|
| --- | --- | --- |
|
|
65
65
|
| `HYPERPROBE_BROKER_URL` | The URL of the HyperProbe Telemetry Broker. | *Required* |
|
|
66
|
-
| `HYPERPROBE_SERVICE_ID` |
|
|
66
|
+
| `HYPERPROBE_SERVICE_ID` | UUIDv4 service identifier provided by the HyperProbe dashboard. | *Required* |
|
|
67
|
+
| `HYPERPROBE_ALLOW_NON_UUID` | Set to `yes` only to allow a legacy non-UUID service identifier. | *(unset)* |
|
|
67
68
|
| `HYPERPROBE_ENVIRONMENT` | Deployment environment name (e.g., `production`, `staging`). | *Required* |
|
|
68
69
|
| `HYPERPROBE_COMMIT_SHA` or `GIT_COMMIT` | Git commit SHA of the running application version. | *Required* |
|
|
69
70
|
| `HYPERPROBE_DISABLED` | Set to `YES` to explicitly disable the agent. | *(unset)* |
|
|
@@ -74,7 +75,7 @@ Configure the agent using the following environment variables:
|
|
|
74
75
|
| `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Max bandwidth limit for telemetry transmissions (quota). | `1024` (1 MB) |
|
|
75
76
|
| `HYPERPROBE_RPC_TIMEOUT_SEC` | Deadline for broker RPCs. | `10` |
|
|
76
77
|
| `HYPERPROBE_COOLDOWN_SEC` | Tracing suspension duration after a RED safety state. | `10` |
|
|
77
|
-
| `HYPERPROBE_MAX_LAG_MS` |
|
|
78
|
+
| `HYPERPROBE_MAX_LAG_MS` | Per-reading scheduler-lag threshold. YELLOW requires 4/10 breaches; RED requires 7/10 breaches or 3/5 readings above 4x the threshold. | `50` |
|
|
78
79
|
| `HYPERPROBE_PAUSE_BUDGET_MS` | Per-second capture pause budget for the safety monitor. | `15` |
|
|
79
80
|
| `HYPERPROBE_REDACT_KEYS` | Comma-separated key patterns to redact. | `password,secret,token,authorization,cookie,key,signature` |
|
|
80
81
|
| `HYPERPROBE_REDACT_VALUES` | Comma-separated value patterns to redact. | *(empty)* |
|
|
@@ -83,8 +84,22 @@ Configure the agent using the following environment variables:
|
|
|
83
84
|
| `HYPERPROBE_STACK_FRAME_DEPTH` | Maximum captured stack-frame depth. | `3` |
|
|
84
85
|
| `HYPERPROBE_MAX_OBJECT_PROPERTIES` | Maximum serialized properties per object. | `50` |
|
|
85
86
|
| `HYPERPROBE_MAX_STRING_LENGTH` | Maximum serialized string length. | `1024` |
|
|
87
|
+
| `HYPERPROBE_DISABLE_SAFE_EVALUATION` | Set to `true` to explicitly disable safe evaluation restrictions and allow arbitrary method/function calls in probe expressions. | `false` |
|
|
86
88
|
| `HYPERPROBE_FORK_MODE` | Set to `worker` for preloaded, prefork servers. The parent defers agent initialization and every worker starts fresh process-local state. | `none` |
|
|
87
89
|
|
|
90
|
+
Programmatic integrations can set `allow_non_uuid=True` for the same legacy
|
|
91
|
+
bypass. Without an explicit bypass, a non-UUIDv4 service ID prevents agent
|
|
92
|
+
startup while the host application continues normally.
|
|
93
|
+
|
|
94
|
+
### Safe Evaluation & Security Considerations
|
|
95
|
+
|
|
96
|
+
By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`). In safe mode:
|
|
97
|
+
- Method and function calls are strictly restricted to a narrow allowlist of side-effect free standard operations.
|
|
98
|
+
- State mutations, assignments, and arbitrary function invocations (such as database calls, network I/O, file operations, or state modifications) are prevented.
|
|
99
|
+
|
|
100
|
+
**Disabling Safe Evaluation (`HYPERPROBE_DISABLE_SAFE_EVALUATION=true`):**
|
|
101
|
+
> ⚠️ **Security Warning**: Setting `HYPERPROBE_DISABLE_SAFE_EVALUATION=true` allows conditions, watch expressions, and log templates to execute arbitrary functions and methods in the host application process. This may lead to unintended side effects, state mutations, performance degradation, or security risks if expressions invoke mutating or blocking operations. Only disable safe evaluation in trusted environments where dynamic method evaluation is strictly required.
|
|
102
|
+
|
|
88
103
|
### Debug logging
|
|
89
104
|
|
|
90
105
|
Set `DEBUG` to a comma- or space-separated list of namespaces. Patterns accept
|
|
@@ -78,6 +78,22 @@ class HyperProbeAgent:
|
|
|
78
78
|
max_lag_ms = options.get("hyperprobe_max_lag_ms") or float(os.getenv("HYPERPROBE_MAX_LAG_MS", 50.0))
|
|
79
79
|
pause_budget_ms = options.get("hyperprobe_pause_budget_ms") or float(os.getenv("HYPERPROBE_PAUSE_BUDGET_MS", 15.0))
|
|
80
80
|
|
|
81
|
+
opt_disable_safe_eval = options.get("disable_safe_evaluation", False)
|
|
82
|
+
disable_safe_eval = (
|
|
83
|
+
os.getenv("HYPERPROBE_DISABLE_SAFE_EVALUATION", "").strip().lower() == "true"
|
|
84
|
+
or opt_disable_safe_eval is True
|
|
85
|
+
or (
|
|
86
|
+
isinstance(opt_disable_safe_eval, str)
|
|
87
|
+
and opt_disable_safe_eval.strip().lower() == "true"
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if disable_safe_eval:
|
|
92
|
+
logger.forceInfo(
|
|
93
|
+
"\033[1m\033[33m⚠️ [HyperProbe] WARN: Safe evaluation is DISABLED via "
|
|
94
|
+
"HYPERPROBE_DISABLE_SAFE_EVALUATION. Function and method calls are permitted in probe expressions.\033[0m"
|
|
95
|
+
)
|
|
96
|
+
|
|
81
97
|
self.global_config = {
|
|
82
98
|
"redact_keys": options.get("redact_keys") or os.getenv("HYPERPROBE_REDACT_KEYS", "password,secret,token,authorization,cookie,key,signature").split(","),
|
|
83
99
|
"redact_values": options.get("redact_values") or os.getenv("HYPERPROBE_REDACT_VALUES", "").split(","),
|
|
@@ -85,7 +101,8 @@ class HyperProbeAgent:
|
|
|
85
101
|
"max_array_length": options.get("max_array_length") or int(os.getenv("HYPERPROBE_MAX_ARRAY_LENGTH", 3)),
|
|
86
102
|
"stack_frame_depth": options.get("stack_frame_depth") or int(os.getenv("HYPERPROBE_STACK_FRAME_DEPTH", 3)),
|
|
87
103
|
"max_object_properties": options.get("max_object_properties") or int(os.getenv("HYPERPROBE_MAX_OBJECT_PROPERTIES", 50)),
|
|
88
|
-
"max_string_length": options.get("max_string_length") or int(os.getenv("HYPERPROBE_MAX_STRING_LENGTH", 1024))
|
|
104
|
+
"max_string_length": options.get("max_string_length") or int(os.getenv("HYPERPROBE_MAX_STRING_LENGTH", 1024)),
|
|
105
|
+
"disable_safe_evaluation": disable_safe_eval,
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
# Strip empty redaction strings
|
|
@@ -108,6 +125,7 @@ class HyperProbeAgent:
|
|
|
108
125
|
agent_id=self.agent_id,
|
|
109
126
|
rpc_timeout_sec=rpc_timeout_sec,
|
|
110
127
|
)
|
|
128
|
+
self.agent_version = self.broker_client.agent_version
|
|
111
129
|
|
|
112
130
|
# Thread-safe queue for telemetry events
|
|
113
131
|
self.telemetry_queue = queue.Queue(maxsize=self.max_queue_size)
|
|
@@ -168,7 +186,21 @@ class HyperProbeAgent:
|
|
|
168
186
|
logger.forceError('\033[1m\033[33m⚠️ [HyperProbe] CRITICAL: Failed to start agent. HYPERPROBE_SERVICE_ID is required.\033[0m')
|
|
169
187
|
return None
|
|
170
188
|
if not UUID_REGEX.match(str(service_id).strip()):
|
|
171
|
-
|
|
189
|
+
allow_non_uuid = options.get("allow_non_uuid")
|
|
190
|
+
if allow_non_uuid is None:
|
|
191
|
+
allow_non_uuid = (
|
|
192
|
+
os.getenv("HYPERPROBE_ALLOW_NON_UUID", "")
|
|
193
|
+
.strip()
|
|
194
|
+
.lower()
|
|
195
|
+
== "yes"
|
|
196
|
+
)
|
|
197
|
+
elif isinstance(allow_non_uuid, str):
|
|
198
|
+
allow_non_uuid = allow_non_uuid.strip().lower() == "yes"
|
|
199
|
+
|
|
200
|
+
if not allow_non_uuid:
|
|
201
|
+
logger.forceError('\033[1m\033[33m⚠️ [HyperProbe] CRITICAL: Failed to start agent. service_id is not a valid UUIDv4. Set HYPERPROBE_ALLOW_NON_UUID=yes to allow non-UUID identifiers.\033[0m')
|
|
202
|
+
return None
|
|
203
|
+
logger.forceInfo('\033[1m\033[33m⚠️ [HyperProbe] WARN: service_id is not a valid UUIDv4, please check again.\033[0m')
|
|
172
204
|
options["service_id"] = service_id
|
|
173
205
|
|
|
174
206
|
environment = options.get("environment") or os.getenv("HYPERPROBE_ENVIRONMENT")
|
|
@@ -215,7 +247,8 @@ class HyperProbeAgent:
|
|
|
215
247
|
return None
|
|
216
248
|
logger.forceInfo(
|
|
217
249
|
f"[HyperProbe] Python agent successfully started "
|
|
218
|
-
f"(
|
|
250
|
+
f"(Version: {cls._instance.agent_version}, "
|
|
251
|
+
f"ID: {cls._instance.agent_id}, PID: {cls._instance.owner_pid})."
|
|
219
252
|
)
|
|
220
253
|
return cls._instance
|
|
221
254
|
|
|
@@ -308,7 +341,8 @@ class HyperProbeAgent:
|
|
|
308
341
|
|
|
309
342
|
logger.forceInfo(
|
|
310
343
|
f"[HyperProbe] Python agent started after fork "
|
|
311
|
-
f"(
|
|
344
|
+
f"(Version: {instance.agent_version}, "
|
|
345
|
+
f"ID: {instance.agent_id}, PID: {instance.owner_pid})."
|
|
312
346
|
)
|
|
313
347
|
|
|
314
348
|
@classmethod
|
|
@@ -716,7 +750,7 @@ class HyperProbeAgent:
|
|
|
716
750
|
return
|
|
717
751
|
|
|
718
752
|
if health == AgentHealth.RED:
|
|
719
|
-
logger.error(f"[HyperProbe] Safety Shield Triggered (RED): {reason or ''}. Suspending
|
|
753
|
+
logger.error(f"[HyperProbe] Safety Shield Triggered (RED): {reason or ''}. Suspending instrumentation.")
|
|
720
754
|
with self._agent_lock:
|
|
721
755
|
self.engine.suspend()
|
|
722
756
|
|
|
@@ -739,6 +773,7 @@ class HyperProbeAgent:
|
|
|
739
773
|
with self._agent_lock:
|
|
740
774
|
cooldown_expired = self.cooldown_timer is None
|
|
741
775
|
if cooldown_expired:
|
|
776
|
+
logger.info(f"[HyperProbe] Status back to GREEN. {reason or ''}.")
|
|
742
777
|
self._resume_instrumentation()
|
|
743
778
|
|
|
744
779
|
def _cooldown_expired(self, cooldown_generation):
|
|
@@ -752,6 +787,7 @@ class HyperProbeAgent:
|
|
|
752
787
|
with self._agent_lock:
|
|
753
788
|
if (
|
|
754
789
|
self.is_shutdown
|
|
790
|
+
or not self.engine.is_suspended
|
|
755
791
|
or self.safety_monitor.get_health() != AgentHealth.GREEN
|
|
756
792
|
or self.cooldown_timer is not None
|
|
757
793
|
):
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import re
|
|
2
3
|
import ast
|
|
3
4
|
from functools import lru_cache
|
|
5
|
+
from typing import Optional
|
|
4
6
|
|
|
5
7
|
from hyperprobe.core.logger import get_logger
|
|
6
8
|
|
|
@@ -23,6 +25,9 @@ SAFE_METHODS = {
|
|
|
23
25
|
"isdigit", "isalpha", "isnumeric", "isalnum",
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
def is_safe_evaluation_enabled() -> bool:
|
|
29
|
+
return os.getenv("HYPERPROBE_DISABLE_SAFE_EVALUATION", "").strip().lower() != "true"
|
|
30
|
+
|
|
26
31
|
class SafeASTVisitor(ast.NodeVisitor):
|
|
27
32
|
def visit_Call(self, node):
|
|
28
33
|
# 1. Is it a direct function call? e.g. len(items)
|
|
@@ -46,11 +51,20 @@ class ProbeEvaluator:
|
|
|
46
51
|
_placeholder_regex = re.compile(r'\$?\{([^}]+)\}')
|
|
47
52
|
|
|
48
53
|
@classmethod
|
|
49
|
-
def
|
|
54
|
+
def is_safe_evaluation_enabled(cls) -> bool:
|
|
55
|
+
return is_safe_evaluation_enabled()
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _get_or_compile(cls, expr: str, safe: bool = True):
|
|
50
59
|
# Normalize the string to prevent duplicate cache entries
|
|
51
60
|
clean_expr = expr.strip()
|
|
61
|
+
if safe:
|
|
62
|
+
return cls._compile_safe(clean_expr)
|
|
63
|
+
return cls._compile_unsafe(clean_expr)
|
|
52
64
|
|
|
53
|
-
|
|
65
|
+
@classmethod
|
|
66
|
+
def _get_or_compile_safe(cls, expr: str):
|
|
67
|
+
return cls._get_or_compile(expr, safe=True)
|
|
54
68
|
|
|
55
69
|
@staticmethod
|
|
56
70
|
@lru_cache(maxsize=COMPILED_CACHE_SIZE)
|
|
@@ -69,24 +83,46 @@ class ProbeEvaluator:
|
|
|
69
83
|
|
|
70
84
|
return compiled_code
|
|
71
85
|
|
|
86
|
+
@staticmethod
|
|
87
|
+
@lru_cache(maxsize=COMPILED_CACHE_SIZE)
|
|
88
|
+
def _compile_unsafe(clean_expr: str):
|
|
89
|
+
# Unsafe mode allows function/method calls without AST validation
|
|
90
|
+
return compile(clean_expr, filename="<string>", mode="eval")
|
|
91
|
+
|
|
72
92
|
@classmethod
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
safe_globals["__builtins__"] = SAFE_BUILTINS
|
|
93
|
+
def evaluate(cls, expr: str, globals_dict: dict, locals_dict: dict, safe: Optional[bool] = None):
|
|
94
|
+
if safe is None:
|
|
95
|
+
safe = cls.is_safe_evaluation_enabled()
|
|
77
96
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
if safe:
|
|
98
|
+
# Create a shadow copy of globals, forcefully removing dangerous builtins
|
|
99
|
+
safe_globals = globals_dict.copy()
|
|
100
|
+
safe_globals["__builtins__"] = SAFE_BUILTINS
|
|
101
|
+
|
|
102
|
+
# Grab the pre-compiled safe bytecode
|
|
103
|
+
compiled = cls._get_or_compile(expr, safe=True)
|
|
104
|
+
|
|
105
|
+
# Execute the bytecode
|
|
106
|
+
return eval(compiled, safe_globals, locals_dict)
|
|
107
|
+
else:
|
|
108
|
+
# Grab the pre-compiled unsafe bytecode allowing full execution
|
|
109
|
+
compiled = cls._get_or_compile(expr, safe=False)
|
|
110
|
+
return eval(compiled, globals_dict, locals_dict)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def safe_eval(cls, expr: str, globals_dict: dict, locals_dict: dict, safe: Optional[bool] = None):
|
|
114
|
+
"""Evaluates an expression, delegating to ProbeEvaluator.evaluate."""
|
|
115
|
+
return cls.evaluate(expr, globals_dict, locals_dict, safe=safe)
|
|
83
116
|
|
|
84
117
|
@classmethod
|
|
85
|
-
def evaluate_log_template(cls, template: str, globals_dict: dict, locals_dict: dict) -> str:
|
|
86
|
-
"""Parses and
|
|
118
|
+
def evaluate_log_template(cls, template: str, globals_dict: dict, locals_dict: dict, safe: Optional[bool] = None) -> str:
|
|
119
|
+
"""Parses and formats a log template string."""
|
|
87
120
|
if not template:
|
|
88
121
|
return ""
|
|
89
122
|
|
|
123
|
+
if safe is None:
|
|
124
|
+
safe = cls.is_safe_evaluation_enabled()
|
|
125
|
+
|
|
90
126
|
result = []
|
|
91
127
|
last_idx = 0
|
|
92
128
|
for match in cls._placeholder_regex.finditer(template):
|
|
@@ -95,8 +131,8 @@ class ProbeEvaluator:
|
|
|
95
131
|
expr = match.group(1).strip()
|
|
96
132
|
|
|
97
133
|
try:
|
|
98
|
-
#
|
|
99
|
-
val = cls.
|
|
134
|
+
# Execute expression
|
|
135
|
+
val = cls.evaluate(expr, globals_dict, locals_dict, safe=safe)
|
|
100
136
|
result.append(str(val))
|
|
101
137
|
except Exception as e:
|
|
102
138
|
result.append(f"<Error: {type(e).__name__}: {str(e)}>")
|
|
@@ -106,14 +142,16 @@ class ProbeEvaluator:
|
|
|
106
142
|
return "".join(result)
|
|
107
143
|
|
|
108
144
|
@classmethod
|
|
109
|
-
def evaluate_watches(cls, watch_expressions: list, globals_dict: dict, locals_dict: dict) -> dict:
|
|
145
|
+
def evaluate_watches(cls, watch_expressions: list, globals_dict: dict, locals_dict: dict, safe: Optional[bool] = None) -> dict:
|
|
110
146
|
"""Evaluates a set of independent watch expressions."""
|
|
111
147
|
results = {}
|
|
112
148
|
if not watch_expressions:
|
|
113
149
|
return results
|
|
150
|
+
if safe is None:
|
|
151
|
+
safe = cls.is_safe_evaluation_enabled()
|
|
114
152
|
for expr in watch_expressions:
|
|
115
153
|
try:
|
|
116
|
-
results[expr] = cls.
|
|
154
|
+
results[expr] = cls.evaluate(expr, globals_dict, locals_dict, safe=safe)
|
|
117
155
|
except Exception as e:
|
|
118
156
|
results[expr] = f"Error: {type(e).__name__}: {str(e)}"
|
|
119
157
|
return results
|
|
@@ -49,6 +49,7 @@ class MonitoringEngine:
|
|
|
49
49
|
self.instrumented_files = set()
|
|
50
50
|
self.is_active = False
|
|
51
51
|
self.is_suspended = False
|
|
52
|
+
self.safe_evaluation = ProbeEvaluator.is_safe_evaluation_enabled()
|
|
52
53
|
self.global_config = {}
|
|
53
54
|
self._redact_keys_re = None
|
|
54
55
|
self._redact_values_re = None
|
|
@@ -112,8 +113,25 @@ class MonitoringEngine:
|
|
|
112
113
|
self.global_config = candidate
|
|
113
114
|
self._redact_keys_re = redact_keys_re
|
|
114
115
|
self._redact_values_re = redact_values_re
|
|
116
|
+
self.safe_evaluation = self._is_safe_evaluation_enabled(candidate)
|
|
115
117
|
return True
|
|
116
118
|
|
|
119
|
+
def _is_safe_evaluation_enabled(self, config_snapshot=None) -> bool:
|
|
120
|
+
"""Resolves whether safe evaluation is active from a config snapshot or internal state."""
|
|
121
|
+
config = config_snapshot if config_snapshot is not None else self.global_config
|
|
122
|
+
if config:
|
|
123
|
+
if "disable_safe_evaluation" in config:
|
|
124
|
+
v = config["disable_safe_evaluation"]
|
|
125
|
+
if isinstance(v, str):
|
|
126
|
+
v = v.strip().lower() == "true"
|
|
127
|
+
return not bool(v)
|
|
128
|
+
if "safe_evaluation" in config:
|
|
129
|
+
v = config["safe_evaluation"]
|
|
130
|
+
if isinstance(v, str):
|
|
131
|
+
v = v.strip().lower() == "true"
|
|
132
|
+
return bool(v)
|
|
133
|
+
return self.safe_evaluation
|
|
134
|
+
|
|
117
135
|
@staticmethod
|
|
118
136
|
def _compile_redaction_patterns(patterns):
|
|
119
137
|
if patterns is None:
|
|
@@ -396,10 +414,11 @@ class MonitoringEngine:
|
|
|
396
414
|
config_snapshot = self.global_config.copy() if self.global_config else {}
|
|
397
415
|
redact_keys_re = self._redact_keys_re
|
|
398
416
|
redact_values_re = self._redact_values_re
|
|
417
|
+
safe_evaluation = self._is_safe_evaluation_enabled(config_snapshot)
|
|
399
418
|
|
|
400
419
|
if probe.condition:
|
|
401
420
|
try:
|
|
402
|
-
cond_val = ProbeEvaluator.
|
|
421
|
+
cond_val = ProbeEvaluator.evaluate(probe.condition, globals_dict, locals_dict, safe=safe_evaluation)
|
|
403
422
|
if not cond_val:
|
|
404
423
|
return
|
|
405
424
|
except Exception as e:
|
|
@@ -440,7 +459,7 @@ class MonitoringEngine:
|
|
|
440
459
|
serialization_context = {}
|
|
441
460
|
|
|
442
461
|
if getattr(probe, 'watch_expressions', None):
|
|
443
|
-
watches_result = ProbeEvaluator.evaluate_watches(probe.watch_expressions, globals_dict, locals_dict)
|
|
462
|
+
watches_result = ProbeEvaluator.evaluate_watches(probe.watch_expressions, globals_dict, locals_dict, safe=safe_evaluation)
|
|
444
463
|
serialized_watches = {}
|
|
445
464
|
for watch_name, watch_value in watches_result.items():
|
|
446
465
|
watch_path = f"watch[{json.dumps(str(watch_name))}]"
|
|
@@ -498,7 +517,7 @@ class MonitoringEngine:
|
|
|
498
517
|
|
|
499
518
|
# LOG TEMPLATE CAPTURE
|
|
500
519
|
elif probe.type == 2:
|
|
501
|
-
evaluated = ProbeEvaluator.evaluate_log_template(probe.template, globals_dict, locals_dict)
|
|
520
|
+
evaluated = ProbeEvaluator.evaluate_log_template(probe.template, globals_dict, locals_dict, safe=safe_evaluation)
|
|
502
521
|
if redact_values_re and redact_values_re.search(evaluated):
|
|
503
522
|
evaluated = redact_values_re.sub("[REDACTED Value]", evaluated)
|
|
504
523
|
if len(evaluated) > max_string_length:
|
|
@@ -529,11 +548,14 @@ class MonitoringEngine:
|
|
|
529
548
|
globals_dict = frame.f_globals
|
|
530
549
|
locals_dict = frame.f_locals
|
|
531
550
|
|
|
551
|
+
with self.lock:
|
|
552
|
+
safe_evaluation = self._is_safe_evaluation_enabled()
|
|
553
|
+
|
|
532
554
|
try:
|
|
533
555
|
if probe.condition:
|
|
534
556
|
try:
|
|
535
|
-
if not ProbeEvaluator.
|
|
536
|
-
probe.condition, globals_dict, locals_dict
|
|
557
|
+
if not ProbeEvaluator.evaluate(
|
|
558
|
+
probe.condition, globals_dict, locals_dict, safe=safe_evaluation
|
|
537
559
|
):
|
|
538
560
|
return
|
|
539
561
|
except Exception as error:
|
|
@@ -553,8 +575,8 @@ class MonitoringEngine:
|
|
|
553
575
|
# an empty expression reaches the SDK.
|
|
554
576
|
return
|
|
555
577
|
try:
|
|
556
|
-
raw_value = ProbeEvaluator.
|
|
557
|
-
metric_expression, globals_dict, locals_dict
|
|
578
|
+
raw_value = ProbeEvaluator.evaluate(
|
|
579
|
+
metric_expression, globals_dict, locals_dict, safe=safe_evaluation
|
|
558
580
|
)
|
|
559
581
|
except Exception as error:
|
|
560
582
|
self._report_metric_error(probe, str(error))
|
|
@@ -578,8 +600,8 @@ class MonitoringEngine:
|
|
|
578
600
|
)
|
|
579
601
|
if correlation_expression:
|
|
580
602
|
try:
|
|
581
|
-
correlation_value = ProbeEvaluator.
|
|
582
|
-
correlation_expression, globals_dict, locals_dict
|
|
603
|
+
correlation_value = ProbeEvaluator.evaluate(
|
|
604
|
+
correlation_expression, globals_dict, locals_dict, safe=safe_evaluation
|
|
583
605
|
)
|
|
584
606
|
except Exception as error:
|
|
585
607
|
self._report_metric_error(probe, f"Error: {error}")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import threading
|
|
3
|
+
from collections import deque
|
|
3
4
|
|
|
4
5
|
class AgentHealth:
|
|
5
6
|
GREEN = 'GREEN' # All good
|
|
@@ -8,6 +9,14 @@ class AgentHealth:
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class SafetyMonitor:
|
|
12
|
+
_SAMPLE_INTERVAL_SECONDS = 0.1
|
|
13
|
+
_LAG_WINDOW_SIZE = 10
|
|
14
|
+
_LAG_YELLOW_BREACHES = 4
|
|
15
|
+
_LAG_RED_BREACHES = 7
|
|
16
|
+
_SEVERE_LAG_WINDOW_SIZE = 5
|
|
17
|
+
_SEVERE_LAG_RED_BREACHES = 3
|
|
18
|
+
_SEVERE_LAG_MULTIPLIER = 4.0
|
|
19
|
+
|
|
11
20
|
def __init__(self, on_state_change, max_lag_ms=50.0, pause_budget_ms=15.0):
|
|
12
21
|
self.on_state_change = on_state_change
|
|
13
22
|
self.max_lag_ms = max_lag_ms
|
|
@@ -17,6 +26,7 @@ class SafetyMonitor:
|
|
|
17
26
|
self.cumulative_pause_time = 0.0
|
|
18
27
|
self.last_window_reset = time.monotonic()
|
|
19
28
|
self.last_thread_lag_ms = 0.0
|
|
29
|
+
self.thread_lag_samples = deque(maxlen=self._LAG_WINDOW_SIZE)
|
|
20
30
|
|
|
21
31
|
self.lock = threading.RLock()
|
|
22
32
|
self._notification_lock = threading.RLock()
|
|
@@ -42,47 +52,79 @@ class SafetyMonitor:
|
|
|
42
52
|
def report_pause_duration(self, ms: float):
|
|
43
53
|
with self.lock:
|
|
44
54
|
self.cumulative_pause_time += ms
|
|
45
|
-
transition = self._evaluate_health_locked(
|
|
55
|
+
transition = self._evaluate_health_locked()
|
|
46
56
|
|
|
47
57
|
self._notify_state_change(transition)
|
|
48
58
|
|
|
49
59
|
def _run_loop(self):
|
|
50
|
-
|
|
60
|
+
while self.is_running:
|
|
61
|
+
wait_started_at = time.monotonic()
|
|
62
|
+
if self.stop_event.wait(self._SAMPLE_INTERVAL_SECONDS):
|
|
63
|
+
break
|
|
51
64
|
|
|
52
|
-
while self.is_running and not self.stop_event.wait(0.1):
|
|
53
65
|
now = time.monotonic()
|
|
54
|
-
thread_lag_ms = (
|
|
55
|
-
|
|
66
|
+
thread_lag_ms = max(
|
|
67
|
+
0.0,
|
|
68
|
+
(
|
|
69
|
+
now
|
|
70
|
+
- wait_started_at
|
|
71
|
+
- self._SAMPLE_INTERVAL_SECONDS
|
|
72
|
+
) * 1000.0,
|
|
73
|
+
)
|
|
56
74
|
|
|
57
75
|
with self.lock:
|
|
58
|
-
self.
|
|
76
|
+
self._record_thread_lag_locked(thread_lag_ms)
|
|
59
77
|
if now - self.last_window_reset > 1.0:
|
|
60
78
|
self.cumulative_pause_time = 0.0
|
|
61
79
|
self.last_window_reset = now
|
|
62
80
|
|
|
63
|
-
transition = self._evaluate_health_locked(
|
|
81
|
+
transition = self._evaluate_health_locked()
|
|
64
82
|
|
|
65
83
|
self._notify_state_change(transition)
|
|
66
84
|
|
|
67
|
-
def _check_health(self, thread_lag_ms
|
|
85
|
+
def _check_health(self, thread_lag_ms=None):
|
|
68
86
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
Records an optional lag sample and executes callbacks outside the
|
|
88
|
+
state lock.
|
|
71
89
|
"""
|
|
72
90
|
with self.lock:
|
|
73
|
-
|
|
91
|
+
if thread_lag_ms is not None:
|
|
92
|
+
self._record_thread_lag_locked(thread_lag_ms)
|
|
93
|
+
transition = self._evaluate_health_locked()
|
|
74
94
|
|
|
75
95
|
self._notify_state_change(transition)
|
|
76
96
|
|
|
77
|
-
def
|
|
97
|
+
def _record_thread_lag_locked(self, thread_lag_ms: float):
|
|
98
|
+
thread_lag_ms = max(0.0, thread_lag_ms)
|
|
99
|
+
self.last_thread_lag_ms = thread_lag_ms
|
|
100
|
+
self.thread_lag_samples.append(thread_lag_ms)
|
|
101
|
+
|
|
102
|
+
def _evaluate_health_locked(self):
|
|
78
103
|
previous_health = self.health
|
|
79
104
|
reason = None
|
|
80
105
|
|
|
81
|
-
|
|
106
|
+
recent_lags = list(self.thread_lag_samples)
|
|
107
|
+
lag_breaches = sum(
|
|
108
|
+
lag > self.max_lag_ms for lag in recent_lags
|
|
109
|
+
)
|
|
110
|
+
severe_lag_ms = self.max_lag_ms * self._SEVERE_LAG_MULTIPLIER
|
|
111
|
+
severe_lag_breaches = sum(
|
|
112
|
+
lag > severe_lag_ms
|
|
113
|
+
for lag in recent_lags[-self._SEVERE_LAG_WINDOW_SIZE:]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if severe_lag_breaches >= self._SEVERE_LAG_RED_BREACHES:
|
|
82
117
|
self.health = AgentHealth.RED
|
|
83
118
|
reason = (
|
|
84
|
-
f"Execution Thread Lag
|
|
85
|
-
f"
|
|
119
|
+
f"Severe Execution Thread Lag exceeded {severe_lag_ms}ms in "
|
|
120
|
+
f"{severe_lag_breaches}/{self._SEVERE_LAG_WINDOW_SIZE} "
|
|
121
|
+
f"recent readings"
|
|
122
|
+
)
|
|
123
|
+
elif lag_breaches >= self._LAG_RED_BREACHES:
|
|
124
|
+
self.health = AgentHealth.RED
|
|
125
|
+
reason = (
|
|
126
|
+
f"Execution Thread Lag exceeded {self.max_lag_ms}ms in "
|
|
127
|
+
f"{lag_breaches}/{self._LAG_WINDOW_SIZE} recent readings"
|
|
86
128
|
)
|
|
87
129
|
elif self.cumulative_pause_time > self.pause_budget_ms:
|
|
88
130
|
self.health = AgentHealth.RED
|
|
@@ -90,12 +132,19 @@ class SafetyMonitor:
|
|
|
90
132
|
f"Cumulative Pause Budget ({self.cumulative_pause_time:.1f}ms) "
|
|
91
133
|
f"exceeded limit ({self.pause_budget_ms}ms)"
|
|
92
134
|
)
|
|
93
|
-
elif
|
|
135
|
+
elif severe_lag_breaches > 0:
|
|
136
|
+
self.health = AgentHealth.YELLOW
|
|
137
|
+
reason = (
|
|
138
|
+
f"Moderate impact: Severe Execution Thread Lag exceeded "
|
|
139
|
+
f"{severe_lag_ms}ms in {severe_lag_breaches}/"
|
|
140
|
+
f"{self._SEVERE_LAG_WINDOW_SIZE} recent readings"
|
|
141
|
+
)
|
|
142
|
+
elif lag_breaches >= self._LAG_YELLOW_BREACHES:
|
|
94
143
|
self.health = AgentHealth.YELLOW
|
|
95
144
|
reason = (
|
|
96
|
-
f"Moderate impact: Execution Thread Lag "
|
|
97
|
-
f"
|
|
98
|
-
f"
|
|
145
|
+
f"Moderate impact: Execution Thread Lag exceeded "
|
|
146
|
+
f"{self.max_lag_ms}ms in {lag_breaches}/"
|
|
147
|
+
f"{self._LAG_WINDOW_SIZE} recent readings"
|
|
99
148
|
)
|
|
100
149
|
elif self.cumulative_pause_time > self.pause_budget_ms / 2.0:
|
|
101
150
|
self.health = AgentHealth.YELLOW
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperprobe-agent
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.27
|
|
4
4
|
Summary: Production-grade, non-breaking live debugger and telemetry agent for Python.
|
|
5
5
|
Home-page: https://www.hyperprobe.co
|
|
6
6
|
Author: Arif, Saksham, Karan
|
|
@@ -98,7 +98,8 @@ Configure the agent using the following environment variables:
|
|
|
98
98
|
| Variable | Description | Default |
|
|
99
99
|
| --- | --- | --- |
|
|
100
100
|
| `HYPERPROBE_BROKER_URL` | The URL of the HyperProbe Telemetry Broker. | *Required* |
|
|
101
|
-
| `HYPERPROBE_SERVICE_ID` |
|
|
101
|
+
| `HYPERPROBE_SERVICE_ID` | UUIDv4 service identifier provided by the HyperProbe dashboard. | *Required* |
|
|
102
|
+
| `HYPERPROBE_ALLOW_NON_UUID` | Set to `yes` only to allow a legacy non-UUID service identifier. | *(unset)* |
|
|
102
103
|
| `HYPERPROBE_ENVIRONMENT` | Deployment environment name (e.g., `production`, `staging`). | *Required* |
|
|
103
104
|
| `HYPERPROBE_COMMIT_SHA` or `GIT_COMMIT` | Git commit SHA of the running application version. | *Required* |
|
|
104
105
|
| `HYPERPROBE_DISABLED` | Set to `YES` to explicitly disable the agent. | *(unset)* |
|
|
@@ -109,7 +110,7 @@ Configure the agent using the following environment variables:
|
|
|
109
110
|
| `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Max bandwidth limit for telemetry transmissions (quota). | `1024` (1 MB) |
|
|
110
111
|
| `HYPERPROBE_RPC_TIMEOUT_SEC` | Deadline for broker RPCs. | `10` |
|
|
111
112
|
| `HYPERPROBE_COOLDOWN_SEC` | Tracing suspension duration after a RED safety state. | `10` |
|
|
112
|
-
| `HYPERPROBE_MAX_LAG_MS` |
|
|
113
|
+
| `HYPERPROBE_MAX_LAG_MS` | Per-reading scheduler-lag threshold. YELLOW requires 4/10 breaches; RED requires 7/10 breaches or 3/5 readings above 4x the threshold. | `50` |
|
|
113
114
|
| `HYPERPROBE_PAUSE_BUDGET_MS` | Per-second capture pause budget for the safety monitor. | `15` |
|
|
114
115
|
| `HYPERPROBE_REDACT_KEYS` | Comma-separated key patterns to redact. | `password,secret,token,authorization,cookie,key,signature` |
|
|
115
116
|
| `HYPERPROBE_REDACT_VALUES` | Comma-separated value patterns to redact. | *(empty)* |
|
|
@@ -118,8 +119,22 @@ Configure the agent using the following environment variables:
|
|
|
118
119
|
| `HYPERPROBE_STACK_FRAME_DEPTH` | Maximum captured stack-frame depth. | `3` |
|
|
119
120
|
| `HYPERPROBE_MAX_OBJECT_PROPERTIES` | Maximum serialized properties per object. | `50` |
|
|
120
121
|
| `HYPERPROBE_MAX_STRING_LENGTH` | Maximum serialized string length. | `1024` |
|
|
122
|
+
| `HYPERPROBE_DISABLE_SAFE_EVALUATION` | Set to `true` to explicitly disable safe evaluation restrictions and allow arbitrary method/function calls in probe expressions. | `false` |
|
|
121
123
|
| `HYPERPROBE_FORK_MODE` | Set to `worker` for preloaded, prefork servers. The parent defers agent initialization and every worker starts fresh process-local state. | `none` |
|
|
122
124
|
|
|
125
|
+
Programmatic integrations can set `allow_non_uuid=True` for the same legacy
|
|
126
|
+
bypass. Without an explicit bypass, a non-UUIDv4 service ID prevents agent
|
|
127
|
+
startup while the host application continues normally.
|
|
128
|
+
|
|
129
|
+
### Safe Evaluation & Security Considerations
|
|
130
|
+
|
|
131
|
+
By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`). In safe mode:
|
|
132
|
+
- Method and function calls are strictly restricted to a narrow allowlist of side-effect free standard operations.
|
|
133
|
+
- State mutations, assignments, and arbitrary function invocations (such as database calls, network I/O, file operations, or state modifications) are prevented.
|
|
134
|
+
|
|
135
|
+
**Disabling Safe Evaluation (`HYPERPROBE_DISABLE_SAFE_EVALUATION=true`):**
|
|
136
|
+
> ⚠️ **Security Warning**: Setting `HYPERPROBE_DISABLE_SAFE_EVALUATION=true` allows conditions, watch expressions, and log templates to execute arbitrary functions and methods in the host application process. This may lead to unintended side effects, state mutations, performance degradation, or security risks if expressions invoke mutating or blocking operations. Only disable safe evaluation in trusted environments where dynamic method evaluation is strictly required.
|
|
137
|
+
|
|
123
138
|
### Debug logging
|
|
124
139
|
|
|
125
140
|
Set `DEBUG` to a comma- or space-separated list of namespaces. Patterns accept
|
|
@@ -7,7 +7,7 @@ long_description = (this_directory / "README.md").read_text() if (this_directory
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name="hyperprobe-agent",
|
|
10
|
-
version="1.2.
|
|
10
|
+
version="1.2.27",
|
|
11
11
|
author="Arif, Saksham, Karan",
|
|
12
12
|
author_email="arif@hypertest.co, saksham@hypertest.co, karan@hypertest.co",
|
|
13
13
|
description="Production-grade, non-breaking live debugger and telemetry agent for Python.",
|