DEV Community

Cover image for I Built a Security Tool That Proves Its Own Exploits — Then Got a Better Threat Model in the Comments

I Built a Security Tool That Proves Its Own Exploits — Then Got a Better Threat Model in the Comments

auto_majicly on July 29, 2026

Automated offense has one embarrassing failure mode: it lies to you about winning. Point a tool at a target, and the naive success check is a subs...
Collapse
 
fromzerotoship profile image
FromZeroToShip

"Three proven beats twenty-three claimed" is the line, and the nonce design is the right instinct even with the reflection gap the commenter found — because it moves the proof from what the target says to what only execution could produce. The reflection hole doesn't invalidate that; it just shows the boundary wasn't drawn tight enough. Freshness under a non-reflecting model is still strictly more than a banner scrape, and naming exactly which threat model it holds under is the part most tools skip.

The pattern underneath it is one I keep meeting from the defensive side: a check is only as trustworthy as the thing it forces to exist that couldn't exist otherwise. Your nonce works to the exact degree that echoing it requires execution — the moment reflection can satisfy it, it's proving presence instead of action, which is the same failure as a health endpoint that returns "db: ok" from a cached string. I seed my own scanner with known-bad code to prove it can detect, and the equivalent trap bit me: I was asserting a finding fired, not that the finding required the vulnerability to actually be present. A reflected nonce and a self-reported green are the same bug wearing different clothes — evidence that the checked thing didn't have to earn.

The comment catching it in an hour is the part I'd frame on the wall, honestly. That's an external threat model arriving faster and cheaper than any internal review would have, from someone with no stake in your tool being right — which is structurally the only kind of reviewer that reliably finds the assumption you built the whole thing on. Publishing the exploit tool got you a better exploit tool. Most people never get that trade because they never post the version that can still be wrong.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
xenocoregiger31 profile image
auto_majicly

You put words to the thing I was circling but hadn't named: a check is only as trustworthy as what it forces to exist that couldn't exist otherwise. That's the whole game. The reflected nonce is exactly your "db: ok from a cached string" — it proves presence, not action, and I hadn't drawn the line between those two until you said it out loud. Your known-bad-seeding trap is the same shape from the other side: "the finding fired" isn't "the vuln had to be there for it to fire." Same bug, different costume — evidence the checked thing never had to earn.

And the part that sticks with me: an outsider with no stake found the load-bearing assumption in an hour, cheaper than any review I'd run on myself. That's the trade for posting the version that can still be wrong — worth it every time. This comment's one I'll be chewing on for a while. Thank you.

Thread Thread
 
xenocoregiger31 profile image
auto_majicly

Ha — just read your profile: "not a developer, shipped 20+ tools with AI anyway." That's my exact story. Turns out you don't need the CS degree to spot the load-bearing assumption — you just have to actually ship the thing and let it be wrong in public. Following your logbook now — same reason you're following mine, I'd bet. 🤝

Thread Thread
 
fromzerotoship profile image
FromZeroToShip

The presence-vs-action line came out of reading your nonce, so it's half yours — I'd had the "db: ok from a cached string" failure for months without the vocabulary to see it was the same category as my seeded findings. Your version made it legible.

And yes, same story. The thing I'd add about our shared position is that the strength and the blind spot come from the same root, which took me a while to accept. Not knowing the conventions means I stop and ask at the points where a trained person would move past on "that's just how it's done" — which is genuinely why an outsider spots load-bearing assumptions. But it also means I can't tell when my own system is behaving strangely, because I have no baseline for normal. My scanner mis-scored six clean files for weeks and nothing about it looked wrong to me. A developer with pattern recognition might have squinted at it on day two. So we're better at other people's assumptions and worse at our own, which is an awkward combination unless you do exactly what you said.

Which is the part I'd underline for anyone reading this thread who hasn't tried it: shipping publicly in a state where it can still be wrong isn't bravery, it's the only substitute available to us for the senior across the table. Every real bug found in my systems this month arrived through a comment on something I'd just written about proudly — the bragging was the bait. Followed back, and looking forward to being corrected by you at some point. 🤝

Thread Thread
 
xenocoregiger31 profile image
auto_majicly

