fix(security): команда с тильдой обходила защиту каталога учётных данных
Проверка A37 исполнением. Защита границы рабочей области работает, обходы
через ../ и ~ в validate_path отсекаются, каталоги учётных данных закрыты.
Но в validate_command нашлась дыра ровно в том месте, ради которого guard и
делался.
Тильда и переменные окружения не раскрывались перед проверкой. Путь
"~/.hermes/agy_profiles" не считался абсолютным, склеивался с каталогом
проекта в путь с буквальным "~" внутри и признавался допустимым. Измерено:
rm -rf ~/.hermes/agy_profiles РАЗРЕШЕНО
rm -rf ~/.ssh РАЗРЕШЕНО
rm -rf $HOME/.hermes РАЗРЕШЕНО
тот же путь абсолютным отказ
тот же путь через validate_path отказ
То есть самый естественный способ написать опасную команду обходил защиту, а
абсолютный путь — нет. После правки все три отклоняются, штатное удаление
внутри проекта по-прежнему разрешено.
Добавлен тест, удерживающий это свойство.
Проверено отдельно, что защита не ломает продукт: страница, app.js, health и
snapshot отдают 200, в снапшоте 13 ролей, секретов в ответе нет, проверка
обновлений работает. 508 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c8f9b5a382
commit
88d579af08
2 changed files with 41 additions and 1 deletions
|
|
@ -321,7 +321,17 @@ class WorkspaceBoundaryGuard:
|
||||||
return False, f"Команда {cmd_name} запущена в недопустимом каталоге: {reason}", alt
|
return False, f"Команда {cmd_name} запущена в недопустимом каталоге: {reason}", alt
|
||||||
else:
|
else:
|
||||||
for target_arg in targets:
|
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")
|
ok, reason, alt = self.validate_path(target_path, operation="delete")
|
||||||
if not ok:
|
if not ok:
|
||||||
return False, f"Команда '{cmd_name}' пытается удалить недопустимый путь '{target_arg}': {reason}", alt
|
return False, f"Команда '{cmd_name}' пытается удалить недопустимый путь '{target_arg}': {reason}", alt
|
||||||
|
|
|
||||||
|
|
@ -366,3 +366,33 @@ class TestCredentialsDeletionGuard:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client():
|
def client():
|
||||||
return TestClient(app)
|
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, "защита мешает штатной работе внутри проекта"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue