
Piano di controllo della sicurezza per agenti LLM: allowlist, kill switch del proprietario, sessioni PIN, limiti di frequenza, rilevamento delle iniezioni di prompt e pulizia dell'output per bloccare la fuga di segreti e l'esfiltrazione tramite beacon nelle immagini.

Security control plane per agenti LLM su chat private (tipicamente DM Discord).
Si posiziona davanti al tuo agente. Decide chi può parlare, se la sessione è sbloccata, se il processo è in pausa e se questo messaggio è abbastanza sicuro da inoltrare. Il tuo modello e i tuoi strumenti restano dietro questo gate. La libreria non chiama un LLM. Non implementa funzionalità di prodotto oltre alla sicurezza.
Ispirato a Hermes. Il design segue le stesse idee di control plane usate nei gateway di messaggistica di Hermes Agent: consegna prioritaria via DM, allowlist di identità, apertura in stile pairing, kill switch del proprietario e una netta separazione tra chi può agire (control plane) e testo del messaggio che il modello vede (data plane). Questo pacchetto è una piccola estrazione autonoma di quel pattern per qualsiasi agente invocabile. Non affiliato a Nous Research.
Maturità: implementato · validato in modo indipendente · mantenuto. Vedi STATUS.md.
Riproduzione: python scripts/repro.py (si aspetta REPRO_OK).
Test offline:
pip install -e ".[dev]" # or: pip install -e . && pip install pytest
python -m pytest -q --tb=line
# or: python scripts/repro.py
Live: https://github.com/SamsonCyber/agentic-dm-gateway
Se metti un agente su Discord (o su qualsiasi API di chat) con degli strumenti, chiunque possa scrivere al bot può provare a:
Ti serve un control plane (controlli di identità e processo) separato dal data plane (il testo del messaggio che il modello vede).
Questo pacchetto è quel control plane.
Ambito: solo security gate. Non è un chatbot, un bot di trading, uno scanner o un framework per agenti. Passa un agent(user_id, text) -> str (o asincrono) se usi Discord. Il core funziona con qualsiasi ID utente intero e testo semplice.
$ 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
Tre percorsi di integrazione. Scegline uno.
Installa con il supporto Discord, punta le variabili d'ambiente ai tuoi ID utente, registra il gateway e avvia il bot.
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
Nel tuo bot:
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)
Cosa fa register_dm_gateway:
on_message sul tuo discord.Client / bot.InboundSecurityPipeline.precheck prima del tuo agente.agent(user_id, sanitized_text, is_owner=...).I messaggi guild non arrivano mai all'agente. Solo i DM degli utenti in allowlist lo raggiungono.
on_message)Se non puoi usare register_dm_gateway (catena di handler esistente), chiama tu stesso la pipeline:
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])
Nessuna importazione Discord richiesta. Usa la stessa precheck intorno a ogni turno dell'agente:
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
Campi di PrecheckResult:
run_agent: inoltra al modello solo se truesanitized_text: input ripulitoreply_text: risposta di rifiuto / comando di controllostage: allowlist | kill | pin | rate | injection | ok | ...Checklist di integrazione:
InboundSecurityPipeline una volta all'avvio del processo (config + env).pre = pipe.precheck(user_id, text).pre.run_agent: chiama il tuo agente solo con pre.sanitized_text.sanitize_agent_output prima dell'invio./auth, /kill, …) come gestite quando 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
Control plane: chi è l'utente (allowlist / owner). Data plane: corpo del messaggio (sempre non attendibile finché i controlli non passano).
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
| Modulo | Responsabilità |
|---|---|
SecurityGateway | Unico check_message(user_id, text) -> SecurityVerdict |
InboundSecurityPipeline | Allowlist + comandi slash + gateway in un'unica chiamata |
DiscordDMGateway | Adapter solo DM; inietti tu la funzione agente |
Zero dipendenze runtime obbligatorie. Discord è opzionale: 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
Directory di stato predefinita: ./data/agentic_dm/.
Non chiamano mai il tuo modello.
MIT. Vedi LICENSE.
| Controllo | Comportamento |
|---|
| Allowlist | Solo gli ID utente configurati possono procedere. Tutti gli altri vengono scartati (in silenzio o con una breve stringa di rifiuto). |
| Owner vs friend | Gli owner saltano il PIN e possono mettere in pausa l'intero agente. Gli amici (friend) possono aver bisogno di un PIN condiviso per un'apertura a tempo limitato (idea di pairing in stile Hermes, semplificata). |
| Kill switch | File di pausa globale o flag di ambiente. Nessun turno dell'agente mentre è attivo. |
| Rate limits | Finestra scorrevole per utente (al minuto e all'ora). |
| Input checks | Lunghezza massima, rimozione di caratteri di controllo anomali, euristiche regex per frasi comuni di injection / ricerca di segreti. |
| Output scrub | Redige i token con forma di segreto (chiavi API, JWT, header Bearer) e rimuove i beacon immagine markdown/HTML che possono esfiltrare dati tramite auto-fetch. |
| Audit log | JSONL append-only di eventi allow/deny/auth/kill per una revisione successiva. |
| Local commands | /auth, /lock, /kill, /unkill, /status gestiti senza chiamare un modello. |
| Key | Default | Significato |
|---|
allowed_user_ids | [] | ID utente autorizzati a chattare |
owner_ids | [] | Salta il PIN; può eseguire /kill |
pin_enabled | True | Gate PIN per i non-owner |
pin_ttl_hours | 72 | durata dell'apertura |
rate_limit_per_minute | 8 | Finestra scorrevole |
rate_limit_per_hour | 60 | Finestra scorrevole |
max_input_chars | 2000 | Lunghezza massima input |
block_injection | True | Lista di blocco euristica |
deny_message | False | Silenzioso, True o stringa personalizzata |
audit_log | True | Scrive l'audit JSONL |
enabled | True | Interruttore principale |
| Variable | Scopo |
|---|
AGENTIC_DM_ALLOWLIST | ID utente separati da virgola |
AGENTIC_DM_OWNER_ID | ID proprietario/i |
AGENTIC_DM_PIN | PIN in chiaro |
AGENTIC_DM_PIN_REQUIRED | 1 = richiedi il PIN anche se non impostato |
AGENTIC_DM_KILLED | 1 = kill switch attivo |
AGENTIC_DM_DATA_DIR | Directory per kill file, apertura, audit log |
AGENTIC_DM_SECRETS_DIR | Directory per dm_pin.txt / dm_allowlist.txt |
| Comando | Chi | Effetto |
|---|
/kill /pause | owner | Mette in pausa l'agente per tutti |
/unkill /resume | owner | Rimuove la pausa |
/status | owner | Snapshot di kill / PIN / allowlist |
/auth <pin> | allowlisted | apre la sessione per la durata del TTL |
/lock | allowlisted | Rimuove l'apertura |