Stop leaking secrets to your LLM provider.
VEIL is a privacy layer for Hermes Agent's memory. Whatever your agent
"remembers" — API keys, database URIs, client emails, wallet addresses —
gets shipped to whichever LLM provider you're using, on every single
turn, as part of the system prompt. VEIL redacts sensitive values before
they reach MEMORY.md or USER.md, encrypts them locally, and resolves
them back to plaintext only at the moment a tool actually needs them.
Nothing sensitive ever crosses the model boundary.
Hermes injects MEMORY.md and USER.md into the system prompt at the
start of every session. Both files are plaintext. Whatever sits in them
— verbatim — is sent to OpenRouter / Nous Portal / OpenAI / Anthropic /
your local Ollama on every request.
Before VEIL:
$ cat ~/.hermes/memories/MEMORY.md
User's staging DB is postgres://admin:hunter2@10.0.1.50:5432/stg
User's OpenAI key for testing is sk-proj-8Kf3jQ2mN...
User's biggest client (alice@acme.corp) pays $48,000/month
Every turn, all three of those lines are in the request body going over the wire. The provider's abuse-monitoring logs probably have them. Any TLS-intercepting proxy between you and them has them.
After VEIL:
$ cat ~/.hermes/memories/MEMORY.md
User's staging DB: {{veil:postgres-a7f2}}
User's testing OpenAI key: {{veil:openai-key-9c1e}}
User's biggest client: {{veil:email-b3d4}} pays {{veil:amount-2f88}}/month
Plaintext lives in ~/.hermes/.veil/vault.age, encrypted with
XChaCha20-Poly1305 under an Argon2id-derived key. The LLM sees only the
placeholders.
Full test suite was run on a clean Ubuntu 24.04 VPS (Hetzner, 2 GB RAM),
inside a Docker container with Hermes Agent v0.10.0 and
anthropic/claude-sonnet-4.6 via OpenRouter.
Unit tests — 34/34 passing (crypto, redaction patterns, resolve
modes, merkle tree).
Smoke tests — 18/18 passing (vault lifecycle, wrong-passphrase
must fail, resolver must not leak plaintext, skill registration in
Hermes).
Trigger tests with a real LLM — 4/4 passing:
| Test | Input to hermes chat |
Expected | Actual |
|---|---|---|---|
| 1 | remember privately: my openai test key is sk-proj-… |
Agent calls veil.py seal, writes placeholder |
✅ agent sealed under openai-test-key, placeholder in USER.md |
| 2 | can you save this for later: ghp_… (no privacy cue) |
Pattern alone triggers the skill | ✅ sealed under github-pat — pattern catalog did the work |
| 3 | remember that Michael from TokyoCorp (michael@tokyocorp.jp) pays us 15000 USDC… |
Sealed as PII + financial | ✅ sealed with --kind pii, ref client-michael-tokyocorp |
| 4 | remember that I prefer typescript and my main project is at ~/code/api |
Must NOT trigger — non-sensitive | ✅ plain memory write, no seal, no vault entry |
Test 4 is the one that matters most: it shows VEIL is selective. A privacy skill that redacts everything is worse than no privacy skill at all — the vault fills with noise and the agent's memory becomes unreadable. The agent itself told the user "Saved in plain memory — no sensitive data there", which is exactly the behavior the SKILL.md description is trying to induce.
Reproduce the tests on your own VPS — see veil-test/
for the Docker image and trigger_test.md checklist.
hermes skills install github:Zhekinmaksim/hermes-veil
# or clone and point Hermes at the directoryOne-time setup:
hermes /veil initYou'll be asked for a master passphrase. Pick a long one. It's cached in the OS keyring so you don't see the prompt again on that machine.
user says "remember my postgres password"
│
▼
┌────────────────────┐
│ redact.py │──▶ scans text against pattern catalog
└────────────────────┘ (15+ providers, PII, connection strings)
│
▼
┌────────────────────┐
│ veil.py seal │──▶ XChaCha20-Poly1305 → vault.age
└────────────────────┘ (optional: merkle root → Solana)
│
▼
┌────────────────────┐
│ memory(add, ...) │──▶ writes {{veil:xxx}} to MEMORY.md
└────────────────────┘
│
(session start)
▼
┌────────────────────┐
│ system prompt │──▶ placeholder travels to LLM
└────────────────────┘
│
▼
agent decides it needs the real value (e.g. to run psql)
│
▼
┌────────────────────┐
│ resolve.py --exec │──▶ decrypts inside subprocess
└────────────────────┘ child runs; only its stdout/stderr
stream back to the agent
The detection catalog lives in scripts/redact.py. It triggers on:
- API keys — OpenAI, Anthropic, GitHub, Slack, AWS, Google, Stripe
- Tokens — JWTs, Bearer tokens
- Private keys — PEM blocks, Solana keypairs, EVM private keys
- Connection strings — Postgres, MySQL, MongoDB, Redis (only when they carry embedded credentials)
- PII — emails and wallet addresses, but only when a financial or explicit-privacy cue is in the same message
See references/patterns.md for the complete catalog and confidence
rules.
If you want tamper-evidence for your vault — protection against a local attacker silently replacing entries — enable the Solana audit log:
# ~/.hermes/config.yaml
skills:
veil:
onchain_enabled: true
solana_rpc: https://api.mainnet-beta.solana.comEvery vault mutation appends a line to audit.log and publishes a
merkle root to Solana via the Memo program. Publish cost is ~$0.001
per transaction. The on-chain payload contains only a 32-byte root —
no ref names, no kinds, no plaintexts. See references/onchain.md
for the full protocol.
Verify locally at any time:
python scripts/onchain.py verify ~/.hermes/.veil/vault.age/veil init # create vault, set passphrase
/veil list # show refs and kinds (no plaintext)
/veil verify # check all refs decrypt
/veil rotate # re-encrypt under new passphrase
/veil scrub <ref> # flag as compromised, block future use
And internally, so the agent can invoke them:
python scripts/veil.py seal --ref foo-1234 --kind api_key < value
python scripts/veil.py unseal foo-1234
python scripts/resolve.py --exec "psql '{{veil:pg-prod-a7f2}}'"
python scripts/resolve.py --env DB_PW=pg-prod-a7f2 -- python run_job.py
- If your passphrase is weak, VEIL is weak. Argon2id buys time, not miracles.
- If a secret was already in
MEMORY.mdbefore you installed VEIL, it has already been sent to the provider. VEIL can stop future exposure; only rotating the real credential fixes the past. - If a hostile skill on your machine calls
resolve.py --raw, it can exfiltrate secrets. Trust skills you install. - VEIL does not do zero-knowledge inference. The LLM can't read
ciphertext; the correct trust boundary is the resolution point, not
the storage point. See
references/threat-model.mdfor the full honest list.
cd ~/.hermes/skills/security/veil
pip install pynacl pytest --break-system-packages
python -m pytest tests/ -v34 tests covering vault lifecycle, every pattern class, resolver modes, merkle tree properties, and the full end-to-end seal-and-resolve flow.
MIT.
Built against Hermes Agent v0.8.x by Nous Research. Uses libsodium via PyNaCl, optionally solders + solana-py for the on-chain audit path. The skill format follows the agentskills.io open standard.