Skip to main content

๐Ÿ›ก๏ธ Malicious-Upload Threat Model

Scope. The video-upload path: Flutter client โ†’ requestUploadPath callable โ†’ Firebase Storage โ†’ processVideoOnUpload Storage trigger โ†’ RunPod GPU pipeline โ†’ results in Storage. Goal: enumerate what can be smuggled in, what damage it can do, and what to fix in priority order. No implementation here โ€” that's deliberate.


1. Threat actors we care aboutโ€‹

ActorMotivationCapability
Authenticated abuser (signed-up user, free trial)Free GPU, mining, harassment, content launderingOwns a real Firebase JWT, can call any callable, can write to their own Storage path
Account-farmerDoS / cost amplification โ€” disposable email accounts at scaleSame as above, but ร—N
Compromised account (credential stuffing)Pivot to another user's data, frame the victimOne real account's full surface
Determined web attacker (no account)Probing for endpoints, SSRF via callable, signed-URL leaksPublic unauth surface only

What we are not in scope for here: insider threat, RunPod side-channel, Firebase platform compromise. Those are upstream of this brief.


2. Pipeline at a glanceโ€‹

Flutter client
โ”‚ pickVideo (image_picker) โ† extension + duration claim only
โ”‚ validateForUpload โ† size/duration/extension by metadata
โ–ผ
requestUploadPath (callable, EU-W1)
โ”‚ requireAuth โœ…
โ”‚ size โ‰ค 2 GB โœ… duration โ‰ค 7200 s โœ… ext โˆˆ {mp4,mov,avi,mkv} โœ…
โ”‚ filename sanitized โœ… sessionId server-issued โœ…
โ”‚ โŒ no per-user quota / concurrency cap
โ”‚ โŒ no MIME / magic-byte / probe
โ–ผ
Firebase Storage videos/{uid}/{sessionId}/{file}
โ”‚ rules: owner-only write, size < 2 GB โœ…
โ”‚ โŒ no content-type rule, no extension rule (delegated to function)
โ–ผ
processVideoOnUpload (Storage trigger)
โ”‚ prefix + extension filter โœ…
โ”‚ job-doc lookup โœ…
โ”‚ โŒ no ffprobe / no codec sanity / no size re-check
โ”‚ signed download URL โ†’ RunPod
โ–ผ
RunPod handler (Python)
โ”‚ exists + ext check โœ…
โ”‚ โŒ no magic bytes, no ffprobe, no max-frames, no max-pixels
โ”‚ cv2.VideoCapture(url) โ†’ full-video buffering
โ–ผ
results/{uid}/{sessionId}/ (Storage; client read; client write denied โœ…)

The only thing that ever sees the file's bytes before RunPod loads them into RAM is Firebase Storage โ€” which checks size and ACL, nothing else.


3. Attacks worth taking seriouslyโ€‹

A. Cost-amplification DoS via "valid-looking" videoโ€‹

  • Client uploads a ~2 GB H.264 mp4 that decodes to a 4 K @ 60 fps stream lasting the full 2 hours (legal under our caps).
  • RunPod allocates a GPU, the pipeline runs end-to-end. With no per-user quota the same account can do this on a loop and burn RunPod credit at ~$/min.
  • Likelihood: high. Impact: financial only โ€” but uncapped. This is the single most important gap.

B. Decompression / metadata bombโ€‹

  • A crafted mp4 with a header advertising tens of millions of frames, or a Matroska container with a malicious EBML element. cv2.VideoCapture happily reads the metadata and downstream code (read_video_optimized keeps a dual-resolution cache) tries to allocate accordingly.
  • Likelihood: medium. Impact: OOM-crashes the worker. Self-limited (RunPod retries 3ร—, then job stalls) but noisy and expensive. No data leak.

C. Polyglot / non-video uploadโ€‹

  • File named clip.mp4, real bytes are HTML/JS/PDF/zip. Reaches RunPod because nothing inspects content. RunPod's OpenCV will fail to open it and the pipeline errors out โ€” but the bytes already lived in our Storage bucket and were served via signed URL to a third party (RunPod) that is not contractually a sub-processor for arbitrary user content.
  • Likelihood: medium. Impact: GDPR/DPA grey zone โ€” we redistributed user-uploaded non-video data to RunPod. Also: phishing kit / malware hosted in our bucket and addressable via the signed URL until expiry.

D. Path-traversal-ish via filenameโ€‹

  • Mitigated. sanitizeFileName strips non-\w chars and lowercases. Worth re-confirming on every change to that helper โ€” it is the only thing standing between a user-controlled string and a Storage object key.

E. SessionId prediction / cross-tenant writeโ€‹

  • Mitigated. sessionId is server-generated in requestUploadPath; Storage rule pins request.auth.uid == userId in the path. Client-supplied sessionId is overwritten.

