Skip to content

Repository files navigation

VEIL

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.


The problem, concretely

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.


Tested on

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 tests34/34 passing (crypto, redaction patterns, resolve modes, merkle tree).

Smoke tests18/18 passing (vault lifecycle, wrong-passphrase must fail, resolver must not leak plaintext, skill registration in Hermes).

Trigger tests with a real LLM4/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 directory

One-time setup:

hermes /veil init

You'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.


How it works

 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.


Optional: on-chain audit log

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.com

Every 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

Commands

/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

What this doesn't do

  • If your passphrase is weak, VEIL is weak. Argon2id buys time, not miracles.
  • If a secret was already in MEMORY.md before 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.md for the full honest list.

Tests

cd ~/.hermes/skills/security/veil
pip install pynacl pytest --break-system-packages
python -m pytest tests/ -v

34 tests covering vault lifecycle, every pattern class, resolver modes, merkle tree properties, and the full end-to-end seal-and-resolve flow.


License

MIT.


Acknowledgements

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.

About

Privacy layer for Hermes Agent — encrypts secrets before they reach MEMORY.md, resolves them locally so plaintext never hits the LLM provider

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages