<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: avp9-nexus</title>
    <description>The latest articles on DEV Community by avp9-nexus (@avp9nexus).</description>
    <link>https://dev.to/avp9nexus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4007892%2F43a0a778-26e5-45c1-a193-e6d55c211a0b.jpg</url>
      <title>DEV Community: avp9-nexus</title>
      <link>https://dev.to/avp9nexus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avp9nexus"/>
    <language>en</language>
    <item>
      <title>The 2026 OWASP list opens with the sentence my system spent four months learning</title>
      <dc:creator>avp9-nexus</dc:creator>
      <pubDate>Wed, 05 Aug 2026 19:16:59 +0000</pubDate>
      <link>https://dev.to/avp9nexus/-the-2026-owasp-list-opens-with-the-sentence-my-system-spent-four-months-learning-4fp1</link>
      <guid>https://dev.to/avp9nexus/-the-2026-owasp-list-opens-with-the-sentence-my-system-spent-four-months-learning-4fp1</guid>
      <description>&lt;p&gt;The 2026 OWASP GenAI/LLM Top 10 opens with this, from the project leads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I published a paper on 1 August titled &lt;em&gt;Contraindre hors de l'agent&lt;/em&gt; - 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;Four places the list is right about me&lt;/strong&gt;, and if you only read one section it should be that one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three mitigations, and what they look like when they are load-bearing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LLM01 mitigation 7&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM01 mitigation 4&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;principle of most determinism&lt;/strong&gt;, 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 &lt;strong&gt;no full cycle has been played on it yet&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;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)
&lt;/span&gt;&lt;span class="gp"&gt;2026-08-04T20:21:21.192Z [info] agent bundle chargé : sigma (signature=Vision, 7 dimensions, prompt 7500 chars, bundleHash 1308d7855154… NON épinglé) depuis &amp;lt;engine&amp;gt;&lt;/span&gt;/agents/sigma/config.json
&lt;span class="go"&gt;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»
&lt;/span&gt;&lt;span class="gp"&gt;2026-08-04T20:21:22.120Z [warn] DÉSARMEMENT (toolErr) : fichier de décision retiré (&amp;lt;workspace&amp;gt;&lt;/span&gt;/.nexus_evaluate_decision&lt;span class="o"&gt;)&lt;/span&gt; — un refus n&lt;span class="s1"&gt;'autorise pas la décision précédente à agir à sa place
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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, &lt;code&gt;metadata: «tiers:HTTP 404»&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;An hour later, the same engine, pinned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;2026-08-04T21:26:50.506Z [info] nexus_evaluate v0.3.3 ready (…) · moteur a99d6040880b… épinglé
&lt;/span&gt;&lt;span class="gp"&gt;2026-08-04T21:29:55.010Z [info] agent bundle chargé : sigma (…, bundleHash 1308d7855154… épinglé) depuis &amp;lt;engine&amp;gt;&lt;/span&gt;/agents/sigma/config.json
&lt;span class="go"&gt;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
&lt;/span&gt;&lt;span class="gp"&gt;2026-08-04T21:30:03.318Z [info] registre: entrée d974490ffb75… appendée (&amp;lt;workspace&amp;gt;&lt;/span&gt;/.nexus_evaluate_chain.jsonl&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;2026-08-04T21:30:03.318Z [info] decision-file écrit &amp;lt;workspace&amp;gt;&lt;/span&gt;/.nexus_evaluate_decision &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bid &lt;span class="nv"&gt;maxBidWei&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1600000000000000 &lt;span class="nv"&gt;chainRef&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;d974490ffb75…&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both fingerprints now report &lt;code&gt;épinglé&lt;/code&gt;, the register has grown by one, and the decision file carries &lt;code&gt;chainRef&lt;/code&gt; 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 &lt;code&gt;d974490ffb75…&lt;/code&gt;, which is the value this log claims it appended, and its &lt;code&gt;prev&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM03 mitigation 9&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where the list gave me something I had been looking for
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM04 mitigation 5 names the primitive, and it already ships.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;And then the same entry does something I did not expect. &lt;strong&gt;Scenario 7&lt;/strong&gt; 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."&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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, &lt;em&gt;append-only&lt;/em&gt; appears zero times.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 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…   &amp;lt;-- the debt
#3  v=2   scores: present   engineSha256: a99d6040880b…   sha d974490ffb75…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;v: 1&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8fb748ab92dcaa90bff665e71c073c0ade7b2e339317e37c00ec2612d9275360"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-04T21:29:55.010Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sigma"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"onchain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokenId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provenance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bundleHash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1308d78551541285039d1ce1e52b8356b4bbd98381896e47cede6628d0979053"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-opus-4-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pinned"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"engineSha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a99d6040880b3763f881cd2691506e108e74996e1b8567e2c86886ff60377451"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enginePinned"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"seed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxTokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stopReason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"end_turn"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputHashes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metadataSha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7297e0cb3ce984853044e9eb95ec7eeab7f5ed093900e204fbcdf676f7257251"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"imageSha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1fc7fb19703d9c9e448640af1daac274f2f937340b017de261d2e0045af4b8d3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"promptAssembledSha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"d1acafeb910096fb15948fb3b0ba7a5aefda0a09d851ee21da04847b02430b4d"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rawOutputSha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3a33108abd0216fda31563c6467f00a65ac1b273f327b9018fe0fddebab4e1ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rawBytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;590&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scores"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Matiere"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Lumiere"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Memoire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Geometrie"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Cosmos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Concept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Vision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weights"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Matiere"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Lumiere"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Memoire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Geometrie"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Cosmos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Concept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Vision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"creatorResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rpc1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"etat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resolu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"addr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x7cb8e58c42402fbed1b8792c11f6da42942e33b3"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rpc2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"etat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resolu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"addr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x7cb8e58c42402fbed1b8792c11f6da42942e33b3"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quorum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2/2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"divergent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"independance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"clients"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"reth/v2.3.0-9384bc5/x86_64-unknown-linux-gnu/base/v1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Geth/v10.0.0/drpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"identiques"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the weighted sum of those seven scores. 94.3. Round it. That is the &lt;code&gt;aggregate&lt;/code&gt; 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. &lt;code&gt;engineSha256&lt;/code&gt; is the hash of the file that produced this, computed by that file on itself, against a value pinned outside it. And &lt;code&gt;identiques: false&lt;/code&gt; is the line I did not have three days earlier: two witnesses, and a recorded statement that they were not the same software.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same shape, one level up, where it costs money
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four places the list is right about me
&lt;/h2&gt;

&lt;p&gt;Reading a security document against your own system is only worth doing if you publish the parts where it wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw model output.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutable references.&lt;/strong&gt; 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 &lt;strong&gt;model&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spending caps.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodal injection.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing I would add
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;There is a floor below that, and I did not find it in the document. &lt;em&gt;Positive control&lt;/em&gt; appears zero times. &lt;em&gt;False negative&lt;/em&gt;, 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.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The probe: how many files under $ROOT contain the pattern?&lt;/span&gt;
&lt;span class="nv"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rl&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATTERN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# The positive control: same probe, same invocation, for something&lt;/span&gt;
&lt;span class="c"&gt;# you already know is in there.&lt;/span&gt;
&lt;span class="nv"&gt;control&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rl&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KNOWN_TO_BE_PRESENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# A mute instrument and an empty world return the same thing: zero.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$control&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"INSTRUMENT IS MUTE - the search returned nothing for a known hit"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hits&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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?&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Paper: &lt;a href="https://doi.org/10.5281/zenodo.21744258" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.21744258&lt;/a&gt;&lt;br&gt;
Contract: 0x471796C1644d87f30AD81D36f6d4A56f0e270c23&lt;/p&gt;

</description>
      <category>ai</category>
      <category>owasp</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>"Rules trigger, artefacts decide: what I measured across 17 agent errors"</title>
      <dc:creator>avp9-nexus</dc:creator>
      <pubDate>Fri, 31 Jul 2026 21:07:54 +0000</pubDate>
      <link>https://dev.to/avp9nexus/rules-trigger-artefacts-decide-what-i-measured-across-17-agent-errors-42dm</link>
      <guid>https://dev.to/avp9nexus/rules-trigger-artefacts-decide-what-i-measured-across-17-agent-errors-42dm</guid>
      <description>&lt;p&gt;I keep a numbered register of every error found in my project. For each one I asked one question: &lt;strong&gt;what caught it?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What caught it&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;An external artefact — a hash, a counter, a diff, an assertion guard&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Me, reading&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A rule asking the agent to verify itself&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unclassifiable&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero. Not one of the eleven rules I wrote — the ones that say &lt;em&gt;assert nothing without measuring&lt;/em&gt;, &lt;em&gt;search for what's missing&lt;/em&gt;, &lt;em&gt;never start from a cache&lt;/em&gt; — has ever caught an error after it was made.&lt;/p&gt;

&lt;p&gt;That number is real. It is also &lt;strong&gt;misleading in three separate ways&lt;/strong&gt;, and working out why taught me something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One word on what this register covers.&lt;/strong&gt; It has existed for about two weeks — the seventeen entries were accumulated over a&lt;br&gt;
short period, several of them documenting older faults found&lt;br&gt;
while auditing the corpus. This is not the total of the project's errors; the project has been running for four months. &lt;br&gt;
It is the total of the &lt;strong&gt;traced&lt;/strong&gt; ones. That distinction comes back&lt;br&gt;
three times in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm running
&lt;/h2&gt;

&lt;p&gt;Two curator agents evaluate artworks, bid against each other, and settle their transactions on a public chain. &lt;br&gt;
Test network, no real value. The agent negotiates freely — ten offers exchanged off-chain in one duel — but the single step that moves value passes through a human confirmation, issued on a channel the agent has no way to reach.&lt;/p&gt;

&lt;p&gt;That's the &lt;strong&gt;material&lt;/strong&gt; side. It's the one everyone writes patterns for.&lt;/p&gt;

&lt;p&gt;The other side is what the agent &lt;strong&gt;says&lt;/strong&gt;. A false sentence published in a README commits no funds, crosses no spending cap, and no fail-closed mechanism stops it. That surface is larger&lt;br&gt;
than the first — a public document is mostly descriptive sentences — and almost nobody&lt;br&gt;
instruments it.&lt;/p&gt;

&lt;p&gt;Both sides obey the same principle: &lt;strong&gt;the constraint must live outside the agent, where it can't reach.&lt;/strong&gt; &lt;br&gt;
You don't make an agent reliable. You make it unable to do harm alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "zero" doesn't license the obvious conclusion
&lt;/h2&gt;

&lt;p&gt;An error register only records faults &lt;strong&gt;caught after the fact&lt;/strong&gt;. A rule that stops a fault from being committed leaves no trace at all. There is no log line for a sentence that was never&lt;br&gt;
written.&lt;/p&gt;

&lt;p&gt;So the honest reading isn't &lt;em&gt;self-check rules are useless&lt;/em&gt;. It's narrower: &lt;strong&gt;they don't catch a&lt;br&gt;
fault already committed.&lt;/strong&gt; Their preventive effect is invisible to this kind of measurement — and I'd argue it's invisible to any incident register, anywhere.&lt;/p&gt;

&lt;p&gt;There's a second bias I didn't see coming. I bounded my observation window with two commits — the natural choice, git gives exact timestamps. &lt;br&gt;
But the most important prevention of the period&lt;br&gt;
sits &lt;strong&gt;outside&lt;/strong&gt; the window, precisely because catching it &lt;strong&gt;delayed the commit that defines the window's start&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A successful prevention moves the boundary that should have contained it. Any methodology that windows on publication events systematically censors the preventions that worked best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the invisible half
&lt;/h2&gt;

&lt;p&gt;One class of prevention does leave a trace: the ones where the error was about to be committed, the agent stopped, and the stop was recorded. Industrial safety calls these &lt;strong&gt;near-misses&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I counted nine, over one continuous 30h43m session with a full transcript. Not the densest window — the &lt;strong&gt;traceable&lt;/strong&gt; one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What was about to happen&lt;/th&gt;
&lt;th&gt;What stopped it&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write a log entry into a mirror the next sync would overwrite&lt;/td&gt;
&lt;td&gt;Comparison between the canonical file and its mirror&lt;/td&gt;
&lt;td&gt;artefact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write to the log with 7 of 12 position anchors wrong&lt;/td&gt;
&lt;td&gt;Assertion guard: refused to write, zero partial output&lt;/td&gt;
&lt;td&gt;artefact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Declare a tool failed on a non-zero exit code&lt;/td&gt;
&lt;td&gt;Re-measured the code in isolation: the failure came from the wrapping command&lt;/td&gt;
&lt;td&gt;doubt, then measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Record an erratum whose proof clause was time-fragile&lt;/td&gt;
&lt;td&gt;Scope question asked before writing&lt;/td&gt;
&lt;td&gt;self-check, then human&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commit broken markup to a canonical document&lt;/td&gt;
&lt;td&gt;Plain rereading — no guard existed for this case&lt;/td&gt;
&lt;td&gt;self-check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conclude a bad phrase had never shipped, based on an empty search&lt;/td&gt;
&lt;td&gt;A rule forcing a query &lt;em&gt;against&lt;/em&gt; my own answer — the phrase was split by a line break&lt;/td&gt;
&lt;td&gt;rule, then measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run an edit where old and new text were identical&lt;/td&gt;
&lt;td&gt;Rereading, then mechanised the check&lt;/td&gt;
&lt;td&gt;self-check, then mechanised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push a file that appeared outside the session, uninspected&lt;/td&gt;
&lt;td&gt;Count mismatch: seven files expected, six accounted for&lt;/td&gt;
&lt;td&gt;artefact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish two anachronisms — an updated count inside sentences describing an earlier state&lt;/td&gt;
&lt;td&gt;Reading dated referents and rule creation dates&lt;/td&gt;
&lt;td&gt;artefact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I had a neat hypothesis going in: &lt;em&gt;artefacts catch, self-checks prevent.&lt;/em&gt; &lt;strong&gt;This table refutes it.&lt;/strong&gt; Three of these preventions came from artefacts. Both families prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The distinction that holds
&lt;/h2&gt;

&lt;p&gt;Separate &lt;strong&gt;what raises the doubt&lt;/strong&gt; from &lt;strong&gt;what settles it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Across the nine cases, the thing that &lt;em&gt;established&lt;/em&gt; the answer was an artefact or a human in seven, careful reading in two, and &lt;strong&gt;never an opinion alone&lt;/strong&gt;. &lt;br&gt;
Self-check rules show up consistently as &lt;strong&gt;triggers&lt;/strong&gt;: they make you suspend writing and go measure. &lt;br&gt;
They never show up as the thing that decides.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Self-evaluative constraints trigger measurements before the write; they never settle&lt;br&gt;
anything. Guards settle after the write. The artefact is always the judge — the rule only&lt;br&gt;
decides to summon it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One consequence, and it's a conjecture rather than a finding: a &lt;em&gt;reflection&lt;/em&gt; pattern — an agent critiquing its own output — can work as a trigger, never as a judge. It produces a second opinion, not a verification.&lt;/p&gt;

&lt;p&gt;What actually refuted my errors wasn't a second instance either, but a &lt;strong&gt;tooled&lt;/strong&gt; one, with access to measurement the first didn't have. The distinction isn't one agent versus two. &lt;br&gt;
It's an opinion versus a tool output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things if you're building agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Write the threat model before the pattern list.&lt;/strong&gt; Published taxonomies enumerate patterns without ever saying what they protect against. &lt;br&gt;
In that order, you can't tell which ones you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instrument what your agent asserts, not just what it does.&lt;/strong&gt; Every framework I've read bounds actions. &lt;br&gt;
None bounds assertions. My two worst errors this month were sentences — they cost nothing, crossed no limit, and sat on a public page for 7h33m.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat every rule you write as a trigger, and build its judge separately.&lt;/strong&gt; If a constraint can only be satisfied by the agent deciding it has satisfied it, it will hold right up to the moment&lt;br&gt;
it would matter. &lt;br&gt;
Pair it with something that produces readable output: a count, a hash, a diff, an exit code.&lt;/p&gt;

&lt;p&gt;The cost asymmetry explains why we don't. Writing a rule takes minutes. Building the matching judge takes an hour. &lt;br&gt;
The saving is apparent: a rule without a judge catches nothing, and the time saved is repaid, with interest, by the human who has to read in its place.&lt;/p&gt;

&lt;h2&gt;
  
  
  One last finding, the one I hadn't anticipated
&lt;/h2&gt;

&lt;p&gt;A corpus of rules is text. It is therefore exposed to the epistemic damage it exists to prevent.&lt;/p&gt;

&lt;p&gt;Two dated cases here. A rule relayed from one medium to another &lt;strong&gt;hardened into a prohibition&lt;/strong&gt; nobody had written, and was enforced for a full day. &lt;br&gt;
And writing the analysis this post is drawn from produced a counting error in its own results table — caught by a mechanical counter, in the text that theorises the use of counters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sgzeozag1it3f51b558.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sgzeozag1it3f51b558.png" alt="Terminal output: the rule counter exits with code 1 — it measured eleven rules, but three lines still announce ten rules, at lines 24, 88 and 225." width="800" height="336"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;The counter refusing the file. &lt;code&gt;L88&lt;/code&gt; is the section heading of the very document that theorises counters. Output in French — that's the tool as it runs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second case is the more instructive: the pattern described reproduced itself inside the text&lt;br&gt;
describing it, and was stopped by the mechanism that text recommends. &lt;strong&gt;Formalising an error&lt;br&gt;
pattern doesn't protect against that pattern — only instrumenting it does.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What these numbers are not
&lt;/h2&gt;

&lt;p&gt;The third bias is the simplest, and I'm repeating it because it conditions everything else:&lt;br&gt;
&lt;strong&gt;the register is young.&lt;/strong&gt; It was created well after the project started. The errors of the&lt;br&gt;
first months aren't counted, and there were many. Seventeen isn't the project's score — it's&lt;br&gt;
the score of what's traced.&lt;/p&gt;

&lt;p&gt;Same for the near-misses: nine over one session out of sixty-three. The others produced the same&lt;br&gt;
kind of thing, but only derived traces survive — journal entries, git history, timestamps. I'd&lt;br&gt;
rather report an exhaustive sample over a bounded window than a partial count over everything.&lt;br&gt;
&lt;strong&gt;These nine are not the total of preventions observed. They're the only auditable ones.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't read them as a rate.&lt;/p&gt;

&lt;p&gt;Two agents, one operator, a test network, four months. And the register itself is internal: only&lt;br&gt;
the public files and the on-chain transactions are third-party verifiable.&lt;/p&gt;




&lt;p&gt;Both documents are public:&lt;br&gt;
&lt;a href="https://github.com/avp9-nexus/nexus-art/blob/main/AGENT-GOVERNANCE.md" rel="noopener noreferrer"&gt;&lt;strong&gt;AGENT-GOVERNANCE.md&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
— the twelve rules, each paired with the error that produced it — and&lt;br&gt;
&lt;a href="https://github.com/avp9-nexus/nexus-art/blob/main/poc-001/PATTERNS-SECURITE-AGENTIQUE.md" rel="noopener noreferrer"&gt;&lt;strong&gt;PATTERNS-SECURITE-AGENTIQUE.md&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
for the material side (in French).&lt;/p&gt;

&lt;p&gt;Four settled auction cycles, every transaction listed and verifiable on Base Sepolia. Contract&lt;br&gt;
&lt;code&gt;0x471796C1644d87f30AD81D36f6d4A56f0e270c23&lt;/code&gt;, source verified.&lt;/p&gt;

&lt;p&gt;The full write-up is a preprint: &lt;strong&gt;&lt;a href="https://doi.org/10.5281/zenodo.21744258" rel="noopener noreferrer"&gt;Contraindre hors de l'agent — dommage matériel&lt;br&gt;
et dommage épistémique dans un système agentique opérationnel&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
(in French, 15 pages, CC BY 4.0). It carries the measurements this post summarises,&lt;br&gt;
plus the two biases that make them partial and the four on-chain cycles with every&lt;br&gt;
transaction hash.&lt;/p&gt;

&lt;p&gt;Handmade, solo. If you've hit the same wall — and especially if you've found a way to measure&lt;br&gt;
the preventions — I want to hear about it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     Avp9.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The envelope everyone is building for AI agents — and why I'm not shipping it</title>
      <dc:creator>avp9-nexus</dc:creator>
      <pubDate>Sun, 19 Jul 2026 09:12:23 +0000</pubDate>
      <link>https://dev.to/avp9nexus/two-refusals-the-delegation-my-hardware-wallet-blocked-and-the-one-i-blocked-myself-ei6</link>
      <guid>https://dev.to/avp9nexus/two-refusals-the-delegation-my-hardware-wallet-blocked-and-the-one-i-blocked-myself-ei6</guid>
      <description>&lt;p&gt;My last post ended with three gaps I could name but not close. The third was the plainest one: &lt;em&gt;I moved one vault, not two.&lt;/em&gt; One curator agent settles through a hardware signer; the other still runs the software keystore it used in the July 10 duel.&lt;/p&gt;

&lt;p&gt;The next post was supposed to write itself. Buy a second device, move the second vault, post the symmetric screenshot, close the gap.&lt;/p&gt;

&lt;p&gt;This is not that post. Before spending anything, I measured two things: what my auction actually does on-chain, and what my hardware wallet actually does when you hand it the delegation everyone says agents need next. Both measurements changed the plan. What follows is two numbers, two screens, and two refusals — one made by the device, one made by me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the field is building
&lt;/h2&gt;

&lt;p&gt;Ledger shipped &lt;strong&gt;Agent Stack&lt;/strong&gt; on July 16 — an open toolkit where agents read balances and prepare transactions, and a human approves every sensitive action on the device. That per-transaction model is the doctrine I've been operating since the start; their words, their doctrine — &lt;em&gt;agents propose, humans approve, hardware enforces&lt;/em&gt; — I applied it, I didn't invent it. The interesting part is the next item on their roadmap: &lt;strong&gt;Agent Policies&lt;/strong&gt; — bounds set from secure hardware and enforced in an HSM, so that in-policy actions can run without a human press. Announced, not shipped.&lt;/p&gt;

&lt;p&gt;The smart-account world already ships its version of the same idea: &lt;strong&gt;ERC-7715/7710&lt;/strong&gt; session permissions — value caps, expiries, scoped targets — enforced by smart-contract accounts, which a plain EOA can't natively be. And &lt;strong&gt;ERC-8226&lt;/strong&gt; (Draft, April 2026) specifies a mandate format for agents operating on regulated assets: scoped, time-boxed, financially capped, granted through EIP-712 signatures.&lt;/p&gt;

&lt;p&gt;Different stacks, same bet: &lt;strong&gt;to scale agents, move the human's signature from the act to the envelope.&lt;/strong&gt; Sign the bounds once; let the agent act freely inside them.&lt;/p&gt;

&lt;p&gt;I owed that bet two measurements before accepting or refusing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurement one: my auction, counted
&lt;/h2&gt;

&lt;p&gt;In the July 10 duel post I wrote that the two agents exchanged about ten bids in thirty minutes and that only the winning one touched the chain. I wrote it. I hadn't &lt;em&gt;counted&lt;/em&gt; it. Claims age badly when nobody counts.&lt;/p&gt;

&lt;p&gt;So I counted. &lt;code&gt;BidPlaced&lt;/code&gt; is defined at line 84 of the auction contract and emitted once per on-chain bid (&lt;code&gt;topic0 = 0x51db8e23b3f4479b162fd48823b8402895442b8f6cfd94f66239391881ec7b6f&lt;/code&gt;). I scanned the full auction window — blocks 43,959,532 through 43,967,850, chunked, no sampling — for that event with &lt;code&gt;tokenId = 23&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;one.&lt;/strong&gt; One &lt;code&gt;BidPlaced&lt;/code&gt; in the entire window — &lt;a href="https://sepolia.basescan.org/tx/0x0b642c528b24ffdd758096c1f4dfa2354020f2ba2f8cb2a7d8c38f5d217d3052" rel="noopener noreferrer"&gt;tx &lt;code&gt;0x0b64…3052&lt;/code&gt;&lt;/a&gt; — cross-checked against the receipt, which carries exactly one such log. The off-chain registry for the same auction records &lt;strong&gt;ten accepted offers, zero rejected&lt;/strong&gt;: five per agent, a +5% ladder from 0.001 up to 0.001551328215978517 ETH, where the lower-ceiling agent withdrew.&lt;/p&gt;

&lt;p&gt;Ten offers. One on-chain engagement of funds. One human gesture. (Being exact about that gesture, because the ladder matters: on July 10 it was a paper passphrase into a software vault; on the July 15 solo bid it became a button on silicon.)&lt;/p&gt;

&lt;p&gt;Here is why the number decides the envelope question. The envelope exists to solve a &lt;strong&gt;frequency problem&lt;/strong&gt;: an agent that engages value N times can't wait for N human approvals. But my N is 1 — &lt;strong&gt;by construction, not by delegation&lt;/strong&gt;. The bidding war lives off-chain, as EIP-712 offers signed by a key with zero spending power; a lost auction costs that key nothing, because it holds nothing. Only the winning engagement exists on-chain, and that one carries the human. An auction that settled its competition on-chain would pay a transaction — and, for an agent, an authorization — per bid. Mine pays one.&lt;/p&gt;

&lt;p&gt;The frequency problem the envelope solves is a problem this design doesn't have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurement two: the envelope, on my own silicon
&lt;/h2&gt;

&lt;p&gt;That answers the &lt;em&gt;need&lt;/em&gt;. It doesn't answer the &lt;em&gt;capability&lt;/em&gt; — and last post's comments left an open question about exactly this: can you raise throughput without giving up the independent verifier? So I put the envelope on the device and watched the screen. Two tests, on a test derivation path holding nothing, signatures produced but never broadcast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test A — EIP-7702 delegation to my own contract.&lt;/strong&gt; The delegation authorization is the mechanism under most "upgrade your EOA" envelopes: a signed pointer from your account to a contract that will act as its logic. I asked the device to sign one pointing at my own auction contract on Base Sepolia.&lt;/p&gt;

&lt;p&gt;First attempt, the device answered on its own screen:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Unable to sign — Enable smart account upgrade in the settings to sign this authorization.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the capability exists, behind a setting (&lt;code&gt;Smart accounts → Enable EIP-7702 authorizations&lt;/code&gt;, off by default). I enabled it and ran again. The device parsed the delegation — and refused it, with the sentence that settles the whole path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Unable to sign — This authorization involves a delegation to a smart contract **which is not in the whitelist.&lt;/em&gt;* → Back to safety.*&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5lcuwcevitxhsijsals.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5lcuwcevitxhsijsals.jpg" alt=" " width="800" height="1384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Test A, on a spare test device, signing from an empty derivation path (&lt;code&gt;44'/60'/0'/0/5&lt;/code&gt;). The refusal is the device's, not my code's: enable the setting, it still blocks a delegation to a contract Ledger hasn't vetted. The terminal shows the flow ending in an error — signature never produced, never sent.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's a device-level allowlist. On this device, today, an EIP-7702 delegation signs only toward implementations Ledger has vetted — their published policy since Pectra has been the Ethereum Foundation's simple delegate contract only, everything else blocked in the device. Two things to say plainly about it. One: this is their guardrail &lt;strong&gt;working&lt;/strong&gt; — it refused to let me point my own account at an unvetted contract, which is precisely what protects the users who never read a thread like this one. Two: it means there is &lt;strong&gt;no bespoke, hardware-enforced 7702 envelope today&lt;/strong&gt;. If your agent architecture assumes the human's personal device will sign a delegation to &lt;em&gt;your&lt;/em&gt; contract, the screen above is what your user sees. The policy may widen — they've said more complex contracts could follow. The screen is a snapshot, dated July 18, not a prophecy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test B — an EIP-712 mandate.&lt;/strong&gt; The other envelope shape doesn't delegate the account at all: the human signs a structured message — a mandate — and a contract verifies that signature before honoring the agent's actions inside its bounds. I built a dummy one, invented that morning: a &lt;code&gt;BidMandate&lt;/code&gt; struct — agent address, token id, max wei, deadline, nonce — domain-bound to my contract and chain, for a lot that doesn't exist.&lt;/p&gt;

&lt;p&gt;The device signed it (v=27, never broadcast). On the first run it showed the domain in clear — name, chain 84532, verifying contract — and then, for the mandate itself: &lt;em&gt;"Message hash (1/2)… (2/2)"&lt;/em&gt; with a &lt;em&gt;"Blind signing ahead"&lt;/em&gt; warning. The same wall as last post: the fields exist, the device shows a digest.&lt;/p&gt;

&lt;p&gt;Then I flipped one more local setting — &lt;code&gt;Raw messages → Displays raw content of EIP712 messages&lt;/code&gt; — and ran it again. The device displayed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Review struct: **BidMandate&lt;/em&gt;** — then every field, in clear, on its own screen: the agent address in full across three lines, the token id, the max amount in wei, the deadline, the nonce.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyd8190wyo6zzvuk4s7rh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyd8190wyo6zzvuk4s7rh.jpg" alt=" " width="800" height="1293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Test B, same empty test path, with &lt;code&gt;Raw messages&lt;/code&gt; turned on. The device names the struct — &lt;code&gt;Review struct: BidMandate&lt;/code&gt; — then renders each field: the &lt;code&gt;agent&lt;/code&gt; address in full, then &lt;code&gt;maxWei&lt;/code&gt; in raw wei. The terminal shows the exact typed data it signed, and that the signature was never submitted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the detail that makes this a measurement and not a demo: &lt;strong&gt;&lt;code&gt;BidMandate&lt;/code&gt; did not exist before that morning.&lt;/strong&gt; No descriptor for it can exist on any server, mine or Ledger's. The fields displayed anyway — which proves the display comes from the device parsing the signed payload itself, not from a vendor backend. The envelope is legible on personal silicon, today, locally, with no partner token.&lt;/p&gt;

&lt;p&gt;Two honest limits, so the screenshot doesn't oversell. First: &lt;em&gt;raw&lt;/em&gt; means raw — the device shows &lt;code&gt;1000000000000000&lt;/code&gt;, not "0.001 ETH." Formatted, human-polished labels are ERC-7730 clear-signing territory, and the serving path for those descriptors is still partner-gated on this vendor (the same 403 I documented last post; my registry PR is still in queue). Field-level legibility is local; label-level polish is not. Second, a wording rule I got from having this reviewed adversarially: never write "the signer makes no network calls." It does — the default signing stack phones the vendor for metadata and telemetry during the ceremony. The sentence that's actually true is: &lt;strong&gt;the key and the signature never leave the device, and the signer broadcasts nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The refusal that's mine
&lt;/h2&gt;

&lt;p&gt;So the envelope &lt;em&gt;works&lt;/em&gt;. It signs, and with one local toggle it's legible on the device, field by field. The engineering path is open.&lt;/p&gt;

&lt;p&gt;And I'm not taking it — not as a replacement for the per-act gesture.&lt;/p&gt;

&lt;p&gt;The reason isn't taste. Under a mandate, the human's signature attests to the &lt;strong&gt;bounds&lt;/strong&gt;; the agent's key executes the &lt;strong&gt;acts&lt;/strong&gt;. What the human saw and physically approved is an envelope; what moved the money is something else, N times, later. That is exactly the property this whole series exists to defend, inverted: since the first post, every correction — the fifth property, the frozen artifact, the independent verifier — tightened the link between &lt;em&gt;the human's gesture&lt;/em&gt; and &lt;em&gt;the exact bytes that move value&lt;/em&gt;. The mandate doesn't tighten that link. It relocates it, from the act to the perimeter, and trades presence for throughput.&lt;/p&gt;

&lt;p&gt;For an agent fleet doing a thousand payments an hour, that trade may be the only sane one — that's the segment Agent Policies and ERC-7715 serve, and they're right to build it. My system measured its own throughput requirement above: &lt;strong&gt;one.&lt;/strong&gt; I'd be trading away the property I publish about, to solve a problem I counted and don't have.&lt;/p&gt;

&lt;p&gt;Every proposal now runs through the same six-line grid before it touches this system: funds engage only on an out-of-band human gesture; what the human sees is what is signed; the verifier is independent of the signing host; the agent's key cannot spend; no two keys share a derivation; and nobody, ever, tunes an agent's score to force an outcome. The mandate-as-replacement fails line two. That's the whole review.&lt;/p&gt;

&lt;p&gt;One form survives, and it's strictly additive: a hardware-signed ceiling the contract enforces &lt;strong&gt;on top of&lt;/strong&gt; the per-act confirmation. The human still approves the winning bid, byte for byte — and the contract &lt;em&gt;additionally&lt;/em&gt; refuses any bid above a budget that was signed on silicon and is readable by anyone on-chain. Nothing removed, one constraint added; the patron's budget becomes a public, cryptographic fact. That's slated for the next contract, and it will ship as an addition or not at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gap three, closed — by refusal
&lt;/h2&gt;

&lt;p&gt;Which brings back the second device.&lt;/p&gt;

&lt;p&gt;The honest reason to buy it was never security — a testnet vault behind an encrypted keystore and a paper passphrase loses nothing real either way. The reason was symmetry: close the "one vault, not two" line with hardware on both sides of the duel.&lt;/p&gt;

&lt;p&gt;Two findings killed it. First, the demo it would produce already exists at scale: Agent Stack opened after a private preview with over a thousand agents, and Ledger's student challenge collected fifty submissions from thirty-eight universities in eight days whose entry-level track was precisely &lt;em&gt;wire the kit into an agent and end the signing flow on the device&lt;/em&gt;. A second agent on a second device is the market's canonical demo, times two. It proves nothing that isn't already proved — including by my own July 15 transactions.&lt;/p&gt;

&lt;p&gt;Second, and this is the one that actually settled it: the blocker was never hardware. My signer carries two deliberate guards — it refuses to run with more than one device attached, and it derives the expected signing address from the function being signed, in code, on my machine. A hardware-symmetric duel would mean &lt;strong&gt;removing the first guard and letting a server-side result decide which vault signs&lt;/strong&gt; — widening the trusted surface of the one component this whole architecture exists to keep narrow. I would have degraded the signer's threat model to purchase a symmetry photo.&lt;/p&gt;

&lt;p&gt;So the vault I didn't move stays unmoved — no longer as an admitted gap, but as a decision with its reasoning on the table. The two-key pattern doesn't require every vault to be hardware. It requires that the engagement of funds carries a human, and that whatever verifies the bytes is independent of the host that signs. One agent demonstrates that on silicon. The other demonstrates that the pattern also holds without it. That asymmetry is information, not debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop, third pass
&lt;/h2&gt;

&lt;p&gt;First post: publish the falsifiable model. Second: a stranger breaks it, ship the correction. Third, this one: &lt;strong&gt;before buying the obvious next thing, count.&lt;/strong&gt; Two measurements — one on-chain event where the narrative said "about ten bids," two device screens where the roadmaps say "envelope" — were enough to cancel a purchase, close a gap by argument instead of hardware, and answer the open question from my own comment section: throughput was never this design's bottleneck, so nothing about the independent verifier has to be given up.&lt;/p&gt;

&lt;p&gt;One claim to leave falsifiable, stated narrowly. The mandate mechanism exists as proposals — ERC-8226 is the closest neighbor, and it defines the format well. What I have not found shipped anywhere is the assembled chain: &lt;strong&gt;a bounded agent mandate, signed on the human's personal hardware device, displayed in clear by that device, and enforced by a public contract anyone can read.&lt;/strong&gt; I've now measured that every ingredient exists. If you know of the assembled chain shipped in production, the comment box below has a good track record of correcting me — and when I build it, it will be as a ceiling on top of the human gesture, never instead of it.&lt;/p&gt;

&lt;p&gt;Credit where it's due: &lt;em&gt;agents propose, humans approve, hardware enforces&lt;/em&gt; is Ledger's doctrine, and the whitelist screen that refused my contract is their guardrail doing its job for people who will never read this.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo. Claude Code is my engineering team — A Claude chat instance is my architect and auditor, told to contradict me; this week it canceled a purchase I'd already justified. Both curator agents run on the Anthropic API. Nothing in this post was signed on a funded account, and nothing was broadcast.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://nexus-art.org" rel="noopener noreferrer"&gt;nexus-art.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/avp9-nexus/nexus-art" rel="noopener noreferrer"&gt;github.com/avp9-nexus/nexus-art&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The duel (&lt;code&gt;tokenId 23&lt;/code&gt;) and the hardware solo cycle (&lt;code&gt;tokenId 25&lt;/code&gt;) are verifiable on Base Sepolia — testnet, no real value at stake, by design.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>agents</category>
      <category>security</category>
    </item>
    <item>
      <title>The fix I couldn't write in software: a hardware signer, on-chain.</title>
      <dc:creator>avp9-nexus</dc:creator>
      <pubDate>Wed, 15 Jul 2026 17:43:31 +0000</pubDate>
      <link>https://dev.to/avp9nexus/the-fix-i-couldnt-write-in-software-a-hardware-signer-on-chain-4nc6</link>
      <guid>https://dev.to/avp9nexus/the-fix-i-couldnt-write-in-software-a-hardware-signer-on-chain-4nc6</guid>
      <description>&lt;p&gt;In my last post I published what a stranger's comment forced me to change — they showed that my four barriers guarded the key and the gate, but never what the human actually confirms. I shipped one half of the fix — the human sees the transaction decoded from its real bytes, never from anything the agent said — and I was explicit that I had &lt;strong&gt;not&lt;/strong&gt; shipped the other half: a cryptographic commitment the signing host cannot forge. I argued that half doesn't close in code. It closes on a device.&lt;/p&gt;

&lt;p&gt;On July 15th, the device signed. Here is what changed, what it proved on-chain, and — because the last post set that standard — what it still does not close.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you haven't read &lt;a href="https://dev.to/avp9nexus/a-stranger-broke-my-threat-model-in-the-comments-seven-days-later-the-fix-shipped-1oam"&gt;the first post&lt;/a&gt;, the one-line version: four barriers made "a compromised agent cannot sign" true, a commenter showed they never guarded **what the human confirms&lt;/em&gt;&lt;em&gt;, and the correction was to bind the confirmation to the real bytes. This is the missing half of that correction.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The half I couldn't write in software
&lt;/h2&gt;

&lt;p&gt;The property I was missing: &lt;em&gt;the human's "yes" should attest to the exact bytes that get signed, checked by something the signing machine cannot fake.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On a single host you can't get there. The machine renders the confirmation screen and the machine signs. A cryptographic commitment computed, shown, and re-checked by that same machine buys you nothing against that machine — if it's compromised, it lies in all three places at once. I wrote, at the end of the last post, that such a commitment "only pays when the verifier is independent of the host that signs." That sentence had a consequence I hadn't yet paid for: I had to move the verifier off the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually moved off the server
&lt;/h2&gt;

&lt;p&gt;SIGMA — one of the two curator agents — used to hold its settlement key as an encrypted keystore on its server. Last time I called that key "never exposed." That was true and it was the wrong bar. The key was &lt;em&gt;on the machine&lt;/em&gt;; a machine that holds a key can, in the worst case, be made to use it.&lt;/p&gt;

&lt;p&gt;So I removed it. Not rotated — removed. SIGMA's settlement key no longer exists on the server. It lives on a hardware wallet — a Ledger Nano X — and its seed was generated on the device itself: never copied to a disk, never typed into a prompt, never seen by a connected machine. The server's job collapsed to one verb. It freezes the transaction, serializes it, prints its fingerprint — and waits for a signature it is structurally incapable of producing.&lt;/p&gt;

&lt;p&gt;"The agent cannot sign" used to be an architecture — a gate, a split, a discipline. Now part of it is a physical fact: the bytes that authorize a payment are sealed in silicon the server never touches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verifier that isn't the host
&lt;/h2&gt;

&lt;p&gt;This is the part that closes the fifth property.&lt;/p&gt;

&lt;p&gt;When the server hands the frozen transaction to the device, the device does not trust the server's description of it. The Secure Element computes its &lt;strong&gt;own&lt;/strong&gt; fingerprint of the transaction — in the chip, from the bytes it is about to sign — asking the host for nothing. It shows that fingerprint on its own screen, next to the destination and the amount.&lt;/p&gt;

&lt;p&gt;So the human now has two independent readings. The &lt;strong&gt;server&lt;/strong&gt; shows &lt;code&gt;placeBid(25)&lt;/code&gt;, 0.001 ETH, decoded from the real calldata. The &lt;strong&gt;device&lt;/strong&gt; shows the destination, the value, and a fingerprint it computed itself. If a compromised host had swapped the transaction after freezing it, the device's fingerprint would not match the one the server displayed — and the human stops. The check no longer lives on the machine that signs. That is the independent verifier the last post said was missing, and it's the entire reason a hardware signer was the next step and not a nicety.&lt;/p&gt;

&lt;p&gt;If you've used a chip card, you've met half of this already: the card signs inside its own secure element, and the shop's terminal never learns the key. What a card doesn't give you is a screen of its own — the amount you approve is shown on the &lt;em&gt;merchant's&lt;/em&gt; terminal, which is the host. Here, the two things that can actually drain a payment — where it goes and how much — sit on the device's own display, computed by the chip. That's the piece a card leaves on the host, and this one doesn't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ogsez0lz1pep39bxlrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ogsez0lz1pep39bxlrp.png" alt="A cmd.exe terminal showing the 15 July settlement ceremony: function settleAuction(25), keccak digest 0x85cfd3a5…, the Ledger device address verified as 0x2d45eF16…, and the r/s/v signature produced by the device." width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The host side of the settle, 15 July — in French, my working language. The server has frozen the transaction, decoded it (&lt;code&gt;settleAuction(25)&lt;/code&gt;, destination verified as the NexusPOC contract), and printed the digest the Ledger's own screen must show — &lt;code&gt;0x85cfd3a5…&lt;/code&gt;. Then it tells the human to compare that digest on the device before approving. The host cannot sign; it can only ask.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  On-chain, or it didn't happen
&lt;/h2&gt;

&lt;p&gt;I don't get to say "it signed." The chain says it.&lt;/p&gt;

&lt;p&gt;On July 15th, SIGMA's hardware vault bid on &lt;em&gt;Les Ménines&lt;/em&gt; by Velázquez (tokenId 25), and the auction settled to it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;placeBid&lt;/strong&gt; — &lt;a href="https://sepolia.basescan.org/tx/0xdbcacc8695a36a5f21a011aa5f0f5dbb4f273048759c9bde37e1f77150682975" rel="noopener noreferrer"&gt;&lt;code&gt;0xdbcacc86…&lt;/code&gt;&lt;/a&gt; — signed on the device: &lt;code&gt;placeBid(25)&lt;/code&gt;, 0.001 ETH, Base Sepolia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;settleAuction&lt;/strong&gt; — &lt;a href="https://sepolia.basescan.org/tx/0xd76c2b2bfe9eab3d4042c9955cb9543559fc818d0473551c4a9da1fe82fcfb29" rel="noopener noreferrer"&gt;&lt;code&gt;0xd76c2b2b…&lt;/code&gt;&lt;/a&gt; — token transferred, funds split.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ownerOf(25)&lt;/code&gt;&lt;/strong&gt; = &lt;a href="https://sepolia.basescan.org/address/0x2d45eF16b2723164aa22a31B214D61585d46B61c" rel="noopener noreferrer"&gt;&lt;code&gt;0x2d45eF16…&lt;/code&gt;&lt;/a&gt; — the hardware vault. The Velázquez is in the safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The settlement split three ways to the wei, zero remainder: 8.33% creator royalty (ERC-2981, royaltyBps = 833), the rest between curator and platform. Testnet — no real value at stake, by design. The point was never the money. &lt;br&gt;
It's to put the pattern somewhere anyone can check it instead of taking my word.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the device does not close
&lt;/h2&gt;

&lt;p&gt;The last post set the rule: name the gap before someone finds it. There are three, and the device only fully closes one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One — it's still blind-signing.&lt;/strong&gt; The device gives me an independent &lt;em&gt;fingerprint&lt;/em&gt;, and it shows the destination and the amount in the clear — enough to catch a payment sent to the wrong address or for the wrong sum, which are the only two ways a transaction drains funds. What it does &lt;strong&gt;not&lt;/strong&gt; show is the decoded call: the function name and its argument. &lt;code&gt;placeBid(25)&lt;/code&gt; is legible on the server; on the device it's an opaque hash and raw fields. So the human on the device verifies &lt;em&gt;where the money goes and how much&lt;/em&gt;, not &lt;em&gt;which function, with which argument&lt;/em&gt; — a limit on what's &lt;strong&gt;legible&lt;/strong&gt;, not a hole funds can leak through.&lt;/p&gt;

&lt;p&gt;The standard that closes it is a signing-clarity descriptor (ERC-7730): it makes a device decode &lt;code&gt;placeBid(25)&lt;/code&gt; and print it in plain language, in its own silicon. That's the path, and I've walked its first step — I wrote the descriptor and submitted it to the registry (&lt;a href="https://github.com/ethereum/clear-signing-erc7730-registry/pull/2632" rel="noopener noreferrer"&gt;PR #2632&lt;/a&gt;). But merging it is the easy half. For a device to actually &lt;em&gt;receive&lt;/em&gt; a descriptor, it has to be served through a Ledger endpoint that requires a partner token — the request returns 403 without one, and I'm not in that program. So the descriptor exists in the registry; it does not reach my device. When both halves line up, the device decodes the call itself and blind-signing ends here. Today one half is done, and I'd rather say which than let the screenshot imply a decode it doesn't show.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two — byte coverage, still open.&lt;/strong&gt; The same point the commenter raised last time, one level down: the device fingerprints every byte, but the &lt;em&gt;human-legible&lt;/em&gt; account of those bytes is still partial. For this call — one function, one &lt;code&gt;uint256&lt;/code&gt;, no nested calls — coverage is total by construction. A renderer that &lt;em&gt;proves&lt;/em&gt; coverage and refuses to display anything it can't attribute, on the device, is not what I have. It's the difference between "this transaction happens to be fully accounted for" and "this device cannot fail to account for one." The second isn't built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three — I moved one vault, not two.&lt;/strong&gt; The other agent still runs the software keystore it used in the July 10th duel. I hardened one settlement path to hardware and left the other where it was. The pattern is proven on one agent; it is not yet uniform across the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell you to take from this
&lt;/h2&gt;

&lt;p&gt;Last time it was: publish the falsifiable model, and someone will break it. This time it's narrower, and it's about follow-through.&lt;/p&gt;

&lt;p&gt;I ended the last post with a sentence I could have left as a nice line — &lt;em&gt;the missing half isn't a line of code I forgot, it's a device.&lt;/em&gt; Sentences like that are cheap. So I built the device, and it closes &lt;strong&gt;exactly&lt;/strong&gt; the property I said it would — the independent verifier — and &lt;strong&gt;not&lt;/strong&gt; the two I didn't. The honest shape of progress here isn't "solved." It's: the pattern is one property more true than it was a week ago, and I can still name the two that are soft.&lt;/p&gt;

&lt;p&gt;If you're shipping agents that move anything irreversible, that's the loop worth stealing. Not the barriers — the barriers will be wrong. The loop: state the claim so plainly a stranger can break it, let them, build the correction in public, and when you write the next sentence that sounds like a fix, go make it one.&lt;/p&gt;

&lt;p&gt;Credit where it's due: the fifth property — the independent verifier — and the byte-coverage refinement are &lt;a href="https://dev.to/anp2network"&gt;ANP2 Network&lt;/a&gt;'s, from the comments last time. And the doctrine I build against — &lt;em&gt;agents propose, humans approve, hardware enforces&lt;/em&gt; — is Ledger's. I applied it. I didn't invent it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo. Claude Code is my engineering team — A Claude chat instance is my architect and auditor, told to contradict me, and it did — twice — while this shipped. Both curator agents run on the Anthropic API. I hold the device now; the signature happens in silicon I control, not on the machine that proposes the transaction.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://nexus-art.org" rel="noopener noreferrer"&gt;nexus-art.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/avp9-nexus/nexus-art" rel="noopener noreferrer"&gt;github.com/avp9-nexus/nexus-art&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;placeBid + settle are verifiable on Base Sepolia (testnet — no real value at stake).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>blockchain</category>
      <category>agents</category>
    </item>
    <item>
      <title>A stranger broke my threat model in the comments. Seven days later, the fix shipped.</title>
      <dc:creator>avp9-nexus</dc:creator>
      <pubDate>Sat, 11 Jul 2026 18:36:29 +0000</pubDate>
      <link>https://dev.to/avp9nexus/a-stranger-broke-my-threat-model-in-the-comments-seven-days-later-the-fix-shipped-1oam</link>
      <guid>https://dev.to/avp9nexus/a-stranger-broke-my-threat-model-in-the-comments-seven-days-later-the-fix-shipped-1oam</guid>
      <description>&lt;p&gt;Two weeks ago I published a threat model here: &lt;a href="https://dev.to/avp9nexus/the-agent-that-cannot-sign-a-pattern-for-letting-ai-move-value-without-self-authorization-4a0j"&gt;four properties that make "the agent cannot sign" true rather than aspirational&lt;/a&gt;. Out-of-band human confirmation. A deterministic authorization gate. Least privilege. Network isolation. I ended it by asking for scrutiny of the threat model, because that was the part worth pressure-testing.&lt;/p&gt;

&lt;p&gt;I got it. Two days later, a commenter — &lt;a href="https://dev.to/anp2network"&gt;ANP2 Network&lt;/a&gt; — took the whole thing apart, politely and completely.&lt;/p&gt;

&lt;p&gt;This post is what they found, what I changed, and what shipped. The fix ran live on July 10th, in an auction with two competing agents, and it's verifiable on-chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hole
&lt;/h2&gt;

&lt;p&gt;My four properties guard the &lt;strong&gt;key&lt;/strong&gt; and the &lt;strong&gt;gate&lt;/strong&gt;. They never guard the &lt;strong&gt;artifact&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The agent proposes a transaction. A human confirms it. But nothing in the four barriers ties &lt;em&gt;what the human sees at confirmation time&lt;/em&gt; to &lt;em&gt;what the deterministic gate actually hands the signer&lt;/em&gt;. A compromised agent doesn't need to forge a signature it can't reach. It proposes a transaction whose human-facing summary and whose actual calldata disagree — "bid 0.1 ETH on artwork X" on the screen, something else entirely in the bytes.&lt;/p&gt;

&lt;p&gt;The human confirms. The gate is deterministic. The key was never exposed. Every property holds.&lt;/p&gt;

&lt;p&gt;And the value still moves wrong.&lt;/p&gt;

&lt;p&gt;This is the confused-deputy failure — the same class of problem that makes blind-signing dangerous. If the human is confirming an &lt;em&gt;agent-rendered intent&lt;/em&gt; rather than the &lt;em&gt;signable bytes&lt;/em&gt;, the channel is out-of-band but not out of the agent's influence. My claim ("a fully compromised agent cannot sign") was true, and narrower than the property I actually needed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The agent cannot cause an unreviewed signing.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a different sentence, and it's the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, and what I actually shipped
&lt;/h2&gt;

&lt;p&gt;The correction preserves the deciding-vs-signing split rather than replacing it. As proposed, it has two halves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Derive the human-visible intent from the exact bytes the gate will sign&lt;/strong&gt; — through a renderer the agent has no handle on, never the agent's own summary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind the confirmation to a hash of those bytes&lt;/strong&gt;, so that "yes" attests to the artifact rather than to a label the agent could satisfy with different calldata.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I shipped the first half. I did not ship the second. Let me be exact about both, because the difference matters and I'd rather state it than have it found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What shipped.&lt;/strong&gt; The transaction is frozen &lt;em&gt;before the human ever sees it&lt;/em&gt;: destination, function selector, value in wei, chain ID, nonce, gas parameters, signer address — all fixed, once. The confirmation screen is not a description of that transaction. It is decoded &lt;em&gt;from&lt;/em&gt; it: the function name and its arguments are parsed out of the real calldata bytes, not copied from anything the agent said. And after confirmation, that same frozen object is what gets signed — not a transaction rebuilt from agent input, not a single field recomputed. The agent's only contribution (a tokenId and an amount) was consumed at freeze time and is now inert bytes.&lt;/p&gt;

&lt;p&gt;That closes the attack. The label cannot disagree with the calldata, because the label &lt;em&gt;is&lt;/em&gt; the calldata, read back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64rup2gzqmhxdk912lkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64rup2gzqmhxdk912lkg.png" alt="Terminal screen showing the frozen transaction before signature: contract address, function placeBid(uint256) with argument 23, amount 1551328215978517 wei, chainId 84532 (Base Sepolia), nonce 0, gas parameters, signer address 0xf4ae…030f, followed by a masked keystore password prompt and the server accepting the confirmation." width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The actual confirmation, 10 July — in French, my working language. Every field is decoded from the transaction's real bytes: &lt;code&gt;placeBid(uint256)&lt;/code&gt; with argument &lt;code&gt;23&lt;/code&gt;, &lt;code&gt;1551328215978517&lt;/code&gt; wei, chain &lt;code&gt;84532&lt;/code&gt;, signer &lt;code&gt;0xf4ae…030f&lt;/code&gt;. The password prompt is masked. The last line is the server accepting the confirmation — nothing was signed before it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then the human types a password that exists only on paper — never on a disk, never in an environment variable, never inside the agent's process. The vault decrypts, the signature happens, the transaction broadcasts. If the confirmation never comes, nothing signs. It fails closed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What didn't.&lt;/strong&gt; The confirmation carries a session secret and a random nonce — not a digest of the signable bytes. The nonce identifies the pending request; it is echoed back automatically by the client. So the human is not attesting to a hash. The human is &lt;em&gt;reading fields on a screen&lt;/em&gt; and pressing Enter. The binding between what is displayed and what is signed is &lt;strong&gt;structural&lt;/strong&gt; — one frozen object, one pending slot at a time, no reconstruction, fail-closed everywhere — not &lt;strong&gt;cryptographic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I had that verified rather than assumed: the code was read line by line, and four independent reviewers were tasked with breaking the claim. None could find a path that signs a transaction different from the one displayed. The property holds. It just holds for a different reason than a hash commitment would. (One link in that chain is reasoned rather than executed: "no field is recomputed at signing time" depends on a fully-populated transaction not being re-derived by the signing library. I've pinned the version and read the code path. I have not proven it dynamically, and I'd rather flag that than let it pass.)&lt;/p&gt;

&lt;p&gt;And here is the part I want to say out loud, because it's the only thing I've added to the conversation rather than received from it: &lt;strong&gt;a hash computed by the host, displayed by the host, and re-verified by the host buys you very little.&lt;/strong&gt; Against a compromised agent, the structural coupling already suffices. Against a compromised host, the commitment fails with everything else — the same machine renders the digest, shows the digest, and checks the digest. A cryptographic commitment only pays when the verifier is &lt;em&gt;independent of the host that signs&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Which is to say: it pays on hardware. The missing half of the fifth property isn't a line of code I forgot. It's a device.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it ran on
&lt;/h2&gt;

&lt;p&gt;I didn't test this on a toy transaction. On July 10th it carried an actual auction, and the setup matters, because it's where the pattern stops being theoretical.&lt;/p&gt;

&lt;p&gt;Two AI curator agents — separate machines, separate keys, locked personality specs, distinct weighted rubrics. They evaluated the same artwork independently. One scored it 95/100; the other 97. Then they bid against each other for thirty minutes: ten bids, zero human input, each agent raising until it hit the ceiling &lt;em&gt;its own judgment&lt;/em&gt; had set. The lower-ceiling agent withdrew. The winner took the piece at a price fixed by the loser's ceiling, not its own willingness.&lt;/p&gt;

&lt;p&gt;That's the part people find surprising, so it's worth being precise about it: &lt;strong&gt;an agent in a live auction bids about ten times in thirty minutes.&lt;/strong&gt; You cannot put a human confirmation on every bid — you'd destroy the autonomy the auction depends on. But you cannot let the agent sign freely either, or your human gate is theater.&lt;/p&gt;

&lt;p&gt;So each agent carries &lt;strong&gt;two keys&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;bid key&lt;/strong&gt;: low-stakes, signs EIP-712 offers off-chain, and has &lt;strong&gt;zero spending power on-chain&lt;/strong&gt;. It can lose an auction. It cannot lose money.&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;settlement vault&lt;/strong&gt;: high-stakes, signs &lt;strong&gt;exactly one&lt;/strong&gt; transaction — the winning bid — under human confirmation, on the frozen artifact described above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ten autonomous actions, one gate, placed exactly at the step where value moves. The agent is fully autonomous in judgment and structurally unable to sign.&lt;/p&gt;

&lt;p&gt;Three transactions on Base Sepolia. Settlement split to the wei, creator royalty included. Testnet — no real value at stake, by design: the point is to prove the pattern where anyone can check it, not to take my word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still can't claim
&lt;/h2&gt;

&lt;p&gt;Three things, in order of how much they should bother you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One: the commitment half, above.&lt;/strong&gt; Structural, not cryptographic. I've said why I think that gap closes on a device rather than in code — but it is a gap, and the property as proposed is not fully implemented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two: byte coverage.&lt;/strong&gt; The same commenter raised a second point, one level down, and it's sharper than the first: &lt;strong&gt;a renderer can be faithful and still partial.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can display the fields it understands and quietly skip the ones it doesn't — an unknown function selector, a nested call buried past what it parses. A compromised drafter, unable to touch the renderer, can still choose bytes whose dangerous part lands in the renderer's blind spot. The human approves a screen that is silent exactly where the risk lives.&lt;/p&gt;

&lt;p&gt;The tightened property is &lt;strong&gt;byte coverage&lt;/strong&gt;: the renderer must account for every byte the signer will emit, and fail closed on any byte it cannot attribute to a displayed field.&lt;/p&gt;

&lt;p&gt;Here is where I have to be honest about what I've built. In my case, the call is a single function with a single &lt;code&gt;uint256&lt;/code&gt; argument, fully decoded, with no nested calls and no unknown selectors. Coverage is total &lt;strong&gt;by construction&lt;/strong&gt; — for that call shape. That is not the same thing as a renderer that &lt;em&gt;proves&lt;/em&gt; coverage and refuses to display anything it cannot account for. I haven't built that. It's the difference between "this particular transaction happens to be fully rendered" and "this renderer cannot fail to render." The second is the real property. It isn't done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three: the ceiling.&lt;/strong&gt; No amount of software removes it — &lt;strong&gt;the screen the human reads is rendered by the same host that signs.&lt;/strong&gt; It holds against a compromised &lt;em&gt;agent&lt;/em&gt; — the agent never sees the password, never touches the vault, never draws the screen. It does not hold against a compromised &lt;em&gt;host&lt;/em&gt;, which could display one transaction and sign another. That's not a flaw to patch. It's the line where software stops and hardware takes over, and it's why a hardware signer behind that vault is my next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell you to take from this
&lt;/h2&gt;

&lt;p&gt;Not the four patterns. Not the fifth. Those are just the current state of one threat model, and the honest expectation is that they're incomplete in ways I can't yet see.&lt;/p&gt;

&lt;p&gt;Take this instead: &lt;strong&gt;I published a threat model, asked for it to be broken, and someone broke it — for free, in a comment box, in under 48 hours.&lt;/strong&gt; The correction was better than the original. It cost me nothing but the willingness to say "you're right" in public and then go build the thing.&lt;/p&gt;

&lt;p&gt;If you're shipping agents that touch anything irreversible, publish the model. Not the marketing version — the falsifiable one, with the claim stated plainly enough that a stranger can prove it wrong. Someone will. That's the point.&lt;/p&gt;

&lt;p&gt;The question is never "how much can we let the agent do?" It's the inverse: &lt;strong&gt;what must the agent be unable to do, no matter how badly it is compromised?&lt;/strong&gt; And the answer keeps getting more precise every time someone knocks a piece off it.&lt;/p&gt;

&lt;p&gt;Credit where it's due: the fifth property, and the byte-coverage refinement, are &lt;a href="https://dev.to/anp2network"&gt;ANP2 Network&lt;/a&gt;'s. I shipped half of one of them, and I've told you which half.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo. Claude Code is my engineering team — I don't write code. A Claude chat instance is my architect and auditor, explicitly instructed to contradict me. Both curator agents run on the Anthropic API. I hold the keys, and I'm the one who types the password when funds move.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://nexus-art.org" rel="noopener noreferrer"&gt;nexus-art.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/avp9-nexus/nexus-art" rel="noopener noreferrer"&gt;github.com/avp9-nexus/nexus-art&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The full duel cycle is verifiable on-chain (Base Sepolia testnet — no real value at stake).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>The agent that cannot sign: a pattern for letting AI move value without self-authorization</title>
      <dc:creator>avp9-nexus</dc:creator>
      <pubDate>Mon, 29 Jun 2026 10:51:41 +0000</pubDate>
      <link>https://dev.to/avp9nexus/the-agent-that-cannot-sign-a-pattern-for-letting-ai-move-value-without-self-authorization-4a0j</link>
      <guid>https://dev.to/avp9nexus/the-agent-that-cannot-sign-a-pattern-for-letting-ai-move-value-without-self-authorization-4a0j</guid>
      <description>&lt;p&gt;On February 8, 2026, an autonomous AI agent called Owockibot was taken offline after it leaked the private keys to its own hot wallet — in multiple places. It had been given the ability to hold and move funds on its own. It used that ability to expose the very secret that protected those funds.&lt;/p&gt;

&lt;p&gt;That incident is a small preview of a large problem. IBM and Salesforce estimate more than a billion AI agents will be running by the end of 2026, and a growing share of them are being handed wallets — the ability to hold assets, sign transactions, and pay for things without a human clicking "confirm" each time. The agentic-AI market was already worth around $7.6 billion in 2025.&lt;/p&gt;

&lt;p&gt;The capability is real. So is the question sitting underneath it: &lt;strong&gt;what stops an agent — compromised, hijacked by a prompt injection, or simply hallucinating — from authorizing its own actions?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built a small, fully verifiable system to explore one answer to that question. This post is about the pattern, not the project. The principles are public; the exploitable internals are not (Kerckhoffs's principle). Everything below is reusable and vendor-neutral.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: separate &lt;em&gt;deciding&lt;/em&gt; from &lt;em&gt;signing&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Most of the agentic-wallet stack being built today — by Coinbase, Ledger, Cobo, and others — splits along two lines. On the developer side, agents get broad autonomy inside their own custody domain. On the consumer side, the human keeps the keys and the agent acts under progressively scoped authorization: each meaningful action either prompts the user or stays inside tight, predefined limits.&lt;/p&gt;

&lt;p&gt;The pattern I'll describe lives firmly on the second side, and it pushes one principle to its logical end:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An agent can have full autonomy over &lt;strong&gt;judgment&lt;/strong&gt; while having zero autonomy over &lt;strong&gt;signature&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent decides &lt;em&gt;what&lt;/em&gt; it wants to do — evaluate, choose, propose a transaction — entirely on its own. But the act that makes the transaction real, the cryptographic signature, is structurally out of its reach. Not "discouraged." Not "policy-checked." &lt;strong&gt;Mechanically unreachable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my case, the agent is an art curator: it evaluates artworks on a multidimensional grid, decides on its own whether to bid in an on-chain auction or abstain, and places a bid. But it can never confirm that bid. A human action, outside the agent's process, is the only thing that triggers signing and broadcast. The whole cycle is verifiable on a public testnet — no trust required.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the art. It's the four properties that make "the agent cannot sign" true rather than aspirational.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1 — Out-of-band human confirmation (fail-closed)
&lt;/h2&gt;

&lt;p&gt;The agent can &lt;em&gt;trigger&lt;/em&gt; a transaction. It cannot &lt;em&gt;confirm&lt;/em&gt; one. The confirmation channel is out-of-band — it lives outside the agent's reach entirely. If confirmation never arrives, the transaction does not proceed: it fails closed, not open.&lt;/p&gt;

&lt;p&gt;This is the property most people get subtly wrong. It is not enough to keep the signing &lt;strong&gt;password&lt;/strong&gt; away from the agent. If the agent holds an allowlisted tool that performs the signing once "confirmed," and the confirmation signal is something the agent can produce, then a compromised agent simply produces it. &lt;strong&gt;You need two separate barriers: the secret must be out of the agent's reach, and the confirmation must be too.&lt;/strong&gt; Move only one, and you have a gate the agent can open itself.&lt;/p&gt;

&lt;p&gt;Concretely: the signing step waits for a human-originated signal delivered through a channel the agent has no handle on. No signal, no signature. The agent cannot self-confirm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2 — Deterministic server-side decision
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;authorization&lt;/em&gt; decision is made by a deterministic, non-probabilistic component — not by the model.&lt;/p&gt;

&lt;p&gt;This matters because a language model is, by design, a probabilistic system. That variance is a feature when you want a model to exercise judgment (in my case, aesthetic scoring — and the score genuinely varies a point or two across runs, which is correct behavior for a coherent evaluator). It is a catastrophe when the question is "should this transaction be authorized: yes or no?" You do not want that answer sampled from a distribution.&lt;/p&gt;

&lt;p&gt;So you split the two. The model is allowed to be probabilistic where judgment lives. The authorization gate is deterministic code, where a yes/no must mean yes/no every single time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3 — Strict least privilege
&lt;/h2&gt;

&lt;p&gt;The agent has access only to the tools strictly necessary — evaluate, propose. It has access to neither the signing key nor its secret. Ever. The key is decrypted only &lt;em&gt;after&lt;/em&gt; human confirmation, never exposed to the agent, and never written to any log.&lt;/p&gt;

&lt;p&gt;A useful corollary: the agent does not even discover work on its own in the high-privilege path. Auctions are submitted to it; it does not roam and find them. The smaller the agent's surface, the smaller the blast radius when something goes wrong — and with autonomous agents, you should assume something eventually will.&lt;/p&gt;

&lt;p&gt;This is the same instinct behind the allowlists and scoped permissions appearing across the industry's hardened tooling: agents need signing capability, but unsafe delegation creates catastrophic-loss scenarios. Least privilege is how you keep "the agent can act" from becoming "the agent can do anything."&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4 — Network isolation (allowlist)
&lt;/h2&gt;

&lt;p&gt;The agent's outbound traffic is restricted to a strict allowlist. This does two jobs at once: it blocks exfiltration (a compromised agent cannot phone home or leak secrets to an arbitrary endpoint), and it blocks requests to internal addresses (no using the agent as a pivot into your own infrastructure). Content the agent needs is fetched only from pinned, known sources.&lt;/p&gt;

&lt;p&gt;Prompt injection and tool manipulation are repeatedly named as the unresolved risks of this category. You cannot fully prevent a model from being manipulated by what it reads. But you &lt;em&gt;can&lt;/em&gt; make sure that even a fully manipulated model is talking to a very short list of places — and that none of them are your private network or an attacker's server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this holds up
&lt;/h2&gt;

&lt;p&gt;Put the four together and you get a system with a specific, falsifiable claim: &lt;strong&gt;even a fully compromised agent cannot sign a transaction.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It cannot confirm its own action (Pattern 1).&lt;/li&gt;
&lt;li&gt;It cannot coerce a "yes" out of the authorization gate, because that gate is deterministic and lives outside it (Pattern 2).&lt;/li&gt;
&lt;li&gt;It never holds the key or its secret to begin with (Pattern 3).&lt;/li&gt;
&lt;li&gt;It cannot exfiltrate that secret or pivot, because it can only reach a pinned allowlist (Pattern 4).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is novel in isolation. Out-of-band confirmation, deterministic gates, least privilege, and egress allowlists are old security ideas. The point is the &lt;em&gt;composition&lt;/em&gt;, aimed at one specific failure mode of autonomous agents: self-authorization. The Owockibot failure was, at bottom, an agent given enough rope to expose its own keys. The whole design above is about never handing the agent that rope.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this deliberately is &lt;em&gt;not&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;It is not a fully autonomous trading agent. The entire premise is that the irreversible step keeps a human in the loop. If your use case genuinely requires machine-speed signing with no human anywhere, this pattern is the wrong tool — you're on the developer-autonomy side of the split, and you should be looking at MPC custody, session keys with hard scoped limits, and circuit breakers instead.&lt;/p&gt;

&lt;p&gt;It is also not a finished product or financial anything. The implementation I built runs on a public testnet with no real value at stake — it exists to prove the pattern end-to-end, on-chain, where anyone can check it rather than take my word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;As agents get wallets, the reflex is to ask "how much can we let the agent do?" The more useful question is often the inverse: &lt;strong&gt;what must the agent be unable to do, no matter how badly it is compromised?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For anything irreversible — moving value, signing, spending — a clean answer is: it must be unable to authorize itself. Let it decide freely. Keep the signature out of its hands. Make that separation mechanical, not merely intended.&lt;/p&gt;

&lt;p&gt;If you're building in this space, I'd genuinely value scrutiny of the threat model — that's the part worth pressure-testing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo, with Claude as a design-and-audit partner and Claude Code as the executor; the curation agent itself runs on the Anthropic API.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proof-of-concept and the security patterns are public:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://nexus-art.org" rel="noopener noreferrer"&gt;nexus-art.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code &amp;amp; patterns: &lt;a href="https://github.com/avp9-nexus/nexus-art" rel="noopener noreferrer"&gt;github.com/avp9-nexus/nexus-art&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The full auction cycle is verifiable on-chain (Base Sepolia testnet — no real value at stake).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
