<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rudra Bhairav</title>
    <description>The latest articles on DEV Community by Rudra Bhairav (@rudra_bhairav_634c3d5d52b).</description>
    <link>https://dev.to/rudra_bhairav_634c3d5d52b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3890261%2F7f8b7e64-f17d-41ef-9ae7-d8ae70086238.png</url>
      <title>DEV Community: Rudra Bhairav</title>
      <link>https://dev.to/rudra_bhairav_634c3d5d52b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rudra_bhairav_634c3d5d52b"/>
    <language>en</language>
    <item>
      <title>How I Built a Secure File Transfer App with Django, ClamAV, and Cloudflare R2</title>
      <dc:creator>Rudra Bhairav</dc:creator>
      <pubDate>Tue, 21 Apr 2026 06:56:51 +0000</pubDate>
      <link>https://dev.to/rudra_bhairav_634c3d5d52b/how-i-built-a-secure-file-transfer-app-with-django-clamav-and-cloudflare-r2-3non</link>
      <guid>https://dev.to/rudra_bhairav_634c3d5d52b/how-i-built-a-secure-file-transfer-app-with-django-clamav-and-cloudflare-r2-3non</guid>
      <description>&lt;p&gt;I recently launched TransferSecure, a file transfer platform built for sending large files to clients and collaborators without forcing them to create an account. Here is a breakdown of how it works under the hood.&lt;/p&gt;

&lt;p&gt;The Stack&lt;br&gt;
Backend: Django 6 + Django REST Framework&lt;br&gt;
File Storage: Cloudflare R2 (S3-compatible) via boto3&lt;br&gt;
Virus Scanning: ClamAV via pyclamd&lt;br&gt;
Task Queue: Celery + Redis&lt;br&gt;
Database: PostgreSQL&lt;br&gt;
Frontend: Django templates + Bootstrap 5 + vanilla JS&lt;/p&gt;

&lt;p&gt;The Upload Flow&lt;br&gt;
The browser never uploads files through Django. Instead:&lt;br&gt;
The frontend requests presigned upload URLs from the backend&lt;br&gt;
Files are uploaded directly from the browser to Cloudflare R2 using those URLs&lt;br&gt;
Once all parts are uploaded, the frontend calls a confirm endpoint&lt;br&gt;
Django sets the transfer status to scanning and triggers a Celery task&lt;br&gt;
The Celery worker streams each file from R2 directly into ClamAV without writing to disk&lt;br&gt;
Once scanning is complete, the transfer becomes active and recipients are emailed&lt;br&gt;
This keeps Django out of the upload path entirely, which means no memory pressure on the server regardless of file size.&lt;/p&gt;

&lt;p&gt;Virus Scanning&lt;br&gt;
ClamAV scans files up to 200 MB by streaming them directly from R2 into the ClamAV daemon via pyclamd. Files larger than 200 MB are accepted but bypass active scanning due to stream length constraints — this is disclosed in the terms of service.&lt;br&gt;
If a virus is detected, the file is deleted from R2 immediately, storage quota is refunded, and the infected file is excluded from the transfer. If all files in a transfer are infected, the transfer is marked deleted and recipients are never notified.&lt;/p&gt;

&lt;p&gt;Anonymous Sending&lt;br&gt;
Registered accounts are not required to send files. Anonymous senders verify their email via a 6-digit OTP before uploading. This gives just enough identity verification to prevent abuse without forcing sign-ups.&lt;/p&gt;

&lt;p&gt;Multipart Uploads&lt;br&gt;
Files above 100 MB use S3 multipart upload. The frontend splits the file into 10 MB chunks, uploads each part with a presigned URL, and calls a complete-part endpoint for each chunk. The backend tracks part completion and finalizes the multipart upload once all parts are confirmed.&lt;/p&gt;

&lt;p&gt;What I Learned&lt;br&gt;
Streaming files from R2 into ClamAV without touching disk works well in practice but requires careful handling of ClamAV's StreamMaxLength limit&lt;br&gt;
Presigned URLs for direct browser-to-R2 uploads eliminate a whole class of server memory and timeout problems&lt;br&gt;
Celery's retry mechanism is essential for ClamAV — if the daemon is temporarily unavailable, the task retries with backoff instead of silently failing&lt;br&gt;
Live at: &lt;a href="https://transfersecure.ai" rel="noopener noreferrer"&gt;transfersecure.ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any part of the implementation.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>python</category>
      <category>security</category>
    </item>
  </channel>
</rss>
