Trust Model — VayuPress
Status: Authoritative
Last reviewed: 2026-10-25 (checked against the code)
Trust Levels
| Level | Entity | What it can do |
|---|---|---|
| L0 — Untrusted | Anonymous HTTP clients, plugin stdin/stdout | Read public content only; all input validated and escaped |
| L1 — Authenticated user | An author (own content) or editor (all content, publishing) session |
Write content as the role allows; no user, role or system settings |
| L1c — Client | An agency client session (ADR-0152) |
A fixed surface scoped to one domain (ClientDomainID): its site and mail, no authoring; not a rung on the ladder above |
| L2 — Admin | An admin session |
Users and roles, system settings, plugins, every site |
| L3 — Host process | VayuPress Go binary (non-root) | DB reads/writes; subprocess management; metric collection |
| L4 — Operator | SSH access to host machine | Systemd management; backup restore; key rotation |
Component Trust Assignments
| Component | Trust level of its callers | Trust level it grants |
|---|---|---|
| Reverse proxy (nginx, or Caddy, CADDY.md) | L0 (internet) | Terminates TLS; forwards to L3. Client-supplied country headers are blanked by the shipped nginx config |
| Go HTTP handlers | L0-L1-L2 (depending on auth) | Invoke L3 services |
| Plugin subprocess | Treated as L0 (untrusted) | Reads PLUGIN_SCRATCH only |
| SQLite DB | Accessible only to L3 | Authoritative data store |
What We Explicitly Do NOT Trust
- Plugin stdout — parsed as untrusted JSON; log lines forwarded after sanitisation
- User-supplied file paths — validated against allowlist prefix; no traversal
- Config values from environment — a missing required value stops the start (
config.MustEnv); a malformed entry in a list such asTRUSTED_PROXIESis skipped with a warning, not fatal - Plugin binary on disk — SHA-256 hash checked against
Manifest.ExecutableHashwhen the manifest carries one
Key Management
| Key | Type | Location | Rotation |
|---|---|---|---|
| TLS certificate | RSA-2048 / ECDSA | certbot (Let's Encrypt) for nginx, run by the root helper, never the server process | certbot's renewal |
| DKIM signing key | RSA | dkim_<selector>.pem in the mail data directory (mode 0600) |
Operator-initiated |
| VayuPGP account keys | OpenPGP | Key files (mode 0600), private key AES-256-GCM encrypted | User-initiated |
| Tor onion service keys | Ed25519 | tor_onions.private_key, sealed by the install's secrets store (internal/secrets, its key in a 0600 file outside the database) |
Per onion; replaced with the onion |
| TOTP secrets | Shared secret | users.totp_secret, sealed by the same store |
User-initiated |
| API keys | Random token | Only sha256(token) is stored (internal/apikeys) |
Revoke and make a new one |
| Release signing key | Ed25519 | Public half compiled into the binary (internal/update/releasekey.go); updates are refused unless signed |
Shipped with a release |
Private keys are never:
- Logged (even at debug level)
- Included in error messages
- Accessible to plugin subprocesses
Onion keys and TOTP secrets are held in the database sealed, so a database
backup alone does not open them: it needs the key file too. A database restored
without its key file keeps the sealed onion keys as they are rather than
overwriting them (cmd/vayupress/vayutor_store.go).