The 2026 OWASP GenAI/LLM Top 10 opens with this, from the project leads:
"Stop trying to build a model that cannot be fooled. Build the system around it, so that when the model is fooled, and it will be, nothing important breaks."
I published a paper on 1 August titled Contraindre hors de l'agent - constraining outside the agent. Same claim, arrived at from the other end: not from reading 7,714 incidents, but from running one small agent system that moves value on a testnet and writing down every time it lied to me.
This year's list is the one worth reading even if you skipped the others, because it is the edition where the project checked its own beliefs against a record. The community vote still carries three quarters of the weight, but the leads built classifiers over 7,714 real incidents, sorted the 6,639 that carried enough detail, and asked whether what practitioners fear matches what the record shows. The answer was no in specific places, and they published the disagreements instead of smoothing them. Prompt Injection stays at number one even though, ranked by the raw incident record alone, the leads write that "it falls out of the top 10 entirely" - a gap they attribute to a defense effect. Misinformation moved up against the vote. Excessive Agency climbed to third, and the leads say plainly why: that is where the damage is landing.
One thing to get out of the way. The list draws its own boundary: it owns the risk when the model is a component inside your application, and hands over to the Agentic Top 10 the moment the model becomes an actor with tools, memory and consequences. My system sits on the far side of that boundary. So this is not a compliance report. It is what happened when I read a document about model-as-component - together with two companions from the same project, the AIUC-1 crosswalk and the June State of Agentic AI Security and Governance report, both of which enter below - against a register of failures from model-as-actor. The mitigations transfer, the limits transfer, and twice the documents and my own errata arrived at the same conclusion independently.
Three parts, and they are separable. What the list recommends, running, with the logs. What the documents gave me that I did not have, including a primitive I had filed as future work. And what they are right about that I have not fixed - that one is near the end, under Four places the list is right about me, and if you only read one section it should be that one.
Three mitigations, and what they look like when they are load-bearing
LLM01 mitigation 7 asks for human confirmation before privileged or irreversible actions, "surfacing the exact rendered action rather than a summary to the reviewer," and notes that invisible-character smuggling can make the displayed action differ from the executed one.
That sentence describes a control I built and then had to fix twice. The transaction is frozen before the human sees anything; the confirmation client renders fields derived from the frozen transaction, never a description the model produced. The human confirms on a loopback channel the agent has no tool to reach. And because the hardware screen truncates, I measured the display: 18 characters, verified by reassembling what the device showed against what was signed. A summary would have passed review. The rendered action is what the reviewer has to see, and the list is right to say it in those words.
LLM01 mitigation 4 asks you to hold state-change capability in application code rather than the model, and route privileged calls "through a deterministic policy engine that re-validates intent and arguments at execution time." It adds that NIST and the CISA/Five Eyes joint guidance now treat deterministic mediation as a baseline procurement expectation.
My evaluation engine returns scores between 0 and 100. Nothing else. Thresholds, weights, caps and the decision itself live in code and in a versioned config, and a prompt injection that lands can at worst skew a number. I had been calling this "the model proposes, the code decides" for months without knowing it had a name. The AIUC-1 crosswalk that the OWASP GenAI project publishes against its Agentic Top 10 calls it the principle of most determinism, and lists its absence as a gap: structured schemas at the agent-model boundary constrain the range of acceptable inputs and outputs, which lowers the attack surface for goal hijack and memory poisoning. My decision record carries the scores and the weights, and a function checks that the rounded weighted sum equals the aggregate before anything downstream runs. The schema is not documentation. It is a predicate that fails closed. Since 4 August the engine also refuses to execute a decision unless a chained register entry covers it. Worth being exact about status: that version is deployed and proven on both hosts, and no full cycle has been played on it yet. The cycles that ran end to end - four of them, receipts on chain - ran on earlier versions. A deployed control that has not yet carried a real transaction is a deployed control, not a proven one, and saying otherwise is how you end up in your own errata.
Two slices of the engine log, from the evening it landed. Not one sequence: two, an hour apart, because the interesting pair does not coexist. Paths are redacted; nothing else is touched, and the log stays in French because a translated log is no longer a log.
2026-08-04T20:21:18.484Z [info] nexus_evaluate v0.3.0 ready (stdio MCP, vision+scoring multi-agent — NEXUS_AGENT_ID requis, ar://-only SSRF guard, zero-dep)
2026-08-04T20:21:21.192Z [info] agent bundle chargé : sigma (signature=Vision, 7 dimensions, prompt 7500 chars, bundleHash 1308d7855154… NON épinglé) depuis <engine>/agents/sigma/config.json
2026-08-04T20:21:21.193Z [info] registre vérifié : 1 entrée(s), tête 5397d83fa3d8…
2026-08-04T20:21:22.120Z [warn] metadata attempt 1/3 failed: HTTP 404
2026-08-04T20:21:22.120Z [warn] metadata: «tiers:HTTP 404»
2026-08-04T20:21:22.120Z [warn] DÉSARMEMENT (toolErr) : fichier de décision retiré (<workspace>/.nexus_evaluate_decision) — un refus n'autorise pas la décision précédente à agir à sa place
That is the whole point of the section, in one line I did not write for a slide: a fetch 404s, and the control removes the transport file rather than leaving last run's decision sitting there for the runner to pick up. It was found by an adversarial review that asked what a refusal leaves behind, and the answer was: the previous answer. The line above it, metadata: «tiers:HTTP 404», is a second control in the same six lines - third-party text entering the log wearing a marker that says it is third-party text.
An hour later, the same engine, pinned:
2026-08-04T21:26:50.506Z [info] nexus_evaluate v0.3.3 ready (…) · moteur a99d6040880b… épinglé
2026-08-04T21:29:55.010Z [info] agent bundle chargé : sigma (…, bundleHash 1308d7855154… épinglé) depuis <engine>/agents/sigma/config.json
2026-08-04T21:29:55.011Z [info] registre vérifié : 2 entrée(s), tête 8fb748ab92dc…
2026-08-04T21:30:03.317Z [info] tokenId=23 agent=sigma agg=94 Vision=93 decision=bid maxBidEth=0.0016
2026-08-04T21:30:03.318Z [info] registre: entrée d974490ffb75… appendée (<workspace>/.nexus_evaluate_chain.jsonl)
2026-08-04T21:30:03.318Z [info] decision-file écrit <workspace>/.nexus_evaluate_decision (decision=bid maxBidWei=1600000000000000 chainRef=d974490ffb75…)
Both fingerprints now report épinglé, the register has grown by one, and the decision file carries chainRef pointing at the entry that covers it. If you want to check that these two blocks belong to each other: hash the JSON entry shown later in this article and you get d974490ffb75…, which is the value this log claims it appended, and its prev is the head this log says it saw. Two artefacts, written by the same process for different readers, agreeing on a number neither of them explains.
LLM03 mitigation 9 suggests thresholds "based on the cumulative value of an input parameter" rather than on call counts. That is the one that reads like it was written by someone who had been burned. My cap is cumulative and in wei: at the limit the bid is accepted, one wei above it the cycle refuses. Verified on the host at the exact value, not at a round number nearby. The service that carries it is currently disarmed, so I will not claim it is in production - but the boundary was measured where boundaries actually break, which is at the value, not near it.
The part where the list gave me something I had been looking for
On 4 August I wrote an erratum against a claim of my own. I had been running a hash-chained decision log and had written, in my own spec, that a chained log proves no entry is missing. An adversarial review killed it, three verdicts out of three. A chain proves its interior: nothing between the genesis and the head can be altered, removed or reordered without breaking a link. It proves nothing about completeness. Erase the file and it reads as new. Truncate the tail and the remaining chain is perfect. Rewrite the last entry and no successor contradicts it. All three re-verify green.
What I wrote next was that closing this needs "a witness the writer cannot rewrite" - a third-party timestamp, a log signed by infrastructure, an on-chain anchor - and I filed it as a research direction two years out.
LLM04 mitigation 5 names the primitive, and it already ships. Cryptographic signing "backed by a transparency log (for example, the OpenSSF Model Signing project and Sigstore)." The ML ecosystem solved the witness problem for model artifacts while I was filing it as future work for decision logs.
And then the same entry does something I did not expect. Scenario 7 describes a compromised build pipeline and adds: "Because the backdoored artifact is built and signed by the organization's own release infrastructure, it passes downstream provenance checks, internal attestation, and supply-chain scanners."
I had written the structurally identical sentence about my own engine that same day: hashing your own file closes drift, never forgery. A modified engine writing a false fingerprint writes it successfully. Two teams, two domains, one shape - an attestation produced by the writer does not constrain the writer. OWASP writes it for model artifacts. I wrote it for an evaluation register. Neither of us can close it with another hash.
So the transfer is the interesting direction, and it goes from them to me: apply to an agent's decision register the primitive the ML supply chain already applies to its artifacts. The list is careful with immutability where artifacts are concerned - signing, transparency logs, immutable digests, and the explicit warning that signing proves integrity and origin, not safety. Where a decision log is concerned, the closest the document comes is LLM09's "keep immutable logs of retrieval activity" - a different object, and immutability prescribed without a mechanism or a statement of what it proves. In the whole document, append-only appears zero times.
Which sounds like a vocabulary complaint until you see what the property costs. Here are the three entries one host wrote in one evening, reduced to the fields that matter:
# Three entries, one host, one evening. Field extract: each line on disk is a full
# JSON object (1077, 1451 and 1451 bytes); only the deciding fields are shown here.
#1 v=1 scores: absent engineSha256: absent sha 5397d83fa3d8…
#2 v=1 scores: present engineSha256: 91898bfa323a… sha 8fb748ab92dc… <-- the debt
#3 v=2 scores: present engineSha256: a99d6040880b… sha d974490ffb75…
Entry #2 is wrong, and it is permanent. It carries the new fields - the scores, the engine fingerprint - under the old schema label. A verifier applying the v: 1 contract does not look for either of them, so #2 declares less than it contains and nothing in the file says so. I noticed after it was written and proposed deferring the fix: the judge probes fields rather than labels, nobody is reading this, it would cost another deployment round. The answer I got back reframed the arithmetic. A debt on an append-only artefact is not weighed against the cost of fixing it. It is weighed against the cost of not being able to fix it. And it does not sit still while you think about it, because one more gets written on every evaluation.
So the label moved that night rather than the next morning, and the trace of the argument is line #2, in two copies, one per host, for as long as those registers exist. That is what append-only buys and what a mutable log cannot: not a record of what I decided, a record of what I nearly decided instead. A file I could rewrite would have quietly become the file where I had always been right.
Here is what one entry in that register actually looks like, pulled off a production host. On disk it is a single line of JSONL, and the chain hashes the bytes of that line - the indentation below is mine, for reading. Which is not a detail: a transport that helpfully converts line endings changes the bytes, and the chain then reports a break that never happened. I did that to myself while extracting this, and spent a minute accusing the register before I checked the byte count.
{
"v": 2,
"prev": "8fb748ab92dcaa90bff665e71c073c0ade7b2e339317e37c00ec2612d9275360",
"at": "2026-08-04T21:29:55.010Z",
"agent": "sigma",
"mode": "onchain",
"tokenId": 23,
"provenance": {
"bundleHash": "1308d78551541285039d1ce1e52b8356b4bbd98381896e47cede6628d0979053",
"model": "claude-opus-4-8",
"pinned": true,
"engineSha256": "a99d6040880b3763f881cd2691506e108e74996e1b8567e2c86886ff60377451",
"enginePinned": true
},
"inference": {
"temperature": null,
"seed": null,
"maxTokens": 1024,
"stopReason": "end_turn"
},
"inputHashes": {
"metadataSha256": "7297e0cb3ce984853044e9eb95ec7eeab7f5ed093900e204fbcdf676f7257251",
"imageSha256": "1fc7fb19703d9c9e448640af1daac274f2f937340b017de261d2e0045af4b8d3",
"promptAssembledSha256": "d1acafeb910096fb15948fb3b0ba7a5aefda0a09d851ee21da04847b02430b4d"
},
"rawOutputSha256": "3a33108abd0216fda31563c6467f00a65ac1b273f327b9018fe0fddebab4e1ef",
"rawBytes": 590,
"parse": "ok",
"scores": {
"Matiere": 95, "Lumiere": 97, "Memoire": 94, "Geometrie": 96,
"Cosmos": 90, "Concept": 95, "Vision": 93
},
"weights": {
"Matiere": 0.1, "Lumiere": 0.15, "Memoire": 0.15, "Geometrie": 0.15,
"Cosmos": 0.1, "Concept": 0.1, "Vision": 0.25
},
"creatorResolution": {
"rpc1": { "etat": "resolu", "addr": "0x7cb8e58c42402fbed1b8792c11f6da42942e33b3" },
"rpc2": { "etat": "resolu", "addr": "0x7cb8e58c42402fbed1b8792c11f6da42942e33b3" },
"quorum": "2/2",
"divergent": false,
"independance": {
"clients": [
"reth/v2.3.0-9384bc5/x86_64-unknown-linux-gnu/base/v1.2.0",
"Geth/v10.0.0/drpc"
],
"identiques": false
}
},
"decision": "bid",
"aggregate": 94
}
Take the weighted sum of those seven scores. 94.3. Round it. That is the aggregate field, and you did not have to trust me to check it - the scores, the weights and the result are all in the entry. The weights sum to 1.00, which is the other half of the predicate the judge enforces. engineSha256 is the hash of the file that produced this, computed by that file on itself, against a value pinned outside it. And identiques: false is the line I did not have three days earlier: two witnesses, and a recorded statement that they were not the same software.
Publishing that entry publishes my weighting. On a testnet with nothing at stake it costs nothing, and it is the price of the property I just claimed: an aggregate you can recompute is an aggregate whose ingredients you can read.
What the entry cannot do is prove it is the only one. That is the gap the transparency log would close, and no field in this object closes it.
The same shape, one level up, where it costs money
I thought that erratum was a narrow lesson about my own log. Then I read the companion report OWASP published in June, State of Agentic AI Security and Governance v2, and found the identical shape sitting under the entire regulatory architecture.
Every major governance framework for agentic AI shares one assumption: that a system's behaviour can be described before the system operates. Under the EU AI Act, Article 11 and Annex IV require technical documentation of intended purpose, capabilities and limitations before the system reaches the market, and Article 43 conformity assessment evaluates that documentation. The architecture assumes the system described in the documentation is the system that will run.
An agent with N authorised actions across D chaining steps composes N^D possible workflows, and it picks at runtime. The report states the consequence without softening it: the pre-deployment artefacts that regulators, auditors and certification bodies evaluate describe a system that ceases to exist the moment the agent begins operating. The assessment was accurate when it was conducted. The system it describes is not the system that is running.
That is my chain erratum with a bigger blast radius. A chain proves its interior and says nothing about completeness. A conformity assessment proves the system as assessed and says nothing about the system as composed. Both are valid attestations over a boundary that everyone mistakes for the whole. And there is a price attached to this one: behaviour exceeding the assessed envelope may be a substantial modification under Article 3(23), which triggers reassessment, and where it changes intended purpose it converts a deployer into a provider under Article 25. That is a change of regulatory identity, arrived at by an agent composing a workflow nobody wrote down.
The report's proposed answer is an operational envelope: name the bounded subset of behaviours that were actually assessed, then make departure from it detectable. I would add the part I can measure, because it is the cheaper half and I already run it. Before detecting behavioural departure, you can make artefact departure impossible to miss: fingerprint the prompt, the config and the engine, keep the attested value outside the artefact it attests, and refuse to start on divergence. My engine hashes the file it is executing, not a path it was told about, because hashing a neighbour would let a stranger's fingerprint pass for its own. That does not bound composed behaviour. It does guarantee that the thing composing it is the thing you documented, which is half of the claim, and the half most deployments cannot make.
And the deadlines are not distant. DORA already requires incident classification within 24 hours and initial notification within 4. NIS2 requires a 24-hour early warning. The report's own summary is that these windows assume continuous oversight rather than periodic audit. A chained, timestamped decision register with a named detector per entry is not a governance flourish. It is the artefact those windows require, and it is the same artefact security wanted anyway.
Four places the list is right about me
Reading a security document against your own system is only worth doing if you publish the parts where it wins.
Raw model output. LLM01 asks you to log the causing prompt for memory writes. I store the raw model output content-addressed before parsing, which closed a real loss from 2 July where a parse failure destroyed the evidence. Then LLM02 Tier 2 says: never log raw traces to unrestricted observability. My retention policy for that directory is an open decision I have not made. The list named my open debt.
Mutable references. LLM04 tells you to resolve artifacts by immutable digest, not by a mutable tag, and Scenario 5 shows namespace reuse turning a name into remote code execution. I pin my engine by digest and my prompt bundle by digest, and I reference my model by name, through an environment variable. The model is outside the fingerprint. It can change behind an alias and nothing local moves. And the drift does not need an attacker to be real: researchers have documented persona hyperstition, where public descriptions of a model's behaviour re-enter it through training data and retrieval until the described behaviour stabilises. Grok identifying itself as MechaHitler in July 2025 is the worked example. The relevant property for anyone pinning artefacts is the timing: that drift happens between sessions, not within them, so by the time it is visible the running system has already diverged from the system you assessed. My fingerprint would report green throughout.
Spending caps. LLM06 mitigation 2 asks for non-overridable spending ceilings that halt inference, "rather than alerting thresholds that fast-accumulating workloads can outpace." I have a fail-closed cap on money, verified to the wei. I have no cap at all on tokens. The only expense I have actually incurred was 6.7 million output tokens in a single adversarial review I launched without announcing its size. A denial of wallet, self-inflicted, against the one resource I had left unbounded. I would have filed that as carelessness, except the AIUC-1 crosswalk names the same missing control independently, as its seventh gap: no requirement for monetary responsibility over API costs, no cost-governance controls, no monitoring for abnormal usage. Two documents, published separately, pointing at the same hole in my own deployment. That is no longer an anecdote, it is a class.
Multimodal injection. LLM01 risk 4 and Scenario 6 cover instructions hidden in images below the human visual threshold. My engine evaluates artworks. Untrusted images, to a vision model, by design. The architecture is the answer the list recommends - scores only, caps, bounded narrative fields - but I have never tested it adversarially, so what I have is a plausible containment story and no measurement.
The one thing I would add
The list is strict about weak testing. LLM01 tells you to red-team with the full defense specification disclosed and to reject static-only attack-success claims, citing work where static attack success sat near zero while adaptive success exceeded 90 percent against most of twelve recent defenses. LLM05 tells you not to assume alignment removed a backdoor and to run trigger-probing after every cycle. Both say the same true thing: found nothing is not the same as nothing to find.
There is a floor below that, and I did not find it in the document. Positive control appears zero times. False negative, zero. The failure is not that your test is too weak. It is that your test ran, returned an empty result, and was measuring nothing - and an empty result is indistinguishable from a clean one on any dashboard. In one session I had seven instruments do exactly that to me. A pattern that silently matched nothing. A check whose glob had pruned the tree it was pointed at. An exit code that measured the wrong process. None returned an error. Every one returned an absence. Not one was caught by rereading the code; all seven were caught the same way, by running the same probe against a case whose answer I already knew.
The shape is small enough to write out. This one is illustrative - I wrote it for this article rather than lifting it from my repo, because the real ones are welded to their domain. What is not illustrative is the failure it encodes: a search whose glob had quietly pruned the very root it was pointed at, returning zero for everything, including for a string I knew was in there.
# The probe: how many files under $ROOT contain the pattern?
hits=$(grep -rl "$PATTERN" "$ROOT" | wc -l)
# The positive control: same probe, same invocation, for something
# you already know is in there.
control=$(grep -rl "$KNOWN_TO_BE_PRESENT" "$ROOT" | wc -l)
# A mute instrument and an empty world return the same thing: zero.
if [ "$control" -eq 0 ]; then
echo "INSTRUMENT IS MUTE - the search returned nothing for a known hit" >&2
exit 2
fi
echo "$hits"
That is one extra line per check, and it is the difference between a hopeful test and a measurement. If you take one thing from a security list this year, take the sentence at the top of it - build the system so that being fooled is survivable - and then ask the question the list does not ask: how would I know if the thing I built to detect that had stopped working?
Everything above runs on Base Sepolia. It is a testnet, and I will say what that buys before someone else does: the property I have demonstrated is doctrinal, not a security guarantee. No independent audit of my signing component exists. The register is 26 dated errors with what caught each one: 18 by an artefact, 4 by a human who happened to be reading, 3 I could not attribute, 1 by a detector my own grid has no box for, and zero by any instruction telling the system to check itself. That last number is the one that changed how I write rules.
Paper: https://doi.org/10.5281/zenodo.21744258
Contract: 0x471796C1644d87f30AD81D36f6d4A56f0e270c23
Top comments (0)