This might be my favorite thing anyone's said about the project — thank you. And the vocabulary went both directions: you naming the strength and the blind spot as the same root is the thing I'd been feeling without being able to say it. I stop and ask exactly where a trained person wouldn't, and I also can't feel when my own thing is off. Those really are one trait wearing two faces.

I have to give you my version of the six mis-scored files, because it's almost too on-the-nose. An early build of this tool once reported 23 of 23 ports breached when the true number was zero. It looked completely fine to me — I had no "normal" to compare it against, so I believed it for a while. Not to mention, I have no senior developer across my table. So theres no-one to ask questions as to why something is happening. That false 23/23 is the entire reason the check is now external and something the tool can't fake: I built the baseline I don't have into the machine, because I couldn't supply it myself. Maybe that's the general move for people like us — when you can't be the senior across the table, you build one that can't be argued with.

And "the bragging was the bait" is exactly right — I'd only sharpen it: the honesty is what arms the bait. Ship the polished version that hides its seams and nobody has anything to grab. Ship it still able to be wrong and you hand people the exact edge to pull. Followed back — genuinely looking forward to being wrong in your comments someday. 🤝

Collapse
 
codemaster_121482 profile image
Seif Ahmed

Hi @auto_majicly, the shift from literal echo to computation to defeat reflection attacks is brilliant. It reminds me of the isolation problems I faced when refactoring the old BlockSocial fork system for my project, Vlox (vlox.bonto.run).

To prevent data-pollution and reflection bugs where forking a fork would recursively hit the root and create major spam loops, I locked down the database business logic entirely—a fork simply cannot be forked anymore. I also stripped the old "friends" wall entirely so anyone can fork content to keep the network open, but mitigated the risk by restricting visibility solely to the forkerId and receiverId.

Architecturally, I simplified the backend overhead by dropping manual fetching loops. Instead, I just record the forkerId, receiverId, and rootId, utilizing a flat reference with .populate(). If either party decides they don't want the connection, they can easily wipe the fork instantly.

Your focus on strict layer isolation definitely highlights why keeping data structures flat and unidirectional is so vital. Great article!

Collapse
 
xenocoregiger31 profile image
auto_majicly

Vlox is exactly the kind of thing I want to dig into — bookmarking it for when I come up for air (honestly, I'm buried right now, but I will). What you did with the fork system is a neat mirror of the article: making a fork un-forkable so a fork-of-a-fork can't recurse back to root is the same instinct as killing reflection — you remove the path that lets the thing loop on itself instead of trying to catch it after the fact. And collapsing those fetching loops into a flat forkerId/receiverId/rootId + .populate() with an instant wipe is the "lazy me" test passing: if I had to open this cold, what's easiest to reason about? Flat and unidirectional wins every time.

The tension I keep sitting in: strict isolation and clean security boundaries aren't optional — but I catch myself asking "at what point is this too gated?" Same rule of thumb as yours, though: don't overcomplicate the simple stuff. Appreciate you following the journey and taking the time to write this all out.

Collapse
 
codemaster_121482 profile image
Seif Ahmed

Ah, this made my day! 🥹

I really appreciate the thoughtful feedback, especially on the security boundary tension. Hope you catch a break soon from being buried—looking forward to hearing your thoughts whenever you get a chance to check out Vlox! 😊

Thread Thread
 
xenocoregiger31 profile image
auto_majicly

Right back at you — this made my day. 😄 Feedback's easy to give when someone's clearly put real thought into their work. That security boundary tension is genuinely one of the hard parts, so the fact that you're already wrestling with it is a good sign. And no worries about the buried thing — I'll carve out time to actually dig into Vlox properly, not just skim it. Talk soon. 🤙 In-fact...Im going to pause and do it right now.

Thread Thread
 
xenocoregiger31 profile image
auto_majicly

I spent some time on Vlox rather than skimming it. What stands out is that it's a complete system, not a demo — accounts, a live feed, tagging, keyword search, markdown rendering, and actual people posting to it. Getting all of those working together is a real piece of engineering.

Two things I want to ask you about. First, the Limn engine — I saw it come up in the feed and I don't want to guess at what it is. What does it handle, and why did you build it as its own engine rather than pulling something off the shelf? Second, the markdown mode: letting users post formatted content is one of those features that's simple to start and genuinely hard to get right. How are you handling it under the hood?

Not asking to poke holes — I'm asking because these are the decisions that separate something you built from something you assembled, and you clearly built this.