Olympus is a civic integrity primitive: it stores cryptographic proofs for government documents. We treat security reports with the same urgency as any production outage. Thank you for helping keep the audit trail trustworthy.
Only the current main branch receives security patches.
| Version / Branch | Supported |
|---|---|
main (0.x) |
✅ |
| Older tags | ❌ |
We follow a coordinated disclosure model:
-
Report privately — Do not open a public GitHub issue for security vulnerabilities. Instead, use the GitHub "Report a vulnerability" button in the Security tab of this repository (GitHub Private Security Advisory).
-
Include in your report:
- A short description of the class of vulnerability (e.g. hash-length extension, chain-linkage bypass, SMT root forgery).
- Steps to reproduce or a proof-of-concept (even a sketch is helpful).
- The affected component(s):
crates/olympus-crypto/,src-tauri/(Axum API, SMT, ZK, anchoring),proofs/, orverifiers/. - Your estimate of severity (CVSS score appreciated but not required).
-
Expected response timeline:
Stage Target SLA Acknowledgment ≤ 2 business days Triage / severity assessment ≤ 5 business days Patch or mitigation plan ≤ 30 days for critical; ≤ 90 days for others Public disclosure Coordinated with reporter; default 90-day window -
What to expect:
- We will not pursue legal action against researchers acting in good faith.
- Credit will be given in the release notes and commit history (with your consent).
- We aim to maintain a public advisory once patched.
- If we cannot reproduce the issue we will ask for clarification before closing.
-
Scope — see the threat model (
docs/threat-model.md) for the audit perimeter; a dedicated pentest scope document is planned.
- Preferred: the GitHub Private Security Advisory ("Report a vulnerability" button in the Security tab), which is always available.
- Email:
olympusledgerorg@gmail.com(use the GitHub advisory for sensitive details).
We welcome independent audits of Olympus protocol and implementation layers
(crates/olympus-crypto/, src-tauri/, proofs/, verifiers/).
- Audit coordination: Open a private GitHub Security Advisory first so we can share test vectors and scope details safely.
- Bug bounty intake (HackerOne): https://hackerone.com/olympus
- Audit scope baseline:
docs/pentest-scope.md(planned)
Post-audit remediations are tracked as regular pull requests so fixes remain publicly reviewable and reproducible.
The threat model is described in detail in:
docs/threat-model.md— Adversary model, security goals, and threat-to-mitigation mapping
Key properties Olympus aims to protect:
- Chain integrity — An attacker who can write to the database cannot silently reorder or delete ledger entries.
- Hash preimage resistance — BLAKE3 hashes are one-way; committed content cannot be reverse-engineered from its hash alone.
- Merkle inclusion proof soundness — A presented proof cannot be forged for a leaf that was never committed to the tree.
- Redaction proof binding — A revealed portion cannot be made to look like it came from a different original document.
| Control | Where |
|---|---|
| CodeQL extended query suite (rust, javascript-typescript, python) | .github/workflows/codeql.yml |
| Rust dependency audit (cargo-audit) | .github/workflows/ci.yml — supply-chain job |
| Rust dependency review baseline (cargo-vet) | .github/workflows/ci.yml — supply-chain job |
| Node.js dependency audit (npm audit) | .github/workflows/ci.yml — supply-chain job |
| SBOM generation (CycloneDX, Rust + Node) | .github/workflows/ci.yml — supply-chain job |
| Release checksums, build provenance, and SBOM attestations | .github/workflows/tauri-release.yml, scripts/verify-release.* |
| Dependabot version updates | .github/dependabot.yml — cargo, npm, github-actions |
| Mutation testing / differential fuzzing | .github/workflows/mutation-testing.yml, fuzz/ |
Olympus implements structured observability for detecting security and integrity issues:
| Component | Purpose | Documentation |
|---|---|---|
| OpenTelemetry traces | End-to-end flow tracing (commit/verify/redact) | docs/observability-deployment.md (planned) |
| Prometheus metrics | Proof latency, ledger height, SMT divergence alerts | docs/prometheus-alerting.md (planned) |
| SMT root divergence alerting | Detects tampering or replication bugs | Federation checkpoint gossip + offline verifiers (verifiers/) compare signed roots; dedicated metric is planned |
| Federation checkpoint comparison | Cross-operator root agreement | src-tauri/src/federation/ (feature-gated) |
The Python telemetry module and
tests/chaos/fault-injection suite from the pre-v0.9.0 stack were retired with the FastAPI server. Integrity divergence is currently surfaced through signed-root comparison in the offline verifiers and federation checkpoint gossip; a first-class Prometheus divergence metric is on the observability roadmap above.
The following are by design outside the current threat model:
- Key management — Signing key rotation, HSM integration, and revocation are future work.
- Completeness guarantees — Olympus cannot force submission of all records.
- Content confidentiality — Documents are stored as hashes; access control to the raw content is a deployment concern.
- Single-operator availability — Multi-node Guardian replication is a planned future enhancement; a single operator deletion of the only copy destroys history.
The following operational security practices are required for production deployments:
The OLYMPUS_INGEST_SIGNING_KEY environment variable contains the Ed25519 private key
for signing shard headers. This key must be protected with the following controls:
- Never commit to source control — Use secret managers (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager).
- Persist, then rotate carefully — The signing key must be persisted:
ephemeral keys make historical signed roots unverifiable. Rotate only with a
documented procedure (and immediately on suspected compromise), keeping the
old public key trusted for verification of historical roots. For BJJ
issuer/SBT keys, historical acceptance is configured through the
trusted-issuer set (
OLYMPUS_BJJ_TRUSTED_ISSUERS_JSON,src-tauri/src/api/trusted_issuers.rs). - Restrict process access — Run the Olympus API in a container or VM with
restricted process listing (
hidepid=2for procfs) to prevent key extraction from/proc/PID/environ. - Audit access — Log all access to the secret manager containing the key.
- Hardware security modules (HSM) — For high-assurance deployments, consider using an HSM or cloud KMS for key material (integration is future work).
API keys for the /ingest/* endpoints must be:
- Pre-hashed — Store only BLAKE3 hashes of API keys in
OLYMPUS_API_KEYS_JSON. Never store raw API key values. - Scoped — Assign minimal required scopes (
ingest,commit,verify) per key. - Expiring — Set realistic
expires_attimestamps and rotate before expiry. - Audited — All key registrations are logged to the security audit ledger.
Ed25519 signing keys are operator/account signing identities and are separate from Olympus API keys. API keys authenticate HTTP callers; Ed25519 keys sign ledger, dataset, witness, or federation payloads.
Current provisioning:
- Shard/header signing uses
OLYMPUS_INGEST_SIGNING_KEYfrom the operator environment. It is blocked outside development when the dev-signing-key flag is enabled. - Account signing keys are registered as public keys via
POST /key/signing. The database stores only public key material plus account binding and audit metadata. tools/signing_key_cli.pycan generate an Ed25519 keypair locally. The private key remains with the operator/user and must not be sent to Olympus.POST /key/signing/dev-generateis a first-boot convenience path that returns private key material once. It is gated behind the opt-indev-signing-routeCargo feature, which is OFF by default — production builds (and ordinarycargo tauri dev) do not register the route at all. When the feature is enabled it is additionally gated at runtime toOLYMPUS_ENV=development+OLYMPUS_ALLOW_DEV_SIGNING_KEY_BOOTSTRAP=1, and it enforces the same label/purpose validation asPOST /key/signing.
Password recovery must never create, rotate, revoke, or expose Ed25519 signing keys. Recovery changes account credentials/API keys only; registered signing-key public records remain unchanged.
Signing-key rotation is explicit: register the replacement key, then revoke the
old key with /key/signing/{key_id} and optional replaced_by_key_id. Revoked
registered signing keys cannot be used for dataset commits.
The preferred recovery path is tokenized:
POST /auth/recovery/requestaccepts an email address and always returns the same generic 202 response. If the account exists, a single-use recovery token is created and stored as a hash.POST /auth/recovery/completeconsumes the token, resets the account password, and issues a recovered API key.
Recovery tokens are account-bound, expire after a short TTL, and are marked used
on successful completion so they cannot be replayed. Production responses never
include the raw token; local development and tests may set
OLYMPUS_RETURN_RECOVERY_TOKEN=1 with OLYMPUS_ENV=development to return it in
the response when no email delivery service is configured.
Recovery key issuance is intentionally scoped:
- No scope escalation — Recovered keys may only request scopes already present
on the account's active, unexpired API keys. Accounts with no active keys can
recover
read/verifyonly. - No account overwrite — Token completion derives the account from the stored token record and does not accept caller-supplied user IDs.
- Existing keys are revoked by default — Tokenized recovery revokes active
API keys before issuing the recovered key unless the caller explicitly opts
out with
revoke_existing_keys=false. - Abuse control — The endpoint uses the shared per-IP
RateLimitdependency. Deployments should also apply network-layer rate limits for distributed attacks.
The legacy password-based POST /auth/reissue-key endpoint remains available
for users who know their current password. It is also scope-capped and does not
reset passwords or revoke existing keys.
Deploy Olympus behind a reverse proxy (nginx, HAProxy, AWS ALB) configured with:
- Maximum request body size: 10 MB (prevents payload-based DoS)
- Connection timeouts: 30-60 seconds (prevents slowloris attacks)
- Rate limiting at network layer: Additional protection against distributed DoS
- TLS required — Set
?sslmode=verify-full(or?sslmode=verify-ca) inDATABASE_URL. - Least privilege — The Olympus service account needs only INSERT and SELECT permissions (no UPDATE, DELETE, or DDL).
- Connection encryption — Ensure all connections are encrypted in transit.
The embedded Axum server emits CORS headers only for the origins listed in the
CORS_ORIGINS environment variable (explicit, comma-separated; no
wildcards). If unset, no cross-origin headers are sent. For browser-based
clients, set CORS_ORIGINS (or terminate CORS at a reverse proxy) rather than
relaxing the server default.
Authentication and authorization are enforced in
src-tauri/src/api/middleware/auth.rs, not by a separate sequencer service (the
Go sequencer and its X-Sequencer-Token were retired in v0.9.0).
- API keys — Requests authenticate with an API key. Keys can be derived from
the Baby Jubjub authority key via
derive_api_key_from_bjj; the bootstrap key- initial API key are surfaced once on first launch.
- SBT-driven scopes — Authorization is resolved from the caller's
Soul-Bound Tokens. The
credential_type → scopesmapping is hardcoded and fail-closed: an unknown credential type grants no scopes. Treat the mapping as security policy, not configuration. - Admin surface —
x-admin-key(OLYMPUS_ADMIN_KEY) gates/key/admin/generate,/key/admin/reload-keys, and shard registration (POST /admin/shards); anadmin-role +admin-scope API key is also accepted viarequire_admin_auth. - First-boot registration — on a fresh database the first non-system
POST /auth/registerreceives theadminrole and all requested scopes, so a single-operator desktop is usable immediately. A transaction advisory lock serializes the decision, so two concurrent first registrations cannot both become admin. As the API is loopback-only, the residual boundary is local: a hostile local process that registers before the operator would gain admin. To close that window, setOLYMPUS_ADMIN_KEYand create accounts via the admin-gatedPOST /auth/admin/users(bootstrap also surfaces an admin-scopedsystem-bootstrapAPI key once on first launch). - Shard-write authorization —
POST /ingest/filescallsapi::shards::authorize_writeunconditionally (fail-closed): ashard_idabsent or inactive in theshardsregistry is rejected403, and a shard bound to an owner accepts writes only from that account or anadminkey. This closes the historical gap where any token holder could attribute leaves to anyshard_id. - Rate limiting — the
RateLimitmiddleware enforces per-key limits in process; network-layer limits at a reverse proxy remain a recommended defense in depth.
Trust assumption: the cryptographic chain proves what was appended and in
what order under a given signed root. Per-shard authorization is now enforced by
the shard registry, but a compromised admin/owner key can still append or
backdate leaves up to the next signed root; once included in a signed root, a
leaf is permanently part of the ledger. Multi-operator trust distribution is the
role of federation checkpoint gossip / quorum credentials.
Note (v0.9.x): Earlier releases described an
OLYMPUS_NODE_REHASH_GATE_SECRETenvironment variable and PostgreSQL "trigger gates" (olympus.allow_smt_insert,olympus.allow_node_rehash). Those were part of the retired Python (storage/postgres.py,storage/gates.py) backend and do not exist in the current Rust/Tauri app. The variable is read by no code; do not set it.
SMT writes are serialized — not gated by a session secret — through
NodeBackend::acquire_write_lock (pg_advisory_lock against the embedded
PostgreSQL, or an in-memory tokio::Mutex), held across the read-modify-write
in update_batch. This is a concurrency control (it prevents stale-cache
stomp between concurrent writers), not an authorization control against an
attacker with direct database access.
Threat model for a database-tier attacker. The application does not, and cannot, defend ledger rows against an adversary who already has direct write access to the PostgreSQL data files or socket. Integrity at that tier relies on:
- Infrastructure access controls — the embedded PostgreSQL listens on loopback only; restrict OS-level access to the data directory and the process. Do not expose the database socket off-host.
- Disk encryption — encrypt the volume holding the data directory (e.g. LUKS, AWS EBS encryption, FileVault). This protects the at-rest signing-key material and ledger contents from offline/storage-layer compromise.
- The signed-root chain — tampering that is not reflected in a validly signed checkpoint root is detectable by any verifier (the cryptographic chain proves what was appended and in what order under a given signed root). Federation checkpoint gossip / quorum credentials distribute that trust across operators so a single compromised host cannot silently rewrite accepted history.
ZK proving and verification run in-process in Rust (arkworks / ark-circom),
not via a snarkjs subprocess. A swapped verification key or proving key on disk
is detected by the ceremony-integrity checks rather than a single env-var
hash pin. See proofs/CEREMONY_INTEGRITY.md for
the full protocol; the three runtime checks are:
- Compile-time vkey pin (
src-tauri/build.rs) — assertsblake3(<circuit>_vkey.json) == manifest.artifacts.vkey.blake3.cargo buildfails on mismatch, so a tampered vkey cannot be compiled in. - Proving-key pin on load (
load_proving_key_with_manifest) — re-hashes the.ark.zkeyfrom disk and asserts it matches the manifest before deserialization, returningZkeyError::ManifestMismatchon tamper. This is the filesystem-swap defense the oldOLYMPUS_ZK_VKEY_HASHpin provided. - Startup manifest verification (
main.rs::verify_ceremony_manifests) — recomputes the contribution-chain hash and verifies the coordinator BJJ-EdDSA signature againstOLYMPUS_BJJ_TRUSTED_ISSUERS_JSON. WithOLYMPUS_ENV=production, any failure (or any remainingPLACEHOLDERartifact) isexit(2); in dev it logs and continues.
Operator setup: set OLYMPUS_CEREMONY_COORDINATOR_KEY when running the setup
scripts, and add the coordinator pubkey to consumer machines'
OLYMPUS_BJJ_TRUSTED_ISSUERS_JSON. Never hand-edit
proofs/keys/manifests/*.json — any vkey change requires regenerating its
manifest in the same commit (re-run setup_circuits.sh).
Sole sanctioned proving path: CircomProvingKey /
prove_circom (src-tauri/src/zk/zkey.rs) seals the proving-key type so callers
cannot bypass CircomReduction and fall back to LibsnarkReduction.