Every application that accepts a file from a stranger is making a small bet: that the bytes arriving at the server are what they claim to be. We spent a few months building a platform for academic researchers where PDF, JPEG, and PNG uploads aren’t a nice-to-have feature. They’re the entire point. And once we sat with that requirement long enough, the bet started to look worse than we’d assumed.
Client-side checks, a file extension filter, a MIME type read off the browser, tell you almost nothing about what a file actually is. They tell you what the client claims it is. An attacker controls the client. So the moment we drew the architecture diagram honestly, the safety question moved entirely to the server, and we realized we didn’t have a good answer for it yet.
The gap we didn’t expect to find
We did what most engineering teams do before writing a line of code: we searched for something we could install. A server-side scanning service, something that would sit between “file arrives” and “file is trusted,” quarantine it, run it through real malware detection, and hand back a verdict. We weren’t looking for a library to bolt onto our own upload handler. We wanted a standalone package doing the job end to end.
We didn’t find one. There are libraries that wrap ClamAV for a single language runtime, and there are academic prototypes built as middleware for a specific framework. What we couldn’t find was a general-purpose, server-side scanning service built to run independently of any single application, with its own quarantine model, its own worker layer, and its own state machine for what happens to a file between “uploaded” and “safe.” So we built one, and we’re releasing it as an open-source project called Bytewall so the next team asking the same question doesn’t have to start from zero. The full code is on GitHub at github.com/EurekAI-Org/bytewall, under the MIT license.
Before we get to how it works, it’s worth sitting with why this problem is bigger than one project, and with the research that shaped how we built it.
Why file upload is a genuine, recurring weakness
MITRE catalogs this exact failure mode as CWE-434, “Unrestricted Upload of File with Dangerous Type.” Public vulnerability trackers disagree on exactly how many disclosed CVEs map to it (the count depends on which database and which mapping methodology you’re reading), but every source we checked agrees on the shape of the problem: it’s not a rare edge case, it’s a repeating pattern spanning two decades of software.
The clearest recent data point comes from Wordfence’s 2024 WordPress security report. Inside the WordPress plugin and theme ecosystem specifically, arbitrary file upload vulnerabilities were the single most common issue in the “high-threat” category that year, ahead of privilege escalation, and accounted for 38% of all high-threat disclosures. That’s one ecosystem, not “the web” in general, and we want to be precise about that scope rather than borrow a bigger number than the source supports.
Academic work backs the same conclusion from a different angle. Pooj and Patil’s study on file upload security catalogs sixteen distinct ways an unrestricted upload can be exploited, ranging from the obvious (no server-side filter at all) to the genuinely subtle: bypassing a blacklist by appending a null byte after a dangerous extension, hiding an executable inside an Office or PDF file’s embedded objects, or overwriting a server’s own .htaccess configuration to make it interpret harmless-looking files as executable code. What struck us reading through their case list wasn’t any single technique. It was how many of them exploit the gap between what a filename claims and what the file actually contains, which is precisely the gap client-side validation cannot see across.
What our research told us before we wrote a line of code
We didn’t limit ourselves to blog posts and vendor documentation while designing Bytewall. Three papers in particular shaped specific decisions in the architecture, and we want to name them rather than gesture vaguely at “research.”
Wichmann, Groddeck, and Federrath’s FileUploadChecker, presented at ARES 2022, is the closest thing we found to prior art, and reading it told us as much about the limits of the field as it did about the solution. Evaluating their tool against eighteen file-upload CVEs disclosed in WordPress, Drupal, Xwiki, and OpenCms between 2017 and 2021, they found it fully prevented exploitation of 56% of those vulnerabilities and partially mitigated another 33%, for a combined 89% coverage, with an average false positive rate of roughly 2 to 4% depending on file type. Two numbers from that paper mattered most to us. First, even a purpose-built detection tool, backed by ClamAV signatures and YARA rules, tested clean, and file-type-specific validation, could not fully protect against every disclosed vulnerability. Some attacks (XML external entity exploits among them) evaded it entirely. That is not a criticism of their work; it is the reason we built Bytewall as a layered pipeline rather than a single clever filter. Second, their own adversarial testing used Fuxploider, the same penetration testing tool we used against Bytewall, and threw 3,465 exploit attempts at PDF, image, and Office file uploads. Every one was rejected. We ran a smaller version of the same test and got the same result, which told us we were at least reproducing a known-good baseline, not inventing our own pass criteria.
That said, we don’t want to overstate what a clean Fuxploider run proves. A more recent paper, Neef and Oudeh’s 2024 work introducing the FUEL testing framework, evaluated several unrestricted file upload scanners, Fuxploider included, against fifteen deliberately constructed vulnerability scenarios, and found that no existing scanner caught all of them. In their benchmark, standard Fuxploider identified roughly half of the modeled vulnerability classes; their extended version, Fuxploider-NG, pushed that past 90%. We’re citing this against our own result on purpose: a clean run from stock Fuxploider is real evidence that a defined set of attack patterns didn’t get through, not proof that no attack pattern could. It is one data point in a body of research that keeps finding the same thing from different angles, that no single scanning tool, ours included, closes every gap.
The third paper we leaned on directly informed why Bytewall runs two detection engines instead of one. Aslan and Samet’s 2020 review of malware detection approaches makes a point that is easy to state and easy to forget in practice: signature-based detection, which is what ClamAV primarily does, is fast and reliable against known malware but is structurally unable to catch anything it hasn’t seen a signature for, including obfuscated or repackaged variants of malware it would otherwise recognize. Their review is blunt about the ceiling here: no single detection approach, of the many surveyed, catches everything in the wild. That’s the argument for running YARA rules alongside ClamAV rather than treating a signature match as the finish line. YARA’s pattern-based rules catch some of what a pure signature database misses, and neither one is asked to be a complete answer on its own.
What we actually built
Bytewall separates the part of the system that talks to the internet from the part that touches the file. A FastAPI service accepts the upload, validates the filename and extension, checks the real MIME type against the file’s content rather than trusting its label, enforces a size limit, and writes the file straight into quarantine storage. That storage is RustFS, an S3-compatible object store we run ourselves rather than renting from a cloud provider, which keeps the files we’re scanning inside infrastructure we control end to end. The API never trusts the file enough to hand it to anything else in the system directly.
Several of these choices map directly onto the mitigation list Pooj and Patil lay out at the end of their paper: use an allowlist rather than a blacklist of file types, never trust the filename a user supplies, generate a new random name instead, store uploads somewhere the web server won’t execute them, and scan everything with antivirus before it’s trusted. We didn’t invent this list. We implemented it, and then added the asynchronous scanning layer their 2016 paper didn’t need to address because the applications they studied weren’t handling files at the volume or size that made synchronous scanning impractical.
From there, the request is done, but the file’s journey isn’t. The API writes a record to PostgreSQL and dispatches a task onto a Celery queue. A separate pool of workers, which never accept public traffic, picks the task up and runs the file through ClamAV and a set of YARA rules. Only after both come back clean does the file move out of quarantine into a state we’re willing to call safe. Every file sits in one of a small number of states: pending, clean, suspicious, malicious, or failed, and nothing downstream is allowed to read a file that hasn’t reached “clean.”
Splitting the API from the workers also solves a problem that has nothing to do with security: latency. Scanning a file, especially a larger PDF or a batch of images from a research dataset, takes real time. Nobody wants an upload request to hang while ClamAV works through a multi-megabyte file. Because the scanning happens in Celery workers running independently of the request cycle, the person uploading gets an immediate response and a status they can poll, not a spinner tied to how long the scan takes.
How we tried to break it before anyone else could
Design intentions and working software are different claims, so we tested the pipeline rather than assuming the architecture diagram was enough. We ran Fuxploider against the upload endpoint using the same attack methodology as the FileUploadChecker evaluation, attempting dangerous extensions, multiple-extension bypasses, and null-byte tricks drawn from the exploitation patterns Pooj and Patil catalogued, and the pipeline returned no successful bypass. Given what the FUEL paper found about Fuxploider’s real ceiling, we’re treating that result as a passed baseline test, not a certificate of invulnerability. It tells us the well-documented attack patterns don’t get through. It doesn’t tell us nothing does.
We also built a small React front end during development, mainly to answer a UX question that mattered more than we expected: what does a person waiting on a scan actually need to see? A progress indicator that reflects the file’s real state, queued, scanning, clean, or flagged, turns out to change how much a user trusts the wait. That’s a product decision as much as a security one, and it’s the kind of seam Thinking in System tends to live in.
What we haven’t solved, and what we’re handing over
Bytewall doesn’t claim to be a complete answer, and the research we built it on says plainly that nothing does. It’s a foundation: input validation, quarantine isolation, dual-engine scanning, and a state machine that keeps untrusted files away from anything downstream until they’ve earned trust. Access control, storage permissions, credential scoping, and monitoring still belong to whoever deploys it, because those decisions depend on the system it’s dropped into.
We’re publishing the code because we spent real time looking for something like this before we built it, and we came up empty. The repository is at github.com/EurekAI-Org/bytewall, MIT licensed, so if a smaller team, a research lab, or a solo developer facing the same file-upload requirement finds this and skips even a week of the search we went through, that’s the point of putting it out there.
The part we keep coming back to is less about the code and more about the habit behind it: treat every file as guilty until the pipeline proves otherwise. Most upload vulnerabilities in the research we read while building this didn’t come from a missing scanner. They came from a system that trusted a file a little too early.
References
- Wichmann, P., Groddeck, A., & Federrath, H. (2022). FileUploadChecker: Detecting and Sanitizing Malicious File Uploads in Web Applications at the Request Level. ARES 2022. https://doi.org/10.1145/3538969.3538999
- Pooj, K., & Patil, S. (2016). Understanding File Upload Security for Web Applications. International Journal of Engineering Trends and Technology, 42(7).
- Neef, S., & Oudeh, M. (2024). Bringing UFUs Back into the Air With FUEL: A Framework for Evaluating the Effectiveness of Unrestricted File Upload Vulnerability Scanners. arXiv:2405.16619.
- Aslan, Ö., & Samet, R. (2020). A Comprehensive Review on Malware Detection Approaches. IEEE Access, 8. https://doi.org/10.1109/ACCESS.2019.2963724
- Wordfence. (2025). 2024 Annual WordPress Security Report.