"""The read-only session grant reads the session's folders, not the machine (OPE-130). `readonly.py` vets what a command DOES — carefully, and fail-closed. It said nothing about what a command READS, so a grant the user reads as "stop asking about my project files" also covered ~/.aws/credentials, another repository's history, and OpenWorker's own secrets file. The self-protection floor does not cover that: it guards those files against modification, not reading. """ from __future__ import annotations from pathlib import Path import pytest from coworker.permissions import Mode, PermissionEngine from coworker.readonly import is_readonly_command, read_targets @pytest.fixture() def session(tmp_path): """A session whose granted root is `repo/`, with the grant already clicked.""" root = tmp_path / "repo" (root / "src").mkdir(parents=True) (root / "build.log").write_text("boom", encoding="utf-8") engine = PermissionEngine(workspace_root=root, mode=Mode.INTERACTIVE) engine.allow_readonly_for_session() return engine def runs(engine, command: str) -> bool: return engine.evaluate("run_shell", {"command": command}, None).allowed # -- the grant still does the job it was built for -------------------------------- # Born of ~15 hand-approvals per security-scan run; scoping must not undo that. @pytest.mark.parametrize( "command", [ "cat build.log", "grep AWS_SECRET build.log", "head -n 5 build.log", "ls src", "git status", "git log --oneline", "pwd", "whoami", "cat build.log | grep error", "find . -name '*.py'", ], ) def test_reads_inside_the_granted_folder_still_run_unprompted(session, command): assert runs(session, command), command # -- and no longer reads the rest of the machine ---------------------------------- @pytest.mark.parametrize( "command", [ "cat ~/.aws/credentials", "strings ~/.ssh/id_rsa", "cat /etc/shadow", "grep -r AWS_SECRET /", "find / -name id_rsa", "head -n 5 ~/.aws/credentials", ], ) def test_reads_outside_every_root_now_ask(session, command): assert not runs(session, command), command def test_openworkers_own_secrets_are_no_longer_readable(session): # The floor hard-denies WRITES to this file in every mode and cannot be overridden. # Reading was never checked, so one click on a convenience button dumped it into the # transcript — and from there to the model provider on the next turn. assert not runs(session, "cat ~/.config/coworker/secrets.json") assert not runs(session, "jq . ~/.config/coworker/secrets.json") @pytest.mark.parametrize( "command", [ r"cat $env:APPDATA\coworker\secrets.json", r"cat $HOME/.config/coworker/secrets.json", r"cat ${HOME}/.config/coworker/secrets.json", ], ) def test_variable_expansion_cannot_hide_an_out_of_scope_read(session, command): assert not runs(session, command) def test_another_repository_is_not_in_scope(session): # `git -C