If you discover a security vulnerability in Ovara, please do not open a public issue. Email security@ovara.dev with the details and we will respond within 72 hours.
When reporting, please include:
- Description of the vulnerability and its impact
- Reproduction steps with the smallest possible policy + request
- Affected component(s) and version
- Your name/handle for credit (optional)
We follow coordinated disclosure: we will work with you to understand the issue, develop a fix, and agree on a disclosure timeline. We aim to acknowledge within 72 hours and ship a fix within 30 days for critical issues.
docs/REVIEW_SCOPE.md is the brief for an external
security review (adversary, modes, the code everything depends on, what to
look at first). docs/SECURITY_RELEASE.md is the
checklist every security release follows, including private fixes through
GitHub Security Advisories.
Ovara is pre-release. Security fixes land on main first and go into the
next 0.9.x release.
| Version | Supported |
|---|---|
main (unreleased) |
Yes: fixes land here first |
| 0.9.x | Yes: the current pre-release line |
Anything older, including the withdrawn v1.0.0 tag and the "2.0-RC1" label in older documents |
No |
Each guarantee Ovara makes, the mode it holds in, and the test behind it
are listed in docs/GUARANTEES.md.
Ovara is a runtime trust layer for autonomous systems. Its security model
combines a gateway authorization chain, an egress proxy, and (optionally)
an enforced network boundary. Only the first two are built into the ovara
binary; the kernel-level layers shown in the diagram below are a design
target, not a shipped feature.
RC1 status: the gateway authorization chain (identity → delegation → lease → policy → approval → execution → evidence) and the egress proxy (MITM + credential injection + scrubbing + signed receipts) are implemented and clean-room verified — see
docs/OVARA_RC1_SECURITY_REPORT.md. The kernel-level layers below (AppArmor/seccomp/eBPF/Firecracker) are NOT implemented in the gateway (it has a Docker sandbox for command execution, nothing more); they are the hardening path, not the current default deployment.What contains the agent today depends on the mode. In cooperative mode (
ovara env) the agent is only asked to use the proxy; a hostile agent can ignore it. If the agent runs as the same OS user as Ovara it can also read Ovara's keys and edit its policy and log; run it as a different user. Enforced mode (--boundary, Linux) removes the network path around the proxy.ovara box(Linux) adds a separate user, its own root file system or a container, and a check of every command the agent runs.ovara doctorreports which of these hold.Target design (not built):
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌───────────────────────────────────────────────────┐ │
│ │ AppArmor Profile │ │
│ │ Capability restrictions, file access control, │ │
│ │ network restrictions, deny ptrace/dbus/mount │ │
│ └───────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Seccomp Filter │ │
│ │ Whitelist of ~130 syscalls, blocks mount, ptrace, │ │
│ │ kexec, bpf, module loading │ │
│ └───────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ eBPF Interceptor │ │
│ │ Runtime syscall monitoring, policy enforcement, │ │
│ │ audit trail via ring buffer │ │
│ └───────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌───────────────────────────────────────────────────┐ │
│ │ Firecracker MicroVM │ │
│ │ Hardware isolation via KVM, read-only rootfs, │ │
│ │ resource limits, network isolation │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Ovara uses well-audited cryptographic primitives:
| Primitive | Use | Library |
|---|---|---|
| ed25519 | Delegation hop signatures, capability lease signatures, trusted-issuer verification, signed journal envelopes | Go crypto/ed25519 |
ed25519 (sig_v2, sig_v1) |
Proxy receipt chain (var/receipts.jsonl); v2 format below, v1 still verifies |
Go crypto/ed25519 |
ed25519 (edsig_v1) |
Gateway receipt signatures: domain-separated, length-prefixed preimage covering every receipt field (and policy_hash when present); receipts issued after their key was revoked, or after a replaced key's rotation grace, are rejected |
Go crypto/ed25519 |
HMAC-SHA256 (hmac_v1) |
Legacy gateway receipt signing (receipt_signing_key), kept alongside edsig_v1. Its prefix was sig_v1: before October 2026 and was renamed so it can never be mistaken for a proxy signature. It is never reported as a valid receipt signature on its own |
Go crypto/hmac + crypto/sha256 |
| SHA-256 | Action digests, delegation replay keys, credential-derived principal IDs, canonical encoding | Go crypto/sha256 |
Canonical signing format: all signed delegation/lease payloads use a
length-prefixed binary encoding (u32be(len)||bytes per field,
u32be(count) arrays, i64be timestamps) implemented identically in
runtime/gateway/internal/identity/canon.go and
sdk/python/src/ovara_sdk/canon.py — cross-verified byte-for-byte,
including Python-signed two-hop chains verified by Go.
Every request that reaches the proxy leaves a receipt in
var/receipts.jsonl, one JSON object per line, each linked to the one
before by prev_hash (SHA-256 of the previous line's bytes).
- Attempt and outcome. A request that goes upstream gets an attempt
receipt (
phase: "attempt"), appended and synced to disk before the first byte is sent, and an outcome receipt (phase: "outcome", withattempt_id) before the response reaches the agent. If the attempt receipt cannot be written, the request is refused. A request that never goes upstream gets one receipt with no phase. - Fields (
"v": 2):receipt_id,session_id,timestamp,method,url,decision,status,approval_id,phase,outcome,attempt_id,agent(pt_+ the first 16 hex of the SHA-256 of the proxy token),rule(the policy rule that decided),policy_hash,approver,req_bytes,resp_bytes,body_sha256,reasons,kid(the signing key),prev_hash,signature. - What is signed.
signatureissig_v2:+ hex Ed25519 over this byte string:"ovara-proxy-receipt-v2\n", then for each field in the orderv, receipt_id, session_id, timestamp (Unix nanoseconds), method, url, decision, status, approval_id, phase, outcome, attempt_id, agent, rule, policy_hash, approver, req_bytes, resp_bytes, body_sha256, reasons, kid, prev_hash: a 4-byte big-endian length and the field name, then a 4-byte big-endian length and the value. Numbers are decimal.reasonsis written as its count, followed by each reason as a length and its bytes. Every value is length-prefixed, so no two receipts share a signed byte string. - Strict lines. A line must be exactly the JSON encoding of what it decodes to. Unknown, duplicate or reordered keys and changed whitespace are refused.
- v1. Older receipts (
sig_v1:, a pipe-joined payload) still verify. A chain may hold v1 receipts and then v2 ones, never v1 after v2. A v1 line that carries v2 fields is refused, because v1 does not sign them. An outcome receipt must follow its attempt. - Anchors.
var/anchors.jsonlrecords the chain head as it grows. A log that was cut short, or cut and continued, fails verification, andovara runrefuses to append to it.
The proxy's public keys are kept in var/receipt_keys.jsonl
(kid, public key, not_before, not_after, revoked_at). A receipt is
checked against the key its kid names, and is refused if it is dated
outside that key's window or at or after its revocation.
ovara keys list-receipt -dir mydir
ovara keys rotate-receipt -dir mydir # stop ovara run first
ovara keys revoke-receipt -dir mydir -key <kid> -at <RFC 3339 time it may have leaked>The proxy will not start with a retired or revoked key, and will not make
a new key over an existing log when the key file is missing. Revocation
stops later-dated forgeries; a stolen key can still sign receipts dated
before the revocation time. ovara log -verify-only, ovara log export,
ovara incident bundle, ovara doctor and ovara-proxy -verify all use
the key history.
- Journal envelopes v2. Each signed journal line covers its version,
its links and its signing time (
at), besides the record. Lines are decoded strictly (unknown or duplicate keys are refused, and v2 lines must be byte-canonical).atnever goes backwards. v1 journals still open; a v1 line after a v2 line is refused. A complete line that fails the strict check is reported as corruption, never trimmed away as a torn tail. - Key state. An envelope signed at or after its key was revoked or destroyed, or after a replaced key's rotation grace, fails to open (v2 envelopes only; v1 envelopes carry no time).
- No receipt, no allow. If the gateway cannot store a receipt, an
allow or escalate becomes a deny with the reason
receipt_unavailable. - Outcome unknown. An action that was running when the gateway
stopped is recovered as
executedwithoutcome_unknown: true. It is never retried automatically; a person has to check what happened. - Owner-only files. Stores, journals, logs and keys are written 0600.
The deployment and
var/directories are 0711: other users cannot list them and can open only the public CA certificate, which the agent needs.ovara initwritespolicy.json0600 and no longer prints the operator token (it is inconfig.json).
- Credential-derived principals: the authoritative identity is
ag_<sha256(token)[:16]>/op_<…>, stamped by the auth middleware. Caller-suppliedagent_identity.subject_idmust equal it or the request is rejected with HTTP 400identity_mismatch. - Delegation chains (capability, not attestation): ed25519-signed
hops verified against a configured
trusted_issuersregistry; chain linkageissuer[i+1] == subject[i]; terminal subject must equal the authenticated principal; non-amplification enforced; replay keyed on the terminal hop's signed nonce (5-minute window; durable across restarts whenreplay_fileis configured, whichovara initdoes — process-local otherwise). A valid chain narrows the request; it can never lift policy. - Capability leases: ed25519-signed, issuer-verified; require exact audience match to the gateway identity; subject must equal the authenticated principal; action+resource scope enforced.
- Policy: independent last gate — escalation is never lifted by valid credentials.
- Approvals: bound to the gateway-recorded decision (not caller fields), single-use resume, owner-or-operator only.
All cryptographic code is reviewed by maintainers before merge. See
docs/OVARA_2_THREAT_MODEL.md and docs/OVARA_RC1_SECURITY_REPORT.md.
Operator tokens in etc/config.json are bearer tokens that grant full
admin access. We recommend:
- Store in a secrets manager, not in version control
- Rotate at least every 90 days
- Use a token with at least 32 bytes of entropy (
openssl rand -hex 32) - Use separate tokens per environment and per operator
- On compromise: replace the token in config and restart the gateway —
tokens in
config.jsonare static credentials and cannot be revoked without a restart. Credentials held in the identity registry (identity_registry_file) can be revoked at runtime.
The gateway fails closed. A configured persistent store (receipts, approvals, continuations, executions, events, capabilities) that cannot be opened stops startup rather than continuing with an empty in-memory store; an approved action whose "executing" claim cannot be written to disk is not run. Recovering from a corrupt or tampered journal is a deliberate operator step (inspect, then move the file aside), never an automatic one.
The receipt_signing_key config field controls HMAC-SHA256 receipt
signing. If unset, the gateway generates a random per-process key
at startup and logs a warning — receipts signed under it cannot be
verified after a restart (see server.go). For production deployments:
{
"receipt_signing_key": "$(openssl rand -hex 32)"
}No vulnerabilities disclosed to date.
We thank the security researchers and contributors who have helped make Ovara more secure.