Home / Resources / Privacy by Design: The Architecture That Makes Leaks Impossible

Privacy by Design: The Architecture That Makes Leaks Impossible

A deep-dive into Zippd's system architecture and the choices that remove categories of risk from the threat model.

Updated May 18, 2026

Most "secure" services treat privacy as a feature added to a normal architecture. Encryption gets bolted on, content scanning runs alongside, the database holds plaintext. A breach exposes everything.

Privacy-by-design flips the model. Start with the question: what can the vendor know? Build the system so the answer is "as little as possible, and never the content itself." Features that would require knowing more don't get built.

Here's how Zippd does it.

The principles

Ann Cavoukian articulated seven principles of privacy by design in the 1990s. The two that matter most for file sharing:

  • Privacy as the default. The user should not have to opt into protection. Anonymous uploads are first-class. Encryption is unconditional.
  • End-to-end security. Protection extends across the full lifecycle of the data. No "secure for the first 30 days then archive in plaintext" loophole.

The deeper principle: data minimization. Don't collect what you don't need. Don't keep what you don't need. Don't even know what you don't need.

What we know and don't know — by component

The web tier

Our Laravel application handles HTTP requests. What it sees:

  • The request URL (without fragment — browsers don't transmit fragments).
  • The HTTP headers (user-agent, cookies for logged-in sessions).
  • The request body (which for file uploads contains... nothing of value, because the actual file goes direct to storage).

What it never sees: file content, file names, encryption keys. The upload flow uses presigned URLs — the browser PUTs ciphertext directly to storage. Our application server is not in the file-content data path.

The database

Stored per file:

  • A random 16-byte public ID.
  • A random storage key (the S3 object path).
  • Ciphertext size in bytes.
  • Expiry timestamp.
  • Owner user ID (NULL for anonymous).
  • An opaque base64 metadata blob — encrypted in the browser with the same key as the file. Contains filename, MIME, plaintext size. We cannot decrypt this.
  • An HMAC of the upload IP — not the IP itself, hashed with the app key.

What's not stored: file content (lives on Wasabi), encryption keys (live only in URL fragments), plaintext IPs, plaintext filenames.

The storage tier

Wasabi (S3-compatible) holds ciphertext blobs. We don't put anything else there. The bucket has server-side encryption enabled as a belt-and-suspenders layer, but the real protection is client-side AES-256-GCM done by the browser before the byte reaches Wasabi.

Logs

Web server access logs contain the IP and request path for up to 30 days, then rotate out. Application-level logs contain hashed identifiers and timestamps, no plaintext content. Stack traces from errors get the same scrubbing.

Where keys live (and don't)

The decryption key for every file:

  • Generated on the uploader's device by crypto.subtle.generateKey().
  • Held in the uploader's browser memory during the upload session.
  • Base64-encoded and appended to the share URL after #k=.
  • Transmitted to the recipient through whatever channel the uploader chose.
  • Parsed by the recipient's browser when they visit the URL.

The key passes through:

  • The uploader's browser memory (briefly).
  • The recipient's browser memory (briefly).
  • Whatever message channel the URL was sent through.

The key does NOT pass through:

  • Zippd's web servers.
  • Zippd's database.
  • Zippd's storage tier.
  • Zippd's logs.
  • Any analytics service.

Defense in depth

Even with the zero-knowledge architecture, multiple layers reinforce each other:

LayerWhat it protects
HTTPS in transitNetwork observers
Wasabi server-side encryptionStolen disks at the storage tier
Client-side AES-256-GCMVendor-side reading; main protection
URL fragment for keysKey transmission to the server
Auto-expiry of filesReduces the exposure window
IP hashingAnonymity if logs leak
Sealed metadata blobFilename privacy

The tradeoffs we accepted

Privacy by design is a real cost. Features we deliberately don't ship:

  • Server-side file preview. We can't render a thumbnail because we can't see the file.
  • Search across files. Same reason. The dashboard lists files by public ID, not by content.
  • Server-side virus scanning. Can't scan what we can't read. We rely on aggressive expiry and abuse reports.
  • Recovery of lost links. Without the key (in the URL), we cannot recover the file. Period.
  • "Smart" notifications based on file content. Same fundamental reason.

These omissions are the cost of the privacy guarantee. They're acceptable.

How to verify the architecture

Don't take our word for any of it. Check:

  1. Open DevTools, upload a file. Watch the Network tab. The actual file content PUTs to *.wasabisys.com, not to our backend. The request bodies are ciphertext.
  2. Read crypto.js in Sources. The encryption logic is plain JavaScript. Search for crypto.subtle.encrypt and trace the flow.
  3. Inspect the share URL. Note the #k=... fragment. Note that this part of the URL was never in any HTTP request.
  4. Check the meta endpoint response. GET /api/files/{publicId}/meta returns an opaque base64 string for encrypted_meta. That's the encrypted filename. We can't read it.

What could still go wrong

Honest threat enumeration:

  • Compromised JavaScript. If our static assets are tampered with, the JavaScript could be modified to leak keys. Mitigated by serving over HTTPS and (eventually) SRI hashes.
  • Endpoint compromise. Malware on the uploader's or recipient's device. Encryption doesn't protect against this. Universal.
  • Side-channel attacks. Timing attacks against the Web Crypto API have been studied; browsers ship constant-time implementations.
  • Implementation bugs. The architecture is sound; the code that implements it could have bugs. Open inspection is the mitigation.

FAQ

Is the source code open?

The crypto runs in unminified JavaScript that you can audit in browser DevTools. The server code is not yet publicly published but is a fairly thin orchestration layer around standard S3 multipart operations.

Has the architecture been independently audited?

Not yet by a formal third party. The model is publicly described and verifiable individually.

What's stored about my uploads in plaintext?

Public ID, storage key, ciphertext size, expiry timestamp, owner user ID (if registered), hashed IP. Nothing about the file's actual content, name, or type.

What if a government compels disclosure?

We comply with valid legal process for what we have. We have ciphertext and an opaque metadata blob. We cannot produce the file's plaintext or its key.

Test it yourself

Upload a file with DevTools open. Walk through the architecture described here. It should match the bytes you see flowing.

Keep reading

Related articles

Explore topics