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:


2. Threat Model

2.1 What We Protect Against

2.2 What We Cannot Protect Against

We are honest about our limits. qub cannot defend against:


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:

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:

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

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:

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:

  1. Possession of the private signing key (you sign a challenge)
  2. 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:

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:

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:


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.

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:


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].