loading . . . Securing DotShare 3.3: Securing DotShare 3.3: Inside the Rust Media API (Magic Bytes, Atomic Quotas, & Race Fixes)âââPart 3 Our journey building a production-grade Cloudflare R2 upload pipeline in Rustâââfeaturing layered security, an atomic quota system, and a zero-trust file validation strategy. In Part 2 of this series , we successfully enforced Strict Separation and Fail-Fast validation to prevent silent scheduling failures and auto-refresh OAuth tokens in the background. We thought the hardest part was over. But our users still needed to attach images to their scheduled posts. And the moment you allow users to upload files to your server, you inherit one of the most complex security problems in backend engineering. The golden rule here is simple but harsh: you can never trust what a user sends you. Here is the full story of how we built the POST /v1/media/upload endpoint in Rustâââstarting from strict file validation, moving through Cloudflare R2 storage, and culminating in an atomic quota system that saved us from a highly elusive race condition. The Problem: File Uploads Are a Security Minefield Allowing file uploads without meticulous validation is a hackerâs favorite way to compromise a server. The naive approachâââchecking the file extension or blindly trusting the Content-Type headerâââis the equivalent of leaving your vault doors wide open. A malicious user can trivially rename exploit.php to photo.jpg and send it with Content-Type: image/jpeg. A server that trusts that header will happily store an executable PHP script in a directory meant for innocent images. A complete disaster. We needed a Zero-Trust validation pipeline. The new rule: Donât believe anything the client tells you. Verify everything from the raw binary. Layer 1: The Gatekeepers & Body Limits (Router Level) Before a single byte of the file payload is even parsed, two guards stand watch at the Axum router level. The first is our existing Bearer JWT middlewareâââno valid token, no entry. The second is a global body size limit enforced directly on the router: // src/main.rs use axum::extract::DefaultBodyLimit; let app = Router::new() .route("/v1/media/upload", post(routes::media::upload_media)) // ... other routes ... .layer(DefaultBodyLimit::max(10 * 1024 * 1024)); // 10 MB hard cap This DefaultBodyLimit layer brutally rejects oversized requests at the framework levelâââlong before our handler allocates any memory for the file. This is our outermost defense wall. Layer 2: Magic Bytes (The Fileâs DNA) Inside the handler, we apply a second, stricter file size check (5 MB) and then move to the core of our zero-trust strategy: Magic Bytes validation . Every legitimate image file format begins with a known binary signature, called a âmagic number,â embedded in the first few bytes of the file. JPEG files always start with FF D8 FF. PNG files always start with 89 50 4E 47. You cannot fake this fingerprint without entirely corrupting the file. Here is how we built our ultimate lie detector: // src/routes/media.rs // Allowed MIME types and their corresponding magic bytes const ALLOWED_TYPES: &[(&str, &[&[u8]], &str)] = &[ ("image/jpeg", &[&[0xff, 0xd8, 0xff]], "jpg"), ("image/png", &[&[0x89, 0x50, 0x4e, 0x47]], "png"), ("image/webp", &[&[0x52, 0x49, 0x46, 0x46]], "webp"), // RIFF header ("image/gif", &[&[0x47, 0x49, 0x46, 0x38]], "gif"), ]; fn validate_magic_bytes(buffer: &[u8], mime_type: &str) -> Option<&'static str> { for (allowed_mime, magics, ext) in ALLOWED_TYPES { if *allowed_mime == mime_type { for magic in *magics { if buffer.starts_with(*magic) { return Some(ext); // Return the verified extension } } } } None // Content-Type claimed a valid MIME but binary doesn't match - reject } This function hits two birds with one stone. First, it ensures the claimed Content-Type is on our whitelist. Second, it verifies that the actual binary content matches that format. An executable masquerading as an image will fail miserably here because its binary signature will never match FF D8 FF. Layer 3: Burning the Filename (Path Traversal Prevention) Even after validating the fileâs integrity, you should never use the client-supplied filename for storage. A filename like ../../../etc/passwdâââknown as a Path Traversal attackâââcould theoretically escape your intended storage directory and overwrite critical system files. Our solution? Burn the original filename entirely and generate a random UUID to serve as the storage key: // The raw client filename is intentionally discarded. // UUID-based key â fully immune to path traversal. let file_name = format!("dotsuite/scheduled_posts/{}.{}", Uuid::new_v4(), ext); Notice that the extension (ext) comes from our trusted validate_magic_bytes functionââânot the client. The entire storage path is server-generated. The user's filename never touches our storage layer. Layer 4: Atomic Quotas (Closing the Race Condition) This is where things got incredibly tricky. Initially, our user quota check looked entirely reasonable: // â The naive (broken) approach let current = user.images_used; // Read from DB if current >= image_quota { return Err(quota_exceeded_error); } // ... upload the file ... db.increment_images_used(user_id).await; // Write to DB The Fatal Flaw: There is a microscopic window of time between reading the quota and writing the increment. What if a user sends two upload requests at the exact same millisecond, and their counter is at Limit - 1? Both requests will read current < limit, both will pass the check, and both will upload the fileâââsilently bypassing the quota and pushing the counter to Limit + 1! To fix this, we utilized the power of atomic operations in MongoDB. We used find_one_and_update to combine the check and the increment into a single, indivisible database command: // src/routes/media.rs // ââ Atomic quota check + slot reservation ââââââââââââââââââââââââââââââââ // find_one_and_update eliminates the race condition: the check and the // increment are a single atomic MongoDB operation, not two separate ones. let quota_filter = if user.tier == Tier::Free { mongodb::bson::doc! { "_id": user_id, "$expr": { "$lt": ["$images_used", image_quota as i64] } } } else { // Paid tiers have no hard cap - we still track for analytics. mongodb::bson::doc! { "_id": user_id } }; let reserved = users_col .find_one_and_update( quota_filter, mongodb::bson::doc! { "$inc": { "images_used": 1 } }, ) .await?; if reserved.is_none() { return Err(AppError::Forbidden(format!( "Image upload quota reached ({}/{} uploads). Upgrade to Basic for unlimited uploads.", user.images_used, image_quota ))); } Now, the database acts as the single source of truth. No two concurrent requests can bypass the gate simultaneously because MongoDB guarantees the atomicity of the operation. Layer 5: Cloudflare R2 Upload & The Rollback After securing a slot in the quota, we upload the file to Cloudflare R2 via the AWS S3-compatible SDK. But wait, what if the cloud upload fails after weâve already incremented the userâs quota counter? The user would be penalized for a network failure they didnât cause. To handle this gracefully, we perform a rollback on failure: if let Err(e) = upload_result { tracing::error!("Failed to upload to R2: {:?}", e); // Rollback: return the slot so the quota isn't wasted. let rollback = users_col .update_one( mongodb::bson::doc! { "_id": user_id }, mongodb::bson::doc! { "$inc": { "images_used": -1i32 } }, ) .await; if let Err(rb_err) = rollback { tracing::error!( "Failed to rollback images_used for user {}: {}", user_id, rb_err ); } return Err(AppError::Internal(anyhow::anyhow!( "Failed to upload media to cloud storage" ))); } This rollback is a âbest-effortâ execution. We log a critical error if it fails, but we keep the internal mechanics hidden from the user. The Big Picture: Our Security Arsenal Here is exactly what happens behind the scenes on every single upload request: 1. Bearer JWT Middleware What It Rejects: Unauthenticated ghost requests. 2. DefaultBodyLimit (10 MB) What It Rejects: Massively oversized payloads before memory allocation. 3. Handler Size Check (5 MB) What It Rejects: Files exceeding the per-file business limit. 4. Magic Bytes Validation What It Rejects: Forged MIME types, executables, and corrupted files. 5. Atomic find_one_and_update What It Rejects: Quota-exceeded requests (entirely immune to race conditions). 6. UUID Storage Keys What It Rejects: Path traversal (../) attacks. 7. R2 Upload + Rollback What It Rejects: Storage network failures (without robbing the userâs quota). None of these layers are sufficient on their own. Together, they form a defense-in-depth architecture where every layer assumes the previous one could have been compromised. Conclusion Building a production-grade file upload endpoint is deceptively complex. Writing the surface-level logicââââreceive file, save fileââââtakes an hour. Hardening it against the harsh reality of the internet takes days. Here are the 3 biggest lessons from this build: Never trust Content-Type . A header is merely a claim, not proof. Always read the raw binary signature (Magic Bytes) of the file. Checking a quota and incrementing it must be one atomic operation. Issuing two separate database callsâââeven milliseconds apartâââcreates a race window that determined users will inevitably exploit. Reserve resources before expensive operations, and roll back on failure. Deducting the quota before the R2 upload, and refunding it upon failure, is far safer than trying to deduct it after a successful upload that might drop midway. Our POST /v1/media/upload endpoint is now an impenetrable vault. In Part 4, we will build the Next.js scheduling UI that communicates with this API. FreeRave is the founder of DotSuite , building productivity tools for developers. Follow for more deep dives into real-world production architecture. Securing DotShare 3.3: was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story. https://infosecwriteups.com/securing-dotshare-3-3-82524ce1d776?source=rss----7b722bfd1b8d---4