Securing the agent from within: protecting tool access and API keys, restricting capabilities to safe operations, and building audit trails and policy enforcement to track and control every agent action.

In episode 10 you gave the agent great power: chained workflows, planning, and automatic retries. The more power a system has, the more serious the duty to secure it. Episode 11 flips the perspective — we are not adding features, but closing gaps and putting up fences.
Here is the roadmap for this episode: securing tool access and API keys, restricting agent capabilities to safe operations, and then building audit trails and policy enforcement.
Security does not start with tools, but with mapping assets and threats. For an agent, the three most important assets are: secrets (API keys), execution capabilities (tools with side effects), and data (memory and tool results). The key question for each asset: who may see it, who may trigger it, and what happens if it is misused.
Aset | Pelaku | Risiko
------------+------------------+-----------------------------
API keys | user, penyerang | Pencurian, pemakaian di luar
Tool exec | prompt injection | Eksekusi perintah berbahaya
Data memory | user lain | Kebocoran data antar sesiAn important note: the biggest actor is not an attacker on the internet, but unexpected input — the content of web pages, documents, or user messages that smuggles malicious instructions into the prompt (prompt injection, already mentioned in episodes 5 and 6). Every fence in this episode is designed with that threat in mind.
Start with the most valuable thing: credentials. The first rule has been repeated since episode 3 — the actual secret values never enter code or committed config. In production, secrets are stored in an external store and injected at runtime (full details in episode 12). Locally, use a dot-env file that is in .gitignore.
Besides storing them safely, also limit the scope of secrets:
import { loadSecret } from "@hermes/security";
const dbCred = await loadSecret("DATABASE_READONLY_URL");
const registry = await loadSecret("REGISTRY_TOKEN", { scopedTo: "registry.example.com" });The loadSecret helper fetches a secret from the configured store, not from code. Make it a habit: every time a new credential is added, ask two things — is its scope as minimal as possible, and would its value leak if the agent log were read.
In episode 4 you learned to set permissions per tool. In episode 11, apply that discipline as a comprehensive policy, not a one-by-one decision. The principle: default deny. Everything not explicitly allowed is automatically forbidden.
security:
default_deny: true
allowed_operations:
- db.query (read_only)
- web.search
- web.fetch (domains: ["*.example.com"])
sandbox:
backend: docker
network: none
secrets:
env_allowlist: ["DATABASE_READONLY_URL"]The sandbox block above answers the code execution threat from episode 4: the agent runs in a docker backend with no network access, so an executed script cannot reach the host or the internet. If the code_execution capability is active, always run it in an isolated sandbox — not on the main host. The default_deny value is the master switch that turns on safe mode overall.
Danger
Default deny is not a suggestion, it is a foundation. Once you start with an allow-list, every new tool automatically requires an explicit decision before it can be executed — that is exactly what you want.
An audit trail is a record of who did what, when, and with what result. Without this record, you cannot confirm a security incident, comply with regulations, or evaluate whether a policy works. In Hermes, every tool call is recorded as an event with complete metadata.
{
"event": "tool.call",
"agent": "ops-agent",
"session": "ses-ops-8821",
"user": "arman@example.com",
"tool": "db.query",
"args_hash": "a1b2c3...",
"result": "success",
"timestamp": "2026-08-03T10:15:30Z",
"duration_ms": 320
}Notice args_hash — a hash of the call arguments, not their raw values. This keeps sensitive values out of the log while still allowing verification. For high-risk operations (payments, deleting data, sending email), store a full argument snapshot in a separate protected store.
An audit trail is useless if it is never read. Make review routine — weekly or monthly — to look for anomalies: tools called outside working hours, denied permissions being probed frequently, or suspicious arguments. This pattern will grow into automatic alerting in episode 20.
Policy enforcement is the layer that enforces rules at the runtime level, not just a hope in the system prompt. Prompts can be persuaded; policies cannot. Hermes loads policies from config and enforces them in the controller before every action is executed.
policy: access-control
rules:
- when: { tool: "http.post", target: "billing-api" }
require: "user.confirmed_payment"
else: deny
- when: { tool: "db.query" }
require: "tool.read_only == true"
else: deny
- when: { tool: "fs.write" }
require: "user.role in ['admin']"
else: denyEvery action passes through these rules in the controller before being executed. If the condition is not met, the action is denied and recorded in the audit trail — the agent never even gets to run its tool. This is the crucial difference from static permissions: policies can consider context such as user role or confirmation status, not just the tool name.
Info
Link policies with the audit trail: every denied action is recorded as a separate event. A frequently appearing violation pattern — for example, the agent trying fs.write repeatedly — is a signal to tighten the prompt or add more sandboxing.
Episode 11 closed the gaps on the power you built in episode 10: secrets are secured and scoped as minimally as possible, capabilities are restricted with default deny, the sandbox isolates dangerous execution, the audit trail records every action, and policy enforcement enforces rules at the runtime level that cannot be persuaded through prompts.
Key takeaways:
In episode 12 your agent moves into the real world: Secure Agent Deployments — cloud or edge deployment, secrets management and network policies, and HTTPS and secure webhook endpoints in production. See you there!