diff --git a/src/antigravity_provider/router/security_guard.py b/src/antigravity_provider/router/security_guard.py index 8bfb901..186b7bb 100644 --- a/src/antigravity_provider/router/security_guard.py +++ b/src/antigravity_provider/router/security_guard.py @@ -321,7 +321,17 @@ class WorkspaceBoundaryGuard: return False, f"Команда {cmd_name} запущена в недопустимом каталоге: {reason}", alt else: for target_arg in targets: - target_path = (base_cwd / target_arg) if not Path(target_arg).is_absolute() else Path(target_arg) + # Тильда и переменные окружения раскрываются ДО проверки. + # + # Без этого "rm -rf ~/.hermes/agy_profiles" не считался + # абсолютным путём, склеивался с каталогом проекта в путь с + # буквальным "~" внутри и признавался допустимым. Проверено: + # команда с тильдой проходила, та же команда с абсолютным + # путём отклонялась. То есть самый естественный способ + # написать опасную команду обходил защиту ровно там, ради + # чего она и делалась — на каталоге учётных данных. + expanded = os.path.expandvars(os.path.expanduser(target_arg)) + target_path = Path(expanded) if Path(expanded).is_absolute() else (base_cwd / expanded) ok, reason, alt = self.validate_path(target_path, operation="delete") if not ok: return False, f"Команда '{cmd_name}' пытается удалить недопустимый путь '{target_arg}': {reason}", alt diff --git a/tests/test_a37_isolation_guards.py b/tests/test_a37_isolation_guards.py index 216bad9..731227e 100644 --- a/tests/test_a37_isolation_guards.py +++ b/tests/test_a37_isolation_guards.py @@ -366,3 +366,33 @@ class TestCredentialsDeletionGuard: @pytest.fixture def client(): return TestClient(app) + +def test_destructive_command_with_tilde_is_rejected(monkeypatch, tmp_path): + """Команда с тильдой не должна обходить защиту каталогов учётных данных. + + validate_command не раскрывал "~" и "$HOME" перед проверкой. Путь + "~/.hermes/agy_profiles" не считался абсолютным, склеивался с каталогом + проекта в путь с буквальным "~" внутри и признавался допустимым. + + Измерено на реализации: "rm -rf ~/.hermes/agy_profiles" проходило, а та же + команда с абсолютным путём отклонялась. То есть самый естественный способ + написать опасную команду обходил защиту ровно в том месте, ради которого + она и делалась. + """ + from antigravity_provider.router.security_guard import WorkspaceBoundaryGuard + + monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) + guard = WorkspaceBoundaryGuard() + + must_reject = [ + "rm -rf ~/.hermes/agy_profiles", + "rm -rf ~/.ssh", + "rm -rf $HOME/.hermes", + ] + for cmd in must_reject: + allowed, reason, _alt = guard.validate_command(cmd) + assert not allowed, f"команда с тильдой прошла мимо защиты: {cmd} ({reason})" + + # Обычная работа внутри проекта не должна страдать. + allowed, _reason, _alt = guard.validate_command("rm src/temp_file.py") + assert allowed, "защита мешает штатной работе внутри проекта"