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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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, "защита мешает штатной работе внутри проекта"
|
||||
|
|
|
|||
Loading…
Reference in a new issue