flawopen.com/Справочник/Безопасность PR-ботов
PR-боты на ИИ: защита от SSRF через Prompt Injection
Как недоверенный текст pull request может влиять на инструменты ИИ и как ограничить права бота и сетевой доступ.
ELI5 — помощник по проверке с пропуском во внутреннюю сеть
Pull request похож на посылку для команды проверки: её содержимое может быть полезным, но не становится политикой компании. Если ИИ-помощник может получать любые URL из частной сети, вводящая в заблуждение инструкция может повлиять на цель запроса. Риск зависит от прав инструмента и сети; чтение вредоносного текста само по себе не даёт сетевого доступа.
Основные понятия и термины
- Недоверенный ввод:
- Заголовки, описания, комментарии, diff и ссылки из pull request участников.
- Косвенная Prompt Injection:
- Недоверенный текст влияет на агента, поскольку включён в контекст модели.
- Граница SSRF:
- Серверный инструмент загрузки может достигать узлов, недоступных участнику напрямую.
- Условия воздействия:
- Также нужны доступный внутренний сервис, имеющиеся учётные данные и недостаточные проверки доступа или сетевого выхода.
Механизм и первопричина
Бот проверки может объединять контролируемый участником текст с доверенными инструкциями ревью. Модель может предложить вызов инструмента на основе любого из этих источников. Промпт и разделители не разрешают сетевой доступ. SSRF возможна только если код принимает выбранный моделью адрес, а инструмент с сетевым доступом может его достичь. Доступ к облачным метаданным или учётным данным — дополнительное условное воздействие, а не автоматический результат Prompt Injection.
Условная цепочка атаки
Участник контролирует содержимое PR
Внешний участник может влиять на заголовки, описания, комментарии, изменённый текст и ссылки.
Агент читает этот контент
Сервис помещает некоторые недоверенные поля в контекст модели; это может повлиять на ответ или предложение инструмента.
Инструмент определяет доступность
Если агент может получать произвольные URL, runner может обращаться к адресам, разрешённым сетью. Без такого инструмента этого пути SSRF нет.
Воздействие зависит от развёртывания
Внутренние данные или учётные сведения уязвимы только если это позволяют доступные сервисы, настройки метаданных, права идентичности и контроль вывода.
Код: непроверенный вызов и авторизованное чтение
# INERT DEMO: model-selected URL reaches a mock tool without policy checks
from dataclasses import dataclass
@dataclass
class ToolCall:
name: str
arguments: dict[str, str]
class MockFetch:
def __init__(self):
self.calls = []
def __call__(self, url: str) -> str:
self.calls.append(url)
return "fixture response"
def dispatch_unchecked(call: ToolCall, tools: dict):
# The mock records a string; it makes no network request.
return tools[call.name](**call.arguments)
mock_fetch = MockFetch()
call = ToolCall("fetch_url", {"url": "https://docs.example.invalid/"})
assert dispatch_unchecked(call, {"fetch_url": mock_fetch}) == "fixture response"
assert mock_fetch.calls == ["https://docs.example.invalid/"]
# SAFE DEMO: the agent receives only a read-only PR capability
from dataclasses import dataclass
@dataclass(frozen=True)
class ReviewContext:
repository: str
pull_request: int
@dataclass
class ToolCall:
name: str
arguments: dict[str, object]
READ_ONLY_TOOLS = {"read_pr_diff"}
def dispatch_review_call(call: ToolCall, context: ReviewContext, policy, tools):
if call.name not in READ_ONLY_TOOLS:
raise PermissionError("Tool is not available to this review")
expected = {"repository": context.repository,
"pull_request": context.pull_request}
if call.arguments != expected:
raise ValueError("Arguments must match the authorized review")
if not policy.can_read_pr(context.repository, context.pull_request):
raise PermissionError("Review principal cannot read this pull request")
# The injected read_pr_diff function is trusted app code, not model output.
return tools["read_pr_diff"](**expected)
Инженерный список мер защиты
- Не выдавайте этот сценарий за подтверждённый инцидент с PR-ботом. Страница описывает условный риск проектирования.
- Не подключайте инструмент произвольной загрузки URL к проверяющему, которому нужно только изучить diff.
- Если нужны предпросмотры внешних ссылок, используйте выделенный egress-сервис с allowlist адресов, проверкой DNS/IP при соединении, повторной проверкой редиректов и строгими лимитами времени и размера без ambient credentials.
- Запретите runner доступ к частным сетям и облачным метаданным. Отключите метаданные, если они не нужны; иначе требуйте IMDSv2 и проверьте hop limit для контейнеров.
- Назначьте боту read-only идентичность с ограничением репозиторием. Не предоставляйте секреты заданиям для недоверенных PR и отделяйте привилегированные процессы от внешнего кода.
- В GitHub Actions выбирайте `pull_request` с минимальными правами для недоверенного кода. Считайте `pull_request_target` привилегированным и никогда не выполняйте fork-код с его секретами или write-токеном.
- Записывайте отклонённые вызовы инструментов и решения по адресам, не сохраняя токены, полные частные diff или чувствительные тела ответов.