From 36730b90c4af0fcf51c6a0713b65d259050c5ca6 Mon Sep 17 00:00:00 2001 From: Teknium Date: Wed, 22 Apr 2026 18:26:04 -0700 Subject: [PATCH] fix(gateway): also clear session-scoped approval state on /new Follow-up to the /resume and /branch cleanup in the previous commit: /new is a conversation-boundary operation too, so session-scoped dangerous-command approvals and /yolo state must not survive it. Adds a scoped unit test for _clear_session_boundary_security_state that also covers the /new path (which calls the same helper). --- gateway/run.py | 5 +++ .../test_session_boundary_security_state.py | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/gateway/run.py b/gateway/run.py index 4b6f1dadb..a024649cb 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -4971,6 +4971,11 @@ class GatewayRunner: # the configured default instead of the previously switched model. self._session_model_overrides.pop(session_key, None) + # Clear session-scoped dangerous-command approvals and /yolo state. + # /new is a conversation-boundary operation — approval state from the + # previous conversation must not survive the reset. + self._clear_session_boundary_security_state(session_key) + # Fire plugin on_session_finalize hook (session boundary) try: from hermes_cli.plugins import invoke_hook as _invoke_hook diff --git a/tests/gateway/test_session_boundary_security_state.py b/tests/gateway/test_session_boundary_security_state.py index bd75eca8b..9908badea 100644 --- a/tests/gateway/test_session_boundary_security_state.py +++ b/tests/gateway/test_session_boundary_security_state.py @@ -161,3 +161,41 @@ async def test_branch_clears_session_scoped_approval_and_yolo_state(): assert is_approved(other_key, "recursive delete") is True assert is_session_yolo_enabled(other_key) is True assert other_key in runner._pending_approvals + + +def test_clear_session_boundary_security_state_is_scoped(): + """The helper must wipe only the target session's approval/yolo state. + + Also exercises the /new reset path indirectly: /new calls this helper, + so if the helper is scoped correctly, /new's clearing is correct too. + """ + from gateway.run import GatewayRunner + + runner = object.__new__(GatewayRunner) + runner._pending_approvals = {} + + source = _make_source() + session_key = build_session_key(source) + other_key = "agent:main:telegram:dm:other-chat" + + approve_session(session_key, "recursive delete") + approve_session(other_key, "recursive delete") + enable_session_yolo(session_key) + enable_session_yolo(other_key) + runner._pending_approvals[session_key] = {"command": "rm -rf /tmp/demo"} + runner._pending_approvals[other_key] = {"command": "rm -rf /tmp/other"} + + runner._clear_session_boundary_security_state(session_key) + + # Target session cleared + assert is_approved(session_key, "recursive delete") is False + assert is_session_yolo_enabled(session_key) is False + assert session_key not in runner._pending_approvals + # Other session untouched + assert is_approved(other_key, "recursive delete") is True + assert is_session_yolo_enabled(other_key) is True + assert other_key in runner._pending_approvals + + # Empty session_key is a no-op + runner._clear_session_boundary_security_state("") + assert is_approved(other_key, "recursive delete") is True