Shipping Faster Than You Can Secure
// 2026-08-31 · Frederic Haddad · 8 min read
I built a working email client for myself in about a day. Real mail, real threading, real search, a UI I was quietly proud of. Then I paid for an independent audit and it came back with four critical vulnerabilities, one of which meant a stranger could attack me by sending me an email. Not by tricking me into clicking anything. Just by sending it.
That is the whole post, really. But the details matter, because the failure mode is not "I was careless." I have been doing this a long time. The failure mode is structural, and if you are a business owner whose team has just discovered that AI can build internal tools in an afternoon, it is coming for you next.
what happened — a day of building, four ways in
The application worked. That is the trap. It looked finished, it behaved correctly on every input I gave it, and every input I gave it was an input I wrote myself. "It works in the demo" is the single most expensive sentence in software, and AI-assisted development makes it arrive about ten times faster than it used to.
So I commissioned an audit. Deliberately independent — fresh eyes, a different reviewer, explicitly not the same agent that wrote the code reviewing its own homework. It found four critical issues, all genuinely exploitable:
- Message subjects and search text were rendered into the page title with no sanitisation. Script content in an incoming subject line executed. Attack vector: send me an email. That is it.
- No cross-site request forgery protection. Any malicious webpage I happened to visit while logged in could silently send mail as me.
- Tracking-pixel exposure — remote content loaded by default, quietly reporting when and where I read things.
- Style-overlay and disguised-link risks in rendered message bodies. An attacker could paint a fake interface over the real one, or make a link say one thing and go somewhere else.
Three further high-severity issues came out alongside them. All seven were fixed the same day, and I added seven security regression tests so those specific holes cannot quietly come back the next time someone refactors the render path. The fixing took hours. The finding was the hard part, and I could not have done the finding myself — not honestly, not on code I had just written and felt good about.
Here is the framing I wish I had held from the first commit: email bodies are adversarial input from strangers. Anything that ingests content from people who do not wish you well is a fundamentally different security category from an internal dashboard. Same code, same framework, same afternoon of work. Completely different risk class. I knew that. I had it in my head as an abstract fact and not as a design constraint, and abstract facts do not sanitise page titles. The same class of hole hiding in something that looked like a marketing task is in The Conversion Audit Security Hole.
the second one was dumber, and that is the point
Around the same period I left a service running with no authentication at all, bound to every network interface on the machine. In practice that is remote code execution for anyone on the same network. It stayed that way for over a day.
Nothing caught it. No alert, no scanner, no CI gate. I caught it because I got a vague itch about a config I had changed and went to look. That is not a security programme. That is luck wearing a hard hat. The systematic answer to that luck is Audit What Is Actually Listening.
The fix I care about is not "I closed the port." I closed the port in ninety seconds and that fix is worthless, because the next person to restart that service — including future me at eleven at night — has to remember the right flags. The durable fix was making the secure configuration the only way the service can start. No flag, no launch. Refuses to bind broadly, full stop. Security that depends on memory is not security; it is a scheduled outage with an unknown date.
One more, smaller and more humbling. I ran an automated scrub-and-abort script over a repository before publishing it — a script that hard-fails the commit if it finds anything sensitive. It caught internal addresses sitting in code comments. In a file a human review had already read and declared clean. That human was me. Ten minutes earlier.
the actual argument — velocity went up, review did not
Everyone is telling the productivity story about AI coding, and the story is true. I am living it. What almost nobody says out loud is that the productivity story is precisely why the security story got worse.
Feature velocity went up something like 10x. Security review stayed manual, stayed human-paced, stayed exactly as slow as it was in 2019. You have not made a system faster. You have widened one side of a gap and left the other side alone, and everything that falls into that gap is a vulnerability you shipped without ever seeing.
And there is a second, nastier problem. The same tool that wrote the code will happily review it and tell you it looks solid. It will be articulate about it. It will produce a confident, well-formatted assessment with headings. It is not lying — it genuinely does not see the hole, for the same reason I did not see it: the reviewer and the author share a mental model, and the vulnerability lives in the part of the model that is wrong. Self-review by the author is not an audit. That has always been true of humans. It is exactly as true of models, and the fluency of the output makes it much easier to forget.
the framework — five questions before you ship
Here is what I now run. It is not heavy. It costs a fraction of what building the thing cost.
1. Does this touch input from anyone I do not employ? Emails, uploaded files, webhooks, public forms, scraped pages, customer chat, PDFs from suppliers. If yes, you need an independent pass — not a careful re-read. If everything it eats comes from inside your own network, your risk is real but an order of magnitude lower, and you can be proportionate about it.
2. Who reviewed it, and did they write it? If the answer is "the same agent, in the same session," you have zero independent review, whatever the report says. Fresh session, different model, or a human who was not in the room. The separation is the entire value.
3. What did I actually ask for? Do not ask "is this secure." You will get yes. Ask for a named threat model against a named attacker: assume the attacker fully controls the content of every incoming message and can get me to visit a webpage they control; enumerate what they can do. Specific adversary, specific capabilities, specific question. That is what surfaced all four of mine.
4. Is the secure state structural or remembered? For every fix, ask whether it survives a restart by someone who has not read the incident. If the safe configuration is a flag, a habit, or a line in a runbook, it will fail. Make it the only state the system can be in.
5. Is there a regression test? A fix without a test is a fix with an expiry date. Seven holes, seven tests. That is not thoroughness, it is the minimum, and it is the difference between fixing a bug and fixing a class of bug.
why this bites harder here
The interesting thing happening across the Gulf right now is that small and mid-sized businesses have stopped waiting for software. A twelve-person logistics firm in Dubai builds its own customer portal. A family trading company writes its own quotation tool. A clinic builds an intake system over a weekend. Five years ago every one of those was a six-month procurement conversation with a vendor. Now it is a motivated ops manager and a subscription.
I think that is genuinely good. I help people do it. But those vendors, for all their faults and their invoices, had a security function — underpaid, ignored, but present. The SME building its own tools has inherited the software capability without inheriting the review capability, and the tools that get built first are exactly the risky ones: customer-facing forms, document intake, anything that reads email. Adversarial input, every time.
Add the regional specifics. Data-protection regimes across the UAE and Saudi have real teeth now and are still being interpreted, breach notification is not optional, and in markets this relationship-dense the reputational cost of leaking a client's documents runs well ahead of the regulatory fine. The building got democratised. The reviewing did not.
where I land
I now run an independent security pass as a standard, budgeted line item on anything I build that touches untrusted input — separate reviewer, explicit threat model, regression tests on every finding. Not because I am careful. Because I shipped four critical vulnerabilities into my own inbox and needed someone else to tell me.
If your team has started building its own tools and nobody has yet asked who reviews them, that is worth an hour of conversation before it is worth a project. I built the vulnerable thing myself, so I know exactly which question was missing. Send me the shape of what you are building and what it ingests — I will tell you honestly whether it is in the risky category or not, and if it isn't, I will say so. On a consulting day that hour becomes a full independent pass with a named threat model and regression tests on whatever we find. Book a consulting day or send me an inquiry first if you'd rather talk before booking.