From 184d8cd9b1e72c42cbe5a5d69b6d8c16e37bc229 Mon Sep 17 00:00:00 2001 From: Harneet Singh <62827797+harneet2512@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:00:45 -0400 Subject: [PATCH] security: require approval for persistent memory mutations --- coworker/permissions.py | 4 ++++ tests/test_permissions_risk.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/coworker/permissions.py b/coworker/permissions.py index 9e822feb1..3397b1b13 100644 --- a/coworker/permissions.py +++ b/coworker/permissions.py @@ -87,6 +87,10 @@ def _is_prefix_eligible(argv: list[str]) -> bool: "create_scheduled_task", "update_scheduled_task", "delete_scheduled_task", + # These mutations persist beyond the current session. + "remember", + "memory_update", + "memory_forget", } diff --git a/tests/test_permissions_risk.py b/tests/test_permissions_risk.py index c5619bafc..fd7cb81f0 100644 --- a/tests/test_permissions_risk.py +++ b/tests/test_permissions_risk.py @@ -15,6 +15,9 @@ EXTERNAL_META = SimpleNamespace(requires_approval=True, category="connector") PLAIN_META = SimpleNamespace(requires_approval=False) +MEMORY_META = SimpleNamespace( + requires_approval=False, category="memory", risk_level="low", capabilities=["remember"] +) # -- classify ------------------------------------------------------------------- @@ -94,6 +97,24 @@ def test_external_asks_in_interactive_allows_in_auto(tmp_path): assert d.allowed +@pytest.mark.parametrize("mode", list(Mode)) +@pytest.mark.parametrize( + "name,args", + [ + ("remember", {"content": "a durable fact", "scope": "global"}), + ("memory_update", {"memory_id": 1, "content": "corrected fact"}), + ("memory_forget", {"memory_id": 1}), + ], +) +def test_memory_mutations_are_human_only_in_every_mode(tmp_path, mode, name, args): + eng = PermissionEngine(workspace_root=tmp_path, mode=mode) + decision = eng.evaluate(name, args, MEMORY_META) + assert not decision.allowed + assert decision.needs_user + assert decision.human_only + assert "outlives the session" in decision.reason + + def test_write_local_path_scoped(tmp_path): eng = PermissionEngine(workspace_root=tmp_path, mode=Mode.BYPASS_APPROVALS) assert eng.evaluate("write_file", {"path": "ok.py", "content": "x"}, None).allowed