
Hardening Hermes — ai-jail, bwrap, and the Day Secrets Stopped Leaking
Hermes Could Leak My Life at Any Moment
My Hermes agent runs terminal commands all the time: npm install, pip install, arbitrary Python scripts. And it has access to everything — API tokens, Stripe keys, bank credentials, Bitwarden SM secrets.
A malicious package in npm, a Python script with os.environ, an accidental curl to the wrong endpoint — and my secrets would land somewhere they shouldn’t.
I needed a barrier. “Just being careful” wasn’t going to cut it.
Meeting ai-jail
ai-jail is a Rust sandbox that combines three isolation layers:
- bubblewrap — user namespaces, mounts only what’s needed
- Landlock — kernel-level file access restrictions (Linux 5.13+)
- Seccomp — specific syscall filtering
The idea: every command Hermes runs, runs inside a cage. The project has write permissions, but ~/.env, secrets/, *.pem — all masked or denied.
The Struggle — Three Modes, One Hook, and a Playbook
Mode 1: Normal
The default. Project is read-write, home is tmpfs (gone on exit), and .ai-jail security rules filter what’s not allowed.
jail npm install
jail python3 script.py
Mode 2: Worker (–worker)
For disposable tasks. No GPU, no Docker, no display. The Kanban worker enters this mode automatically.
jail --worker python3 analyze.py
Mode 3: Lockdown (–lockdown)
Everything read-only. No network. No GPU. Just the binary and its libs. For production deploys, database restores, critical operations.
jail --lockdown pg_restore ...
jail --lockdown alembic upgrade head
The Auto Hook
The breaking point was having to remember to type jail before every command. I installed a hook in ~/.bashrc.d/jail-auto.sh that detects when I enter a directory with .ai-jail and activates the sandbox automatically — npm, pip, python, pnpm, cargo all become jail without thinking.
# ~/.bashrc.d/jail-auto.sh (abbreviated)
enter_project() {
if [ -f "$PWD/.ai-jail/config.toml" ]; then
alias npm='jail npm'
alias pip='jail pip'
alias python3='jail python3'
fi
}
cd() { builtin cd "$@" && enter_project; }
When I leave the project directory, the aliases disappear. Zero mental overhead.
The IR Playbook
To complete the picture, I created an incident response playbook (~/.config/hermes/scripts/ir-playbook.sh) with 5 scenarios:
| Playbook | Trigger | Action |
|---|---|---|
| Port exposed | Unexpected open port | Block via iptables + notify |
| Disk critical | Disk < 10% | Clean cache + alert |
| Backup fail | Backup didn’t run | Retry + escalate |
| Breach | Suspected intrusion | Isolate + capture evidence |
| Secrets leak | Secret leaked | Rotate + Bitwarden SM |
And Gitleaks in TatuEngine’s CI — every time someone commits a credential, CI rejects it automatically.
Resolution
The sandbox is now integrated at three layers:
- Hermes terminal() — direct commands run via
jail - Workers (delegate_task/Kanban) — automatic worker mode, no GPU/Docker
- Arachne — sandbox for pipeline stages (code, math, jsonata) and browser
And the best part: when jail --dry-run python3 script.py shows what will be mounted, you see exactly what each command can access. No “wait for a disaster to find out.”
Metrics
| Metric | Before | After |
|---|---|---|
| Attack surface | Everything accessible | Only what’s needed |
| Protection modes | 0 | 3 (normal, worker, lockdown) |
| Secrets masked at home | 0 | 15+ (.env, *.pem, secrets/) |
| IR playbooks | 0 | 5 |
| False positives (broken by jail) | — | 0 so far |
Lessons Learned
- Security isn’t a feature, it’s a layer — you can’t bolt it on at the end. ai-jail was configured before any worker hit production.
- The auto hook was the real MVP — if I had to remember to type
jail, half the commands would run unprotected. - Sandboxing doesn’t kill productivity — on WSL, the jail adds ~1.4s per command. Acceptable for not leaking Stripe keys.