DEV Community

FileShot
FileShot

Posted on

The Hidden Risk in Every File Sharing Link (And the Zero-Knowledge Solution)

Every time you share a file via Google Drive, Dropbox, or WeTransfer, you're making an implicit trust decision: I trust this server to not read my file.

For most files, that's fine. For sensitive files — contracts, credentials, medical records, source code — it's a significant risk that most developers ignore.

The Problem: Server-Side Trust

When you upload a file to a typical sharing service:

  1. Your file travels over HTTPS to their server
  2. Their server stores it (usually encrypted at rest, but they hold the key)
  3. They give you a share link

The server itself can read your file. So can employees with database access, law enforcement with a subpoena, and attackers who compromise their infrastructure.

What "Zero-Knowledge" Actually Means

Two conditions must both be true:

  1. Client-side encryption — the file is encrypted before leaving your browser
  2. Key never reaches the server — the decryption key is delivered out-of-band

HTTPS alone does not count — the server decrypts on arrival. Server-managed encryption does not count — they still hold the key.

The URL Fragment Solution

The hash fragment (#) part of a URL has a critical property: browsers never include it in HTTP requests. This is specified in RFC 3986.

This makes URL fragments a natural out-of-band channel for key delivery.

How FileShot.io Implements This

FileShot.io uses the Web Crypto API for AES-256-GCM encryption entirely in the browser:

// Generate 256-bit AES-GCM key
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

// Random 96-bit IV (correct for GCM)
const iv = crypto.getRandomValues(new Uint8Array(12));

// Encrypt the file buffer
const ciphertext = await crypto.subtle.encrypt(
  { name: 'AES-GCM', iv },
  key,
  fileBuffer
);

// Export key for fragment delivery
const rawKey = await crypto.subtle.exportKey('raw', key);
const b64Key = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
// Share URL: https://fileshot.io/d/FILE_ID + "#" + b64Key
Enter fullscreen mode Exit fullscreen mode

The server receives only the encrypted file and a random file ID — mathematically incapable of decryption.

Practical Implications

Protected against:

  • Server compromise — attacker gets ciphertext only
  • Subpoenas — server has nothing to hand over
  • Employee access — no plaintext stored

NOT protected against:

  • Compromised browser/endpoint
  • Sharing the complete URL to an untrusted party

Try It

fileshot.io — no account needed, free, no file size limit.

Self-hosted: github.com/FileShot/FileShotZKE (MIT)

Top comments (0)