F. Status-machine forgery via Firestoreโ€‹

  • The jobs rule allows the owner (or admin) to update any field, including status. A user can flip their own job to done and synthesize a resultPath. Today this only fools the admin UI / their own home screen โ€” but if we ever gate billing or quota on status: done, this becomes a free-tier-bypass.
  • Likelihood: low (no current incentive). Impact: depends on what we wire to status. Worth a rule tightening before billing flips on.

G. Signed-URL leak (RunPod side)โ€‹

  • The Cloud Function generates a signed download URL with default TTL and hands it to RunPod. If RunPod logs the URL or we leak it in error reports, anyone with the link gets the user's raw video until expiry.
  • Likelihood: low. Impact: privacy disclosure of one user. Mitigated partly by short TTL โ€” confirm TTL is โ‰ค 1 h on the download URL, not just upload.

H. NSFW / illegal content uploadsโ€‹

  • We have no scanner. A user can upload anything visually arbitrary; it sits in our EU bucket forever. Apple/Google App Store policies require us to be able to take it down on report. Today our only lever is manual deletion via the admin panel.
  • Likelihood: medium over time. Impact: App Store removal risk + legal exposure if CSAM ever lands.

4. Risk matrixโ€‹

#ThreatLikelihoodImpactPriority
ACost-amplification via legitimate-looking videoHighHigh ($)P0
BMetadata / decompression bombMedMedP1
CPolyglot / non-video upload reaches RunPod & bucketMedMed (compliance)P1
HNSFW / illegal content sits in EU bucketMed-over-timeHigh (legal)P1
FStatus-machine forgery via FirestoreLow (today)High iff billing wiresP2 โ†’ P0 when billing ships
GSigned-URL leak from RunPod logsLowMed (one-user)P2
B/DFilename-based path manipulationLowLowP3 (already mitigated)

Listed from cheapest to most disruptive. Each is a separate decision; do not bundle.

P0 โ€” within one weekโ€‹

  1. Per-user upload quota. N uploads per day, M concurrent jobs per user. Pure Firestore counter, gated in requestUploadPath. Kills threat A without touching the pipeline.
  2. Tight signed-URL TTL on the RunPod handoff. Confirm download URL โ‰ค 30 min; explicit assertion in processVideoOnUpload. No code change if it's already short โ€” just write the test.

P1 โ€” within one monthโ€‹

  1. Magic-byte check at the function boundary. In processVideoOnUpload, fetch the first ~64 bytes of the Storage object and reject anything whose ftyp/EBML header doesn't match the declared extension. Cheap, kills threat C.
  2. ffprobe gate. Before dispatching to RunPod, run ffprobe on the signed URL โ€” assert nb_frames, resolution, and duration match what the client claimed (within 5 %). Reject everything else. Kills threats A and B at the function layer; RunPod never sees a bomb.
  3. Storage-side content moderation. Wire Cloud Vision SafeSearch (or a lighter equivalent) on first frame extraction. Quarantine + auto-delete on adult/violence/medical. Mitigates threat H. Document the legal basis under our existing DPIA.
  4. Tighten Firestore jobs rules. Owner can write userId, videoPath, metadata on create only; cannot mutate status, resultPath, processedAt after create. Server-only fields. Kills threat F before billing depends on it.

P2 โ€” opportunisticโ€‹

  1. RunPod-side memory budget. Refuse to allocate frame buffers above a hard ceiling (e.g. 8 GB of decoded frames). Belt-and-braces against B even if the function-side ffprobe is bypassed.
  2. Audit log for signed URL issuance. Every URL minted โ†’ Firestore append-only log. Lets us correlate RunPod incidents to specific URLs.
  3. Rate-limit requestUploadPath itself (Cloud Armor / function concurrency) for unauthenticated probes โ€” separate from per-user quota.

Won't-do (yet)โ€‹

  • ClamAV / signature scanning for malware. We don't redistribute the file to other users; RunPod doesn't execute it. Cost > benefit until we add a "share clip" feature.
  • Watermark detection / stolen-content matching. Out of scope for an MVP fundraising-stage product.

6. Things to re-check whenever the pipeline changesโ€‹

  • sanitizeFileName() โ€” only thing between user input and Storage path keys.
  • Storage rules videos/{userId}/{sessionId}/{fileName} โ€” owner-only + size cap.
  • requestUploadPath validators โ€” extension whitelist, size cap, duration cap, sessionId server-issued.
  • Signed-URL TTL on the RunPod download link.
  • The jobs Firestore rule's update allowance โ€” once billing depends on status, this becomes a P0.

7. Out of scopeโ€‹

  • Auth surface itself (covered in compliance/breach-response.md).
  • RunPod sub-processor agreement (covered in compliance/dpas).
  • Admin-side abuse (one human, contractual control).

Owner: akshaysarode@acesense.io ยท Review cadence: every quarter or whenever the upload pipeline changes (whichever is sooner).