๐ก๏ธ Malicious-Upload Threat Model
Scope. The video-upload path: Flutter client โ
requestUploadPathcallable โ Firebase Storage โprocessVideoOnUploadStorage 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โ
| Actor | Motivation | Capability |
|---|---|---|
| Authenticated abuser (signed-up user, free trial) | Free GPU, mining, harassment, content laundering | Owns a real Firebase JWT, can call any callable, can write to their own Storage path |
| Account-farmer | DoS / cost amplification โ disposable email accounts at scale | Same as above, but รN |
| Compromised account (credential stuffing) | Pivot to another user's data, frame the victim | One real account's full surface |
| Determined web attacker (no account) | Probing for endpoints, SSRF via callable, signed-URL leaks | Public 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.VideoCapturehappily reads the metadata and downstream code (read_video_optimizedkeeps 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.
sanitizeFileNamestrips non-\wchars 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.
sessionIdis server-generated inrequestUploadPath; Storage rule pinsrequest.auth.uid == userIdin the path. Client-supplied sessionId is overwritten.
F. Status-machine forgery via Firestoreโ
- The
jobsrule allows the owner (or admin) to update any field, includingstatus. A user can flip their own job todoneand synthesize aresultPath. Today this only fools the admin UI / their own home screen โ but if we ever gate billing or quota onstatus: 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โ
| # | Threat | Likelihood | Impact | Priority |
|---|---|---|---|---|
| A | Cost-amplification via legitimate-looking video | High | High ($) | P0 |
| B | Metadata / decompression bomb | Med | Med | P1 |
| C | Polyglot / non-video upload reaches RunPod & bucket | Med | Med (compliance) | P1 |
| H | NSFW / illegal content sits in EU bucket | Med-over-time | High (legal) | P1 |
| F | Status-machine forgery via Firestore | Low (today) | High iff billing wires | P2 โ P0 when billing ships |
| G | Signed-URL leak from RunPod logs | Low | Med (one-user) | P2 |
| B/D | Filename-based path manipulation | Low | Low | P3 (already mitigated) |
5. Recommended controls โ defence in depthโ
Listed from cheapest to most disruptive. Each is a separate decision; do not bundle.
P0 โ within one weekโ
- 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. - 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โ
- 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. ffprobegate. Before dispatching to RunPod, runffprobeon the signed URL โ assertnb_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.- 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.
- Tighten Firestore
jobsrules. Owner can writeuserId,videoPath,metadataon create only; cannot mutatestatus,resultPath,processedAtafter create. Server-only fields. Kills threat F before billing depends on it.
P2 โ opportunisticโ
- 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
ffprobeis bypassed. - Audit log for signed URL issuance. Every URL minted โ Firestore append-only log. Lets us correlate RunPod incidents to specific URLs.
- Rate-limit
requestUploadPathitself (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. requestUploadPathvalidators โ extension whitelist, size cap, duration cap, sessionId server-issued.- Signed-URL TTL on the RunPod download link.
- The
jobsFirestore rule'supdateallowance โ once billing depends onstatus, 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).