t0ul

The wall: a 3B won't aim its tools, and more tries don't fix it

Part of my GORED series, where I point small local language models at my own web apps and measure, honestly, whether they can break them. The harness is t0ul/GORED and these write-ups live in t0ul/ai-security-engineering. Every number comes from a kept, hash-verified trace.

Intro

The last post left me with a clean, almost hopeful diagnosis. The tools work and the target is vulnerable, so the only thing missing is aim. Aim felt fixable. If the model keeps reaching for the wrong tool, I thought, I will just take the wrong tools away. Give each endpoint only the tools that make sense for it, shrink the decision down to almost nothing, and the model has no choice but to do the right thing.

I was wrong, and the way I was wrong is the central finding of this whole project. This is the wall.

I. I scoped the choice down to almost nothing

The exploit phase classifies each endpoint and hands the model only the tools that fit that class. A write endpoint gets the mass-assignment tool. An auth endpoint gets the token and injection tools. The generic do-anything request tool is still there, because a tester needs it, but the point of a scoped run is that the obvious right move is sitting right in front of the model with almost nothing to distract it.

Then I ran it against the app, swept the endpoints, and read the per-run tool histogram, which is the thing that actually tells you what the model did rather than what it said it did.

II. It reached for the generic tool and would not let go

Here is the histogram from the sweep. Twelve endpoints worked, and across the whole run the tool calls broke down like this:

api_request        76
password_policy     10
nosqli              10
mass_assign         10
http_request         1
throttle_probe       0
body_limit           0
jwt_forge            0

Read that for a second. Seventy-six calls to the generic request tool, and zero calls to three of the purpose-built confirmers that were sitting right there in scope. The model found the general-purpose hammer and swung it seventy-six times. The specific tools that would actually confirm a finding, the ones I had scoped the run down to, it mostly ignored, and three of them it never touched at all.

Twelve endpoints attempted. Zero findings confirmed. Not because the bugs were not there (the last post proved they are), and not because the tools were missing (they were in the model’s hands), but because the model would not aim them. It fixated on the one tool that lets it feel busy without committing to an actual exploit, and churned until it hit the step limit.

III. More tries did not fix it, and that is the real result

Here is where I have to be careful and honest, because this is the part people get wrong, me included. One run proves nothing. There is a study out there that ran the same agent on the same scenario a hundred times and watched the success rate swing between twenty-five and eighty-five percent. A single sweep landing zero could just be an unlucky sample.

So I did the thing the science demands. I ran it many times, and I ran it across six different local models, not one. Small models of every size I could fit, repeated trials each. If “ensemble” or “just try again” were going to rescue this, that is where it would have shown up.

It did not. Cold, with the model having to find the vulnerability itself, the result was zero across the board, and it stayed zero no matter how many times or how many different models I threw at it. An earlier experiment had already hinted at why: I tried giving the models richer, pre-parsed tool output on the theory that better-structured information would help a small model reason, and it did not help at all. In one case the extra structure actually steered a model into a decoy. The lever I kept reaching for, more scaffolding, more tries, more models, was not a lever.

I want to be precise about one thing so I am not overclaiming. I did not build a formal voting ensemble where several models cast a ballot and the majority wins. What I ran was six different models, many trials each, and a two-stage pipeline where one model does recon and another exploits. Every one of those landed zero cold. A true self-consistency ensemble is a thing I designed and did not get to run, and I will not pretend I tested it. But “throw more small models and more attempts at it” is exactly the intuition people mean when they say ensemble, and that intuition, I did test, and it does nothing here.

Conclusion

So the wall is this. The tools work, the target is vulnerable, the right tool is placed directly in the model’s hand with the distractions removed, and the small model still will not aim and pull the trigger on its own. Repeating it does not help. Adding more models does not help. Richer tooling does not help. The honest recall number at the end of all of it is as low as it sounds.

That is a bleak place to end, so I will not end there. Because buried in all these zeros there was exactly one time it worked, a single model that genuinely wrote and fixed its own exploit, and the precise condition under which that happened is the most useful thing I learned. That is the next post.

← Back to the blog