Case Study · Applied Security

Engineering Encrypted Document Storage for DOSCARS Mobile

A real-world walkthrough of how I designed and implemented secure document handling for a global vehicle export business — including threat modeling, storage rule design, and file encryption at rest.

Project DOSCARS US LLC · Mobile App Role Sole Builder / Security Lead Stack Firebase · Supabase · AWS Year 2025–2026

The Problem

DOSCARS US LLC is an independent wholesale dealership that exports exotic and luxury vehicles across the U.S., Middle East, and international markets. That business moves a specific class of document that cannot leak:

The DOSCARS Mobile application needed to let clients, buyers, and compliance partners upload, view, and share these documents from iOS and Android — securely, reliably, and without a full backend team to maintain a custom solution.

What's at stake
A leaked title can enable title fraud on a six-figure vehicle. A leaked passport scan can enable identity theft across jurisdictions. A leaked contract can expose sensitive pricing to competitors. "Good enough" security was not good enough.

The Threat Model

Before touching a single line of code, I mapped out the adversaries, their motivations, and where they'd most likely attack. Four primary threat categories emerged:

Unauthorized document access
High
An authenticated user of the app accessing another user's documents through direct URL enumeration or misconfigured access rules.
Stolen device / offline extraction
High
A lost or stolen phone with the app installed — attacker attempts to recover cached documents from local storage without the user's credentials.
Compromised backend / insider
Medium
Access to the storage bucket by a compromised backend credential or a malicious insider — even with read access, attackers should see only ciphertext.
URL / link sharing leakage
Medium
Users inadvertently sharing long-lived signed URLs in email or messaging apps, giving anyone with the link persistent access.

Design Decisions

Why Firebase + Supabase, not AWS S3 directly

S3 was the obvious default, but for a one-person build I needed platforms with first-class row-level / path-level security rules, built-in auth, and short-lived signed URLs out of the box. Firebase Storage and Supabase Storage both ship with these primitives. S3 can get there, but only with meaningful Lambda, IAM, and KMS glue — added complexity means added misconfiguration risk.

Why two platforms, not one

Firebase hosts the real-time and transactional side — auth, client profiles, inventory state. Supabase hosts the document-heavy and relational side — contracts, export records, search over metadata. Splitting them gives:

Implementation Highlights

1. Path-level storage rules (Firebase)

Documents live under a per-user path. Access is denied by default; only the owning user (or an admin with a custom claim) can read or write:

// Firebase Storage Rules — simplified
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/docs/{docId} {
      allow read, write: if request.auth != null
        && request.auth.uid == userId
        && request.resource.size < 25 * 1024 * 1024
        && request.resource.contentType.matches('application/pdf|image/.*');
    }
    match /admin/{allPaths=**} {
      allow read, write: if request.auth.token.admin == true;
    }
  }
}
Key Controls
Authenticated-only · owner-scoped · size-capped · MIME-type whitelisted · admin isolation via custom claim.

2. Client-side encryption before upload

Storage rules protect against unauthorized access. They do not protect against a compromised storage provider or insider reading your data directly. So documents are encrypted on the device using a per-user key derived from the user's auth session — the cloud only ever sees ciphertext:

// Pseudo-code — AES-GCM client-side encryption flow
const uploadSecureDoc = async (file, user) => {
  const key  = await deriveKey(user.uid, user.sessionSecret);
  const iv   = crypto.getRandomValues(new Uint8Array(12));
  const data = await encryptAES_GCM(file, key, iv);

  await firebaseStorage
    .ref(`users/${user.uid}/docs/${uuid()}.enc`)
    .put(data, { customMetadata: { iv: b64(iv) }});
};

3. Short-lived signed URLs

When a document is shared with a compliance partner, the app generates a URL that expires in 15 minutes and is single-use. No long-lived links in inboxes or Slack threads. If the recipient needs access again, they request it again.

4. Metadata separation

Sensitive document contents live encrypted in storage. Document metadata (title, upload date, related vehicle) lives in Postgres (Supabase) with row-level security, so the UI can list and search documents without ever touching the encrypted blobs unless a decryption is explicitly requested.

What I Learned

Lesson 1
Storage rules are a floor, not a ceiling. Even the best access rules don't protect against a provider-side compromise. Encrypt sensitive content before it leaves the device — the cloud should never see plaintext you care about.
Lesson 2
Default deny, whitelist intent. Every storage rule I wrote starts by denying everything. Access is then granted explicitly for exactly the path, user, and operation the product needs. Fail safe.
Lesson 3
Short-lived links are non-negotiable. 15-minute signed URLs created more friction for partners initially, but eliminated an entire class of real-world document leaks that would have happened through normal human behavior.
What I'd Do Differently
Given more time, I'd move the client-side key derivation to a hardware-backed keystore (iOS Keychain / Android Keystore) for extra protection against memory-dumping attacks on compromised devices. That's on the roadmap for v2.

Interested in the work?

If you'd like to discuss this case study, DOSCARS, or how cybersecurity thinking applies to your product — reach out.

Get in Touch →