The 2MB Firewall: When Strict Types Meet Wet Fish
I read a story in the Financial Times this week that struck a nerve. It wasn’t about a security breach or a crypto collapse. It was about fish rotting in Rotterdam because of a configuration default.
The EU launched a new digital import control system called CATCH. The goal was noble: stop illegal fishing by digitizing the paper trail. The result? A logistical deadlock where frozen fish is piling up at ports because the software physically cannot accept the reality of the trade.
It’s easy for us to sit back in our ergonomic chairs and mock the developers who built this. “How could they miss this?” we ask. But the truth is, if we aren’t careful, we are all one upload_max_filesize away from causing a supply chain crisis.
Here is a look at the reality gap and why modeling the real world is harder than it looks.
“Inexplicable Error Messages” (The 2MB Limit)
The headline failure is almost comical: Importers are trying to upload catch certificates, and the system is rejecting them with “inexplicable error messages” and “server errors”.
This specific phrasing, “inexplicable”, is the smoking gun. If the application logic caught the error, it would say “File too large.” When a user sees a generic “Server Error,” it usually means the request was killed by the infrastructure before it even reached the application code.
Let’s look at the physics of this. A catch certificate isn’t just one page. It’s a dossier containing the original cert, transshipment declarations, and bills of lading. Users report these documents can run up to 80 pages.
If you scan an A4 page at a readable 300 DPI in color (needed to verify stamps), you are looking at roughly 25MB for an uncompressed image. Even with compression, an 80-page dossier is heavy.
The system provided a 2MB pipe for a 30MB payload.
2MB is the default upload_max_filesize in PHP. It’s a common default in Nginx ingress controllers. The “inexplicable” error is likely the web server panicking or severing the connection because the Content-Length header exceeded the safety limit. Someone deployed the infrastructure with default configs, assuming they were building a form for text, not a repository for high-res historical artifacts.
The “Closed World” Fallacy
Beyond the crashes, the article notes another blocker: “Not all country zip codes are in the system… and not all fish species are in the system so we cannot enter them”.
You might think, “That’s just a data entry problem. The database is empty.”
No. It is a software architecture problem.
The developers treated the list of zip codes and fish species as a Closed Set (like an enum). They built Strict Validation: If input is not in ReferenceTable, then Block_Transaction.
This assumes the database is the absolute reality. But the real world is an Open Set. New industrial zones in Vietnam or processing parks in Ecuador have postal codes that didn’t exist when you bought your dataset. New commercial species enter the market.
By enforcing strict referential integrity on an external, chaotic world, the software prioritizes its own internal consistency over the reality of the trade. A resilient system would apply Postel’s Law: accept the unknown zip code, flag it with a warning (“Unknown Address”), and let the fish through. Instead, the system halts the global supply chain because a table row is missing.
The Integer vs. Float Problem
Perhaps the most dangerous disconnect is the requirement for skippers to “count fish caught in mixed batches”.
In our code, we often default to integers for quantities. ItemCount: int. It makes sense for e-commerce shopping carts. It does not make sense for a trawler in the North Atlantic hauling up 50 tons of mixed biomass.
Skippers estimate catch by volume or weight. They don’t count individual sardines. Asking a captain to enter an exact integer count forces them to fabricate data, taking a weight estimate and doing backward math to guess a number.
This is a Type Mismatch.
- The Model:
Quantity (Integer) - The Reality:
Weight (Float)with aMarginOfError.
By forcing the physical world to conform to a rigid digital type, the system isn’t increasing accuracy, it’s manufacturing “fraud.” If the skipper estimates 200'000 fish and the port inspector counts 195'000, the system flags a discrepancy.
In Defense of the Architects
I don’t want to come across as the “hindsight architect” who claims they would have done it perfectly. There are often very good reasons why systems end up this way.
1. Security and DoS Prevention: That 2MB limit? It’s the easiest way to prevent a Denial of Service attack where a malicious actor uploads terabytes of data to crash your storage. You can easily handle larger files by switching to established patterns like chunked uploads or async processing.
2. Data Consistency: Why strict Zip codes? Likely because downstream systems — customs routing, risk algorithms, tax calculations — rely on that data being perfect. If you allow free-text entry, you break the automation further down the pipe. The developers were likely protecting the integrity of the process, not just the database.
The Takeaway
The lesson here isn’t “raise your file limits.” It’s that when we build software for the physical world, we cannot model the “Happy Path”.
We have to model the “Rainy Path”. The path where the internet connection is spotty, the scanner is from 2005, the zip code is brand new, and the catch is a wet, chaotic pile of fish that cannot be counted as an integer.
We need to build “Escape Hatches” into our UIs. We need to accept that sometimes, the data will be dirty, and that’s okay, because sometimes keeping the real-world process moving is more important than a pristine schema.
Until then, I guess we’re eating frozen fish sticks.