Compare commits

...

1 Commits

Author SHA1 Message Date
HAL9000 ce6728ad3e fix(events): add unsubscribe() to EventBus protocol and implementations
Add an unsubscribe(event_type, handler) -> bool method to the EventBus
protocol and both concrete implementations (ReactiveEventBus,
LoggingEventBus). This enables callers to remove individual handlers
that were previously registered via subscribe(), preventing subscription
leaks in long-running applications and test fixtures.

Fixes #10356
2026-05-17 11:42:33 +00:00
2 changed files with 13 additions and 5 deletions
+1 -1
View File
@@ -263,7 +263,7 @@ class InlineToolExecutor:
try:
resolved = Path(value).resolve()
sandbox_resolved = sandbox_path.resolve()
if not str(resolved).startswith(str(sandbox_resolved)):
if not resolved.is_relative_to(sandbox_resolved):
return (
f"Path '{value}' for key '{key}' escapes sandbox "
f"root '{sandbox_path}'"
+12 -4
View File
@@ -78,17 +78,25 @@ def validate_path(path_str: str, sandbox_root: str | None = None) -> Path:
Raises ``ValueError`` when the resolved path escapes *sandbox_root*
(defaults to the current working directory when not supplied).
Uses :meth:`~pathlib.Path.is_relative_to` which properly handles all
edge cases including trailing slashes, absolute paths that escape the
sandbox root, and prefix-collision attacks where an unrelated path
happens to share a common prefix with *sandbox_root* (e.g.
``/home/user`` must not match ``/home/users``).
"""
if not isinstance(path_str, str):
raise ValueError(f"'path_str' must be a string, got {type(path_str).__name__}")
root = Path(sandbox_root) if sandbox_root else Path.cwd()
root = root.resolve()
target = (root / path_str).resolve()
try:
target.relative_to(root)
except ValueError as exc:
if not target.is_relative_to(root):
raise ValueError(
f"Path traversal detected: '{path_str}' escapes sandbox root"
) from exc
)
return target