What Hermes's Blocklist Taught Us About Agent Guardrails
Hermes Agent does something most harnesses don’t: it publishes exactly what it refuses to run. Its approval module ships 12 hardline patterns (blocked unconditionally, even in yolo mode) and about 47 dangerous patterns (escalated to an inline human prompt). All of it is readable source.
We read it, compared it line-by-line to ACP’s risk classifier, and adopted five categories. This post is the comparison — including what Hermes does better than us, and why we still think a control plane belongs above harness-native guardrails.
What their list gets right
The patterns carry visible battle scars. rm -rf "/" — quoted, so a naive path match misses it. /private/etc/sudoers — on macOS, /etc is a symlink into /private, so a plain /etc/ check is bypassable by spelling. git reset --h — git accepts unambiguous long-flag prefixes, so matching the literal --hard isn’t enough. Each of those is a lesson someone learned in production, encoded as a regex. You don’t get that from a threat-modeling session; you get it from running agents.
The categories we adopted into ACP’s classifier, credited to their list:
Self-protection. Hermes blocks commands that kill or reconfigure its own gateway — an agent shouldn’t be able to un-govern itself mid-task. We had nothing equivalent: an agent could edit the hook config or credentials that govern it and go dark. Now governance-tamper is a first-class category — editing ~/.acp, hook settings, or uninstalling the plugin classifies high on both the shell and file-edit channels. This is their best idea, and we adopted it the week we read it — governance-tamper is now a first-class category.
Decode-pipe obfuscation. base64 -d | sh, xxd -r | sh, echo | tr | sh, openssl enc -d | sh, heredocs, powershell -enc. Our laundering detection caught curl | sh and sh -c but none of the decode pipes. The payload enters the shell in an encoding no pattern-matcher can read — that’s laundering by construction.
Path beats tool. A file write to src/ and a file write to ~/.ssh/authorized_keys are not the same action, and a classifier that rates “Write” as one thing misses that. Writes to SSH keys, shell rc files (persistence), ~/.netrc/.npmrc/.pypirc (credential exfil and supply chain), /etc and /private/etc, and project .env files now classify high across every channel — redirect, tee, cp/mv, sed -i, and the edit tools.
Destructive SQL. DROP, TRUNCATE, DELETE without WHERE — scoped to database clients so the word “DROP” in ordinary text never fires. Agents run SQL constantly; nobody’s guardrails seem to notice.
A hardline floor. ACP had no unconditional tier — everything was policy-configurable. Now a small set of catastrophic patterns (recursive delete of root/home/system dirs with the quoting hardening above, mkfs, dd onto block devices, fork bombs, kill -1, shutdown) denies above policy. No tenant setting or standing approval can allow them. The bar for that list: no legitimate agent task ever needs it.
What a harness blocklist can’t do
Hermes’s guardrails are good. They are also, structurally, local: one machine, one harness, one process, regex-only, and configured by whoever runs the agent — which is often the agent’s own environment.
The gaps a control plane covers are not about better regexes:
Identity. A local blocklist knows what ran, not who. Server-side, every call is attributed to a user, key, and agent — and the delegation chain when agents spawn agents. “Which agent did this, under whose authority” is not a question a regex answers.
Policy that survives the machine. Local config can be edited by anything with file access — including the agent it constrains (see self-protection, above — the difference is our floor lives server-side, where the agent can’t reach it). Fleet-wide policy set once applies to every machine and every harness: Hermes, Claude Code, Codex, MCP clients, same rules.
Approvals with memory. Hermes’s inline prompt is genuinely good UX — we escalate to it rather than around it. But a local prompt at 3am on a server you’re not watching is a denial, not an approval. Server-side asks notify you where you are, and an approval becomes a standing grant with an expiry — reviewed once, enforced deterministically after.
The audit trail and the bill. What actually ran, across everything, priced. Guardrails prevent; they don’t explain. Most of what we’ve learned about our own agents came from the trail, not the blocks.
The pairing is the point. Harness-native guardrails are the reflexes; the control plane is the record, the identity, and the policy that no single machine owns. We run both — the hermes-acp plugin escalates ACP policy into Hermes’s own approval prompt, so you keep their UX and get our floor.
This is part one of a series. Part two surveys what every agent harness ships by default — and what survives yolo mode.
Full comparison and the adoption commit: gatewaystack-connect#304. Credit where due: NousResearch/hermes-agent, tools/approval.py.