| name | veil |
| description | Encrypt secrets and PII before they reach LLM providers via MEMORY.md. Intercept save-to-memory requests containing credentials, API keys, or personal data; replace sensitive payload with opaque reference; store real value in local age-encrypted vault. Agent resolves references locally at tool-execution time via veil_resolve. |
| version | 0.1.0 |
| platforms | ["macos","linux"] |
| metadata | {"hermes":{"tags":["security","privacy","secrets","pii","encryption","solana"],"category":"security","required_environment_variables":[{"name":"VEIL_PASSPHRASE","prompt":"Master passphrase for the VEIL vault","help":"Used to derive the age encryption key via Argon2id. If lost, the vault cannot be recovered — there is no key escrow.","required_for":"full functionality"}],"config":[{"key":"veil.vault_path","description":"Path to the encrypted vault file","default":"~/.hermes/.veil/vault.age","prompt":"Vault file path"},{"key":"veil.pii_mode","description":"Redact personally identifiable information (names, emails, amounts) in addition to secrets","default":"true","prompt":"Enable PII redaction? (true/false)"},{"key":"veil.onchain_audit","description":"Publish vault state digest to Solana mainnet as tamper-evident log (opt-in, costs ~0.000005 SOL per write)","default":"false","prompt":"Enable on-chain audit log? (true/false)"},{"key":"veil.solana_keypair_path","description":"Path to Solana keypair for publishing audit entries (only required if onchain_audit is true)","default":"~/.config/solana/id.json","prompt":"Solana keypair path"}]}} |
VEIL — Privacy Layer for Hermes Memory
When to Use
Trigger this skill before writing to MEMORY.md or USER.md when the
user's message contains any of the following categories of data:
- Credentials: passwords, API keys, private keys, tokens, connection strings
- Identifiers: SSH keys, TLS certs, webhook secrets, database URLs with auth
- Personally identifiable information of third parties: full names paired
with emails/phones, monetary amounts tied to named clients, addresses
- Anything the user explicitly marks as sensitive: "private", "secret",
"don't tell anyone", "between us"
Common triggers in user language:
- "remember my X password is ..."
- "save my API key for ..."
- "my client Y paid Z amount"
- "don't forget the SSH key for staging is ..."
Do not trigger for: generic environment facts ("I use Ubuntu 22.04"),
public project names, the user's own first name, or tool/library preferences.
These belong in plain memory.
Procedure
-
Detect: Parse the user's remember-request. Identify the sensitive
span(s). If unsure whether something is sensitive, ask the user once —
do not default to saving in plaintext.
-
Seal: Call scripts/veil.py seal with the sensitive value and a
short human-readable label (e.g., pg-prod, client-anna). The script
returns an opaque reference like {{veil:pg-prod-2026a}}.
-
Write placeholder to memory: Use the memory tool to save an entry
containing the placeholder instead of the plaintext.
Good: User's Postgres prod credentials stored in VEIL: {{veil:pg-prod-2026a}}. Host: db.example.com, port 5432.
Bad: User's Postgres prod password is hunter2.
The non-sensitive scaffolding (host, port, which system it's for)
stays in plaintext — the agent needs it to decide when to resolve.
-
Resolve at execution time: When the agent later needs the real
value to run a command or make a request, call
scripts/veil.py resolve <reference>. The plaintext is returned only
to the tool execution context and only on the user's machine. Never
write the resolved value back to MEMORY.md or echo it in a chat
response.
-
Audit (if enabled): If veil.onchain_audit is true, every seal
and rotate operation automatically appends
SHA256(vault_state || timestamp) to the Solana audit log. No user
action needed.
Commands
veil init one-time setup; creates vault, prompts for passphrase
veil seal <label> <value> returns {{veil:label-hash}}
veil resolve <reference> returns plaintext (local only)
veil list lists all references with labels, no values
veil rotate re-encrypt vault with new passphrase
veil migrate scan existing MEMORY.md, propose redactions
veil audit verify check local vault matches latest on-chain digest
veil audit publish manually publish current digest
Pitfalls
-
Never log the resolved value. If the agent prints plaintext into a
chat response, a log file, or a terminal command visible to another
user, the leak is real. Prefer piping into stdin of the target
command: veil resolve pg-prod-2026a | psql -h db.example.com ...
-
Placeholders are not semantically hidden. An LLM seeing
User's Postgres prod credentials: {{veil:pg-prod-2026a}} can infer
the category of the secret. This is acceptable — the value is still
protected — but don't claim VEIL hides the existence of credentials.
-
Vault corruption is unrecoverable. If vault.age is deleted or
the passphrase is forgotten, references become dangling. Back up
vault.age to an external location; it is encrypted, so backup
exposure is not a leak.
-
On-chain audit is opt-in for a reason. It costs ~0.000005 SOL per
operation and publicly reveals that the user updates their vault,
even though it hides what. Users who don't want any network
footprint should keep it off.
-
Do not mix VEIL references across machines. Each vault is a closed
world. If a user copies MEMORY.md to a new machine without the
matching vault.age, references cannot be resolved.
Verification
After seal:
veil list | grep <label> reference should appear
grep -r "<plaintext>" ~/.hermes/ should return nothing
After resolve:
veil resolve <reference> should return exact plaintext
After on-chain audit publish:
veil audit verify should report "OK: local digest matches chain"
References
references/threat-model.md — what VEIL does and does not protect against
references/crypto-choices.md — why age, why Argon2id, why Solana for audit
references/usage-examples.md — end-to-end examples for common workflows