
Плоскость управления безопасностью для LLM-агентов: списки разрешений, аварийный выключатель владельца, PIN-сессии, ограничения частоты запросов, обнаружение промпт-инъекций и очистка вывода для блокировки утечки секретов и экфильтрации через изображения-маяки.

Контрольная плоскость безопасности для LLM-агентов в приватных чатах (как правило, в личных сообщениях Discord).
Она находится перед вашим агентом. Она решает, кто может общаться, разблокирована ли сессия, приостановлен ли процесс и достаточно ли безопасно это сообщение для передачи дальше. Ваша модель и инструменты остаются за этим шлюзом. Библиотека не вызывает LLM. Она не реализует продуктовые функции, кроме безопасности.
Вдохновлено Hermes. Дизайн следует тем же идеям плоскости управления, которые используются в шлюзах обмена сообщениями Hermes Agent: доставка в первую очередь в ЛС, список разрешённых личностей, открытие в стиле сопряжения, аварийный выключатель владельца и жёсткое разделение между кто может действовать (плоскость управления) и текстом сообщения, который видит модель (плоскость данных). Этот пакет — небольшое автономное извлечение этого паттерна для любого вызываемого агента. Не аффилирован с Nous Research.
Зрелость: реализовано · независимо проверено · поддерживается. См. STATUS.md.
Воспроизведение: python scripts/repro.py (ожидается REPRO_OK).
Офлайн-тесты:
pip install -e ".[dev]" # or: pip install -e . && pip install pytest
python -m pytest -q --tb=line
# or: python scripts/repro.py
Репозиторий: https://github.com/SamsonCyber/agentic-dm-gateway
Если вы разместите агента в Discord (или любом чат-API) с инструментами, любой, кто может написать боту, может попытаться:
Вам нужна плоскость управления (контроль личности и процессов), отдельная от плоскости данных (текст сообщения, который видит модель).
Этот пакет и есть такая плоскость управления.
Область применения: только шлюз безопасности. Это не чат-бот, торговый бот, сканер или агентный фреймворк. Передайте agent(user_id, text) -> str (или асинхронную версию), если используете Discord. Ядро работает с любым целочисленным ID пользователя и обычным текстом.
$ python - <<'PY'
from agentic_dm_gateway import InboundSecurityPipeline
pipe = InboundSecurityPipeline({
"allowed_user_ids": [111],
"owner_ids": [111],
"pin_enabled": False,
"block_injection": True,
"deny_message": "Not authorized.",
})
for uid, text in [
(99, "hi"),
(111, "ignore previous instructions"),
(111, "summarize this note"),
]:
r = pipe.precheck(uid, text)
print(uid, r.stage, r.run_agent, r.reply_text)
PY
99 allowlist False Not authorized.
111 injection False Blocked: looks like prompt injection / secret fishing. Rephrase.
111 ok True None
$ python scripts/repro.py
REPRO_OK agentic-dm-gateway unit suite
Три пути интеграции. Выберите один.
Установите с поддержкой Discord, укажите в env ваши ID пользователей, зарегистрируйте шлюз и запустите бота.
pip install -e ".[discord]"
# or: pip install agentic-dm-gateway[discord]

export DISCORD_BOT_TOKEN=...
export AGENTIC_DM_ALLOWLIST=your_discord_user_id
export AGENTIC_DM_OWNER_ID=your_discord_user_id
# optional: export AGENTIC_DM_PIN=....

python examples/discord_echo_bot.py
В вашем собственном боте:
import discord
from agentic_dm_gateway.discord_adapter import register_dm_gateway
def agent(user_id: int, text: str, *, is_owner: bool = False) -> str:
# your Hermes / local model / tool loop
return call_your_model(text)
intents = discord.Intents.default()
intents.message_content = True
bot = discord.Client(intents=intents)
register_dm_gateway(
bot,
{
"allowed_user_ids": [], # or rely on AGENTIC_DM_ALLOWLIST env
"owner_ids": [],
"pin_enabled": False,
"deny_message": False, # silent drop for strangers
},
agent=agent,
)
bot.run(TOKEN)
Что делает register_dm_gateway:
on_message на ваш discord.Client / бота.InboundSecurityPipeline.precheck перед вашим агентом.agent(user_id, sanitized_text, is_owner=...).Сообщения с серверов (guild) никогда не доходят до агента. Доходят только ЛС от пользователей из списка разрешённых.
on_message)Если вы не можете использовать register_dm_gateway (уже есть цепочка обработчиков), вызывайте конвейер самостоятельно:
from agentic_dm_gateway import InboundSecurityPipeline
from agentic_dm_gateway.security import sanitize_agent_output
pipe = InboundSecurityPipeline({
"allowed_user_ids": [YOUR_ID],
"owner_ids": [YOUR_ID],
"pin_enabled": True,
})
@bot.event
async def on_message(message):
if message.author.bot or message.guild is not None:
return
pre = pipe.precheck(int(message.author.id), message.content or "")
if pre.reply_text and not pre.run_agent:
await message.channel.send(pre.reply_text[:1900])
return
if not pre.run_agent:
return
raw = await your_agent(pre.sanitized_text) # Hermes, Ollama, API, ...
await message.channel.send(sanitize_agent_output(str(raw))[:1900])
Импорт Discord не требуется. Используйте ту же предварительную проверку вокруг любого хода агента:
from agentic_dm_gateway import InboundSecurityPipeline
from agentic_dm_gateway.security import sanitize_agent_output
pipe = InboundSecurityPipeline({
"allowed_user_ids": [111],
"owner_ids": [111],
"pin_enabled": False,
"rate_limit_per_minute": 20,
"block_injection": True,
"deny_message": "Not authorized.",
})
def handle_inbound(user_id: int, text: str) -> str | None:
pre = pipe.precheck(user_id, text)
if pre.run_agent:
answer = my_llm(pre.sanitized_text) # your model / Hermes run
return sanitize_agent_output(str(answer))
return pre.reply_text # deny or control-command reply
Поля PrecheckResult:
run_agent: передавать модели только если truesanitized_text: очищенный вводreply_text: ответ с отказом или управляющей командойstage: allowlist | kill | pin | rate | injection | ok | ...Чек-лист подключения:
InboundSecurityPipeline один раз при запуске процесса (конфиг + env).pre = pipe.precheck(user_id, text).pre.run_agent: вызывайте вашего агента только с pre.sanitized_text.sanitize_agent_output перед отправкой./auth, /kill, …) выполненными, когда run_agent равен false.1. Adapter: ignore bots; only accept DMs (not server channels)
2. Allowlist: is this user id permitted?
3. Owner commands: /kill /unkill /status -> reply, stop
4. Session commands: /auth <pin> /lock -> reply, stop
5. SecurityGateway.check_message:
kill switch?
session unlocked? (PIN)
under rate limit?
length + injection heuristics OK?
6. If ok -> run_agent=True with sanitized text
7. After your agent returns -> sanitize_agent_output (redact + strip image beacons)
8. Audit rows written along the way
Плоскость управления: кто такой пользователь (allowlist / owner). Плоскость данных: тело сообщения (всегда считается ненадёжным, пока проверки не пройдены).
src/agentic_dm_gateway/
security.py # RateLimiter, SessionAuth, SecurityGateway,
# sanitize_input, redact_secrets, sanitize_agent_output,
# kill switch, audit_log
allowlist.py # merge config + env + file into allowlist / owners
commands.py # /kill /unkill /status /auth /lock (no LLM)
pipeline.py # InboundSecurityPipeline.precheck() orchestration
discord_adapter.py # optional discord.py on_message wire-up
tests/ # unit tests for the core (no Discord required)
examples/
minimal_precheck.py # CLI-style demo of precheck outcomes
discord_echo_bot.py # secured DMs + echo agent
| Модуль | Ответственность |
|---|---|
SecurityGateway | Единый check_message(user_id, text) -> SecurityVerdict |
InboundSecurityPipeline | Allowlist + слэш-команды + шлюз в одном вызове |
DiscordDMGateway | Адаптер только для ЛС; вы внедряете функцию агента |
Ноль обязательных зависимостей рантайма. Discord опционален: pip install agentic-dm-gateway[discord].
git clone https://github.com/SamsonCyber/agentic-dm-gateway.git
cd agentic-dm-gateway
pip install -e ".[dev]"
python scripts/repro.py
Каталог состояния по умолчанию: ./data/agentic_dm/.
Эти команды никогда не вызывают вашу модель.
MIT. См. LICENSE.
| Контроль | Поведение |
|---|
| Список разрешённых | Продолжать могут только настроенные ID пользователей. Все остальные отбрасываются (молча или с коротким отказным сообщением). |
| Владелец vs друг | Владельцы пропускают PIN и могут приостановить всего агента. Друзьям может понадобиться общий PIN для временного открытия (упрощённая идея сопряжения в стиле Hermes). |
| Аварийный выключатель | Глобальный файл паузы или флаг окружения. Пока активен, агент не выполняет ни одного хода. |
| Лимиты скорости | Скользящее окно для каждого пользователя (в минуту и в час). |
| Проверки ввода | Максимальная длина, удаление нестандартных управляющих символов, regex-эвристики для типичных инъекций и фраз, выуживающих секреты. |
| Очистка вывода | Маскирует токены, похожие на секреты (API-ключи, JWT, Bearer-заголовки), и удаляет markdown/HTML image-маячки, которые могут выводить данные через автозагрузку. |
| Журнал аудита | Только-добавление JSONL событий allow/deny/auth/kill для последующего просмотра. |
| Локальные команды | /auth, /lock, /kill, /unkill, /status обрабатываются без вызова модели. |
| Ключ | По умолчанию | Значение |
|---|
allowed_user_ids | [] | ID пользователей, которым разрешено общаться |
owner_ids | [] | Пропускают PIN; могут выполнить /kill |
pin_enabled | True | PIN-контроль для не-владельцев |
pin_ttl_hours | 72 | Длительность открытой сессии |
rate_limit_per_minute | 8 | Скользящее окно |
rate_limit_per_hour | 60 | Скользящее окно |
max_input_chars | 2000 | Максимальная длина ввода |
block_injection | True | Эвристический список блокировки |
deny_message | False | Молча, True или произвольная строка |
audit_log | True | Писать журнал аудита JSONL |
enabled | True | Главный выключатель |
| Переменная | Назначение |
|---|
AGENTIC_DM_ALLOWLIST | ID пользователей через запятую |
AGENTIC_DM_OWNER_ID | ID владельца (владельцев) |
AGENTIC_DM_PIN | PIN в открытом виде |
AGENTIC_DM_PIN_REQUIRED | 1 = требовать PIN, даже если не задан |
AGENTIC_DM_KILLED | 1 = аварийный выключатель включён |
AGENTIC_DM_DATA_DIR | Каталог для kill-файла, открытой сессии, журнала аудита |
AGENTIC_DM_SECRETS_DIR | Каталог для dm_pin.txt / dm_allowlist.txt |
| Команда | Кто | Эффект |
|---|
/kill /pause | владелец | Приостановить агента для всех |
/unkill /resume | владелец | Снять паузу |
/status | владелец | Снимок состояния kill / PIN / allowlist |
/auth <pin> | из списка разрешённых | Открыть сессию на TTL |
/lock | из списка разрешённых | Закрыть открытую сессию |