Security at qub
Effective date: 1 May 2026
1. Our Approach
qub is trust infrastructure. The product is worthless if it is not secure, so security is not a feature — it is the substrate. This page describes, in concrete terms, how we safeguard our stack, your data, and the integrity of sealed content.
We do not ask you to trust us. We design so that the trust required of us is as small as possible, and where trust is required we explain exactly what is being trusted and why.
Three principles drive every design decision:
- Minimise what the server can see. Plaintext content never leaves your device unencrypted. Where we must hold metadata, we hold the smallest amount consistent with the feature.
- Make compromise locally contained. A breach of any one component (our server, the email provider, a drand node) should not reveal sealed content that has not yet reached its reveal time.
- Make the protocol auditable. The sealed artefact is verifiable end-to-end with public cryptography. You do not need to trust qub the service to verify a qub the artefact.
2. Threat Model
2.1 What We Protect Against
- An attacker who gains full read access to our server infrastructure before the reveal time of a qub. They learn metadata (timestamps, device identifiers, attestation records). They do not learn sealed plaintext.
- An attacker who intercepts traffic between your browser and our infrastructure. TLS terminates at our CDN edge; sealed payloads are already encrypted before transit.
- An attacker who tampers with a sealed payload after it reaches Arweave. Tampering changes the body hash, which is bound into the signature chain; verification fails and the viewer refuses to render the qub.
- An attacker who attempts to bind a forged author email to a signing key. Email attestation requires possession of both the private signing key and a one-time code delivered to the email inbox.
- A compromised drand beacon operator. The drand network uses threshold BLS signatures across multiple independent operators; a minority cannot forge early-release signatures.
2.2 What We Cannot Protect Against
We are honest about our limits. qub cannot defend against:
- A compromise of your device before you seal. Local keyloggers, malicious browser extensions, or physical access to an unlocked device can capture plaintext at the point of composition.
- A collapse of the drand threshold. If a sufficient majority of drand operators collude, they could derive timelock keys early. The drand network is operated by independent organisations specifically to make this difficult, but it is not cryptographically impossible.
- The fundamental property of Arweave: once data is on-chain it is permanent. An attacker who obtains a valid copy of the sealed payload before reveal and the time to wait cannot be stopped from reading it at reveal time. This is the feature, not a bug.
- A global adversary who breaks the underlying cryptography (AES-GCM, BLS12-381 pairing assumptions, SHA3-256, ML-DSA-65). If these primitives fall, the cryptographic ecosystem at large has larger problems.
3. Client-Side Cryptography
All content encryption happens in your browser before any network request is made.
3.1 Timelock Encryption
qub uses tlock — identity-based encryption keyed to a future drand beacon round. Encryption proceeds in your browser using the drand network's public key; the decryption key is released publicly by the drand network only when the target round is reached. Nobody, including us, can reconstruct the decryption key ahead of time.
We target the quicknet chain:
- 3-second round period
- Unchained mode (each round is independent)
- BLS12-381 G1 signatures
- Chain hash
52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971
The quicknet chain's public key and genesis time are compiled into the client. We do not fetch chain parameters at runtime, so a malicious node cannot substitute a chain we control.
3.2 Symmetric Encryption
The tlock scheme wraps an AES-256-GCM content key. AES-GCM provides authenticated encryption: a single bit flipped in the ciphertext causes decryption to fail, rather than producing silently-corrupted plaintext.
3.3 Canonical Serialisation
Sealed content is serialised using deterministic CBOR (RFC 8949 §4.2 core deterministic encoding). Two clients sealing the same content with the same parameters produce byte-identical payloads. This is load-bearing for verification: the body hash is computed over the canonical byte sequence, so any re-encoding of the payload breaks verification.
We wrote the CBOR encoder by hand for both our client and server implementations rather than relying on a generic serialisation library — the requirement is exactness, not ergonomics, and property tests run in both implementations to verify they agree.
3.4 Body Hashing
Every sealed payload carries a SHA3-256 hash of its canonical body. The hash is bound into the signature chain. A viewer recomputes the hash on download and rejects the payload if the recomputed hash does not match.
3.5 Signing (ML-DSA-65)
Authorship signing uses ML-DSA-65 (FIPS 204), a NIST-standardised post-quantum signature scheme. We deliberately chose a post-quantum primitive for signing because sealed content is permanent: a signature that verifies today must still verify decades from now, including after large-scale quantum computers become practical.
Private signing keys are generated and stored on your device. They never leave your browser. Only public keys and attestation records reach our server.
The same in-browser tlock decryption applies inside the qub embed: when a sealed qub is rendered through <qub-embed> on a third-party page, decryption still happens in the embed iframe in the viewer's browser. The embed does not change the trust model — plaintext is never decrypted on a qub server.
4. Transport and Edge
4.1 TLS
All traffic between your browser and our infrastructure uses TLS 1.3 terminated at our CDN edge. We do not operate origin servers you can reach directly; every request passes through our edge.
4.2 Content Security
The compiled client is served with strict content-type and cache headers. The SPA shell is a single origin. We do not embed third-party scripts for analytics or advertising; the only third-party script execution occurs inside a dedicated Stripe checkout iframe on the purchase flow and a privacy-preserving bot-detection widget on the seal flow. Both are sandboxed by the browser and cannot read the rest of the page.
The qub embed iframe (served from qub.social/embed/{tx_id} and loaded into third-party sites by embed.js) carries its own Content-Security-Policy that pins outbound connect-src to 'self' plus the two drand endpoints listed in §4.3. The iframe runs with sandbox="allow-scripts allow-top-navigation-by-user-activation": the host page cannot read the iframe's DOM, and the iframe cannot navigate the host page except in direct response to a user click on the embed's CTA.
4.3 CORS and Fetch Scope
The browser client makes fetch requests only to:
- Our own API (
api.qub.socialand staging equivalents) - Arweave gateways (read-only, for sealed payload retrieval)
- drand beacon endpoints (read-only, for reveal-time round signatures)
Each destination is whitelisted; an unexpected destination indicates a tampered build and is caught by SRI on deploy artefacts.
The embed iframe is loaded same-origin from qub.social and reaches the same set of destinations as the main client: sealed CBOR is fetched directly from Arweave gateways, and the reveal-time round signature is fetched from api.drand.sh and drand.cloudflare.com. The iframe's CSP forbids any other origin, so a compromised payload cannot exfiltrate data to a third-party host.
5. Server-Side Infrastructure
5.1 Serverless Edge
Our API runs entirely on a managed serverless runtime at the edge. There are no VMs, no containers, and no persistent server processes we administer. This dramatically reduces the attack surface we are responsible for: we do not run an OS, a web server, or an application runtime that we must patch.
A small "public CORS" middleware applies a wildcard Access-Control-Allow-Origin: * only to the tight allowlist of endpoints that the embed loader and iframe actually call: the loader script, the iframe shell, /api/v1/qub/{tx_id} together with its /meta, /watch, and /view actions, and /api/v1/telemetry. Every other API endpoint preserves the qub.social-restricted CORS policy unchanged.
5.2 Storage
- Our metadata store holds entitlement records, identity records, attestation records, denylist entries, per-key rate-limit counters.
- Our object store holds structured event logs (NDJSON) and a cache of read-only qub JSON responses.
- Arweave holds sealed payloads. We do not operate Arweave; we submit payloads through a server-side signing wallet and then rely on the network.
Plaintext content is never stored anywhere we operate. Our storage tier is metadata-only by construction.
5.3 Secrets
Secrets (signing wallets, API tokens for our payment and email providers, HMAC keys for magic-link tokens) are stored as platform-managed secrets. They are not visible in source control and are not readable by our runtime except by name. Rotation is manual and logged.
5.4 Logging and Telemetry
Structured JSON logs are written on every API request with a correlation ID surfaced in the X-Request-Id response header. Client telemetry is anonymous — no device identifier, no IP address, no content preview. Events are buffered in memory and flushed on a best-effort basis; a failed flush is discarded, not retried. Telemetry is designed to be disableable at the network layer without affecting the product.
6. Authentication
6.1 Magic-Link Sign-In
Sign-in uses a single-use, HMAC-signed token delivered to your email inbox. The link is valid for 15 minutes and is invalidated immediately on use. Token contents are opaque; the server verifies the HMAC on redemption. If the token has been tampered with, the signature does not verify and the request is rejected.
We deliberately chose HMAC over stored random tokens so that a read-only compromise of our metadata store does not yield usable sign-in tokens; an attacker would need the HMAC key, which is a platform-managed secret.
6.2 API Keys (Developer Tier)
Developer API keys use the prefix qub_sk_ for easy recognition and grepability. Each key:
- Is bound to an account and a set of allowed IP CIDR ranges
- Can be rotated with a grace period in which both the old and new key are accepted
- Has independent rate-limit counters in our metadata store
- Is never logged in full; logs record only the key identifier
Admin key-management endpoints are gated behind a separate admin credential.
6.3 Email Attestation (Authorship Signing)
Binding an email address to a signing key requires:
- Possession of the private signing key (you sign a challenge)
- Possession of the email inbox (you enter a 6-digit code delivered by email)
Either alone is insufficient. Revocation is a signed record on your own account and takes effect immediately; viewers fetching the attestation see the revoked state and display accordingly.
7. Payments
Payment processing runs entirely inside Stripe's checkout UI. We never see card numbers, expiry dates, or CVCs, and we do not store any of Stripe's customer identifiers beyond the entitlement binding. Stripe's privacy and security statements govern their handling of that data.
The seal endpoint cross-checks the entitlement record against the device identifier and, for signed-in users, against the linked identity. An entitlement cannot be reused across devices without the user explicitly restoring it via magic-link sign-in.
8. Abuse Resistance
8.1 Bot Detection
The seal flow is gated by a privacy-preserving CAPTCHA alternative that does not use cookies for tracking and does not fingerprint for advertising. A failed challenge blocks the seal request at the edge before it reaches our API.
8.2 Rate Limiting
Rate limits are enforced at several layers:
- Per-IP and per-key limits on seal, read, and auth endpoints
- Per-email limits on magic-link requests (prevents mailbox flooding)
- Per-counter-party limits on pact invite emails (three per address per day)
- Per-device limits on telemetry submission
Limits are counters in our metadata store; exceeding them returns a 429 response with a Retry-After header.
8.3 Content Moderation
We cannot scan sealed content — it is encrypted on your device. We operate a denylist at the viewer layer: a qub on the denylist is refused by our viewer regardless of whether the Arweave payload is reachable. This is the full extent of moderation our architecture permits. We are transparent that denylisting does not remove data from Arweave.
Abuse reports are rate-limited using a one-way hash of the reporter's IP; we do not store IPs in the clear for this purpose.
9. Supply Chain and Build Integrity
9.1 Toolchain Pinning
Compiler and runtime versions are pinned in repository configuration. All dependencies are pinned to exact versions via lockfiles. A build on a clean checkout produces a deterministic artefact.
9.2 Lints and Static Analysis
The workspace enables our strictest lint groups at the deny level. CI treats every warning — including documentation-link warnings — as a build failure. This is deliberate: we use the lint strictness as a tripwire for subtle regressions.
9.3 CI Gates
Every push runs:
- Formatting check
- Full lint sweep across all targets
- Doc-link verification
- Native test suite (hundreds of tests, including property-based tests)
- Headless browser tests
- i18n key-consistency script
- Server-side type check
- Server-side unit and property-based tests
Deploy workflows depend on CI passing. A branch cannot be merged to main until every gate is green.
9.4 Mutation Testing
A weekly job runs mutation testing against the security-critical pure modules (CBOR encoding, hashing, seal/unlock, pact validation). Mutation testing answers "does our test suite catch subtly wrong code?" — if a mutated implementation still passes all tests, we know we have a test-coverage gap and address it.
9.5 Git Hooks
Local hooks (pre-commit, pre-push) mirror the CI gates so regressions are caught before they leave the developer's machine. Hooks are installed via a repo script; they are not bypassed in our workflow and the CI is the authoritative gate if they are skipped.
10. Testing
Security-critical code carries three kinds of tests:
- Unit tests verify expected behaviour on known inputs, including test vectors derived from the protocol specification.
- Property tests generate thousands of arbitrary inputs and assert invariants: canonical CBOR round-trips, signature verification round-trips, email-binding predicates, pact acknowledgement determinism.
- Cross-implementation tests verify that our client and server implementations agree byte-for-byte on canonical encodings. This catches divergence between the two implementations before it reaches production.
11. Branch and Release Hygiene
main is only advanced by merging dev after CI is green. Deploy workflows are triggered from the deploy branch, not from developer pushes. There is no path by which an un-reviewed, un-CI-gated change reaches production.
Secrets used in deploy workflows are scoped to the deploy environment by our CI platform. They are not available to pull-request workflows from forks.
12. Coordinated Disclosure
If you believe you have found a security vulnerability in qub, we want to hear about it quickly and we commit to handling the report professionally.
- Email
support@qub.socialwith the subject prefix[SECURITY]. - Please describe the vulnerability, the steps to reproduce, and any proof-of-concept.
- Please give us a reasonable window (typically 90 days) to investigate and ship a fix before public disclosure.
- Do not access data that does not belong to you, degrade service for other users, or retain data obtained during research beyond what is necessary to demonstrate the issue.
We acknowledge receipt within three business days and keep you informed as we investigate. Where appropriate and with your consent, we credit reporters in release notes.
13. Honest Limitations
Security is a practice, not a state. Some limitations are worth naming directly:
- We are a small team. Our review depth does not match a large corporation's dedicated application-security function. We compensate with strict automated gates and a minimal attack surface, but we do not claim infallibility.
- The permanence of Arweave is a one-way door. If a mistake causes sealed content to become decryptable earlier than intended, we cannot undo it. We treat the seal flow with commensurate care.
- The drand network is an external dependency. A catastrophic failure of drand would affect every qub's reveal behaviour. We monitor drand health and have contingency documentation for chain migration if required.
- Cryptographic primitives we rely on are standardised and widely reviewed, but cryptography evolves. Where we have choices (post-quantum signing, authenticated encryption), we pick the more conservative option.
14. Changes to This Page
Material changes are noted by updating the effective date at the top. Where a change reflects a concrete security improvement, we describe it briefly in the public changelog. Where a change reflects a policy clarification, we describe what changed and why.
For questions about anything on this page, email support@qub.social with the subject prefix [SECURITY].