Security
Your venue holds things worth protecting: users' workspaces and files, agents and their memory, API keys, and an audit trail people will rely on. Covia's security posture is built on two principles — secure defaults (anonymous callers get a read-only ceiling, unknown identities get nothing, agents cannot mint authority) and fail-closed configuration (a security misconfiguration stops the venue at startup; it never silently downgrades to an unprotected state).
This page tours the controls in the order an attacker would meet them, and ends with a production checklist. Config snippets show venue-entry keys — in a real config file they sit inside an entry of the top-level venues array (see Configuration).
Encrypt the store
A venue's Etch store contains everything: lattice state, agent records, DLFS drives, secrets. If a disk, VM image, or backup walks away, encryption at rest is what makes that a non-event. Venues run on encrypted Etch v3 stores:
{
"store": "/data/venue.etch",
"etch": {
"version": 3,
"cipher": "aes-256-ctr",
"encryptIndex": true,
"key": { "env": "COVIA_ETCH_KEY" }
}
}
- Ciphers:
aes-256-ctrorchacha20(encryption requiresversion: 3). - Key sourcing: exactly one of
{"env": "VAR"},{"file": "path"}, or an inline hex string (dev/test only — never commit key material). The key must resolve to exactly 32 bytes of hex or the venue refuses to start. - Key identity hints: the store is stamped with an identity derived from its key, and the venue verifies it before opening. Start with the wrong key and you get a one-line diagnosis naming the key the store actually needs — never a garbage-decrypt or a mysteriously empty venue.
- Fail-closed, everywhere: an unset environment variable, an unreadable key file, a wrong-sized key, a cipher with no key source — all are startup errors. There is no code path from "operator asked for encryption" to "data landed on disk unencrypted".
Two caveats the venue itself warns about: with storage.content: "file", asset content bytes live outside the encrypted store as plaintext files; and an auto-generated plaintext venue.key sitting beside an encrypted store means disk theft still yields your venue identity — prefer seed or a keystore and remove the key file.
Embedded deployments can go further: the embedding application supplies a key function (backed by a KMS, HSM, or user passphrase) and hands the venue an already-opened store — key material never touches config, environment, or disk. See Embedded Venue.
Secrets get a second, independent layer: the per-user secret store encrypts every value with AES-256-GCM under a key bound to the venue's identity, so with an Etch cipher enabled your users' API keys are encrypted twice.
Pin the venue identity
The venue's key pair is the root of every UCAN it signs, every session it issues, and every federation trust decision — so identity is pinned, not assumed. Declare it with the did config key:
- A declared
did:keymust match the venue key pair — a venue started with the wrong seed or keystore refuses to run rather than impersonate. - A declared
did:webmust match the configured publichostname, and did:web callers verify at every ingress with HTTPS-only, public-hostname-only resolution. - An existing store opened with a key that owns none of its state is a startup error naming the store's real owner — a wrong key can never silently create a fresh empty venue and orphan your data.
- The auto-generated
venue.keyfile is written with owner-only (0600) permissions, repaired on every launch. For production, prefer an explicitseedor a PKCS12keystore.
One consequence to plan around: user secrets are encrypted under a key derived from the venue seed, so rotating the seed orphans existing encrypted secrets — treat the identity seed and the store key as separate secrets, rotated deliberately.
Control the network surface
The defaults suit development: bind on all interfaces, permissive CORS. A venue holding real data should tighten each one — and the venue helps, by failing at startup on invalid values rather than silently widening.
bindAddress— set"127.0.0.1"for anything local or embedded; the loopback bind also flips rate limiting and Private Network Access to matching defaults.- TLS — the venue deliberately does not terminate TLS; run it behind a reverse proxy (the repo's
deploy/directory ships a working Caddy setup with automatic HTTPS) and setbaseUrlexplicitly. corsOrigins— narrow"*"to your real origins. The"loopback"sentinel matches literal localhost forms without ever resolving DNS, which blocks DNS-rebinding tricks; denied origins get a clean 403.- Rate limiting — per-caller token buckets, on by default for any non-loopback bind:
{"rateLimit": {"rps": 100, "burst": 300, "maxConcurrentJobsPerUser": 100}}. Buckets are keyed per user DID (all anonymous traffic shares one bucket), over-limit requests get429withRetry-After, and a concurrent-jobs cap stops one caller from monopolising the job engine. - SSRF protection — outbound HTTP, bridged MCP servers, and A2A URLs all pass the same guard: loopback, site-local, and link-local addresses are blocked, HTTP(S) only.
Decide who gets in
Authentication proves control of a DID; it does not make someone a member of your venue. Admission is a separate, deliberate step:
- An unknown authenticated DID gets a
403with an actionable registration message — before any state is created for it.users.autoCreatedefaults tofalse; leave it that way on private and production venues. - Admit users explicitly:
users.bootstrapin config (named users and their public keys, provisioned before HTTP starts), or at runtime viauser:create— as the venue itself, or by a provisioner holding a venue-issued UCAN delegation for exactly that ability. No separate admin API to secure; the capability check is the admission control. - Anonymous access, when enabled, is bounded by the public capability ceiling: read-only, no
invoke, unless you explicitly widen it."unrestricted"exists for loopback throwaway venues only. - MCP inherits the same posture:
mcp.auth.requireddefaults to the inverse of public access, and an explicitfalsecannot re-open MCP on a venue that requires authentication elsewhere.
Extend without widening
Everything that adds code or capability to a venue is operator-gated:
- Venue modules load only from config, at boot, with optional
sha256content pinning — and there is deliberately no runtime module-load operation, because in-process code is total compromise. - The Python adapter is not a sandbox: configured scripts run with the venue's full authority. The operator selects every script and allowlists every callable; callers can never submit source. Use containers for untrusted code.
strictConfigrejects unknown config fields at startup (typo-proofing your security settings); known fields are always validated fatally either way.strictAssets(default on) refuses to boot a venue whose operations failed to install.
Production checklist
-
storeis a file path, withetch.cipherset and the key sourced fromenvorfile - Venue identity from
seedorkeystore— no plaintextvenue.keybeside an encrypted store -
diddeclared, pinning the identity fail-closed - TLS terminated by a reverse proxy;
baseUrlset explicitly -
corsOriginsnarrowed from"*";bindAddressrestricted where applicable - Rate limiting left on (or tuned);
maxContentSizeappropriate -
auth.public.capsreviewed;users.autoCreateoff; admission via bootstrap or delegateduser:create - Secrets supplied as bootstrap config or
s/NAMEreferences — never in operation inputs, never committed - Modules pinned with
sha256; Python scripts treated as trusted code - Startup log reviewed: the venue warns on exactly the gaps above (unencrypted vault data, plaintext key files, content stored outside the cipher)
Found a vulnerability? Report it privately via GitHub private vulnerability reporting or security@covia.ai — and remember the trust model: the security of a federation depends on the policies its operators configure.
Related
- Configuration Reference — every key used above
- Authentication — OAuth, token types, the access model
- Persistence — the store the cipher protects
- Embedded Venue — the loopback, key-function deployment shape