<?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: Michael "Mike" K. Saleme</title>
    <description>The latest articles on DEV Community by Michael "Mike" K. Saleme (@mspro3210).</description>
    <link>https://dev.to/mspro3210</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%2F3851462%2Fa7c27b1b-53a0-4eb1-ac6d-c5a785fbc6ad.jpg</url>
      <title>DEV Community: Michael "Mike" K. Saleme</title>
      <link>https://dev.to/mspro3210</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mspro3210"/>
    <language>en</language>
    <item>
      <title>Satisfied Is Not Established</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 05 Sep 2026 18:04:36 +0000</pubDate>
      <link>https://dev.to/mspro3210/satisfied-is-not-established-bf5</link>
      <guid>https://dev.to/mspro3210/satisfied-is-not-established-bf5</guid>
      <description>&lt;p&gt;I went back through two months of my own writing this week and found I had written the same sentence about twenty times.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MCP went stateless. &lt;strong&gt;My test suite stayed green anyway.&lt;/strong&gt;&lt;br&gt;
Every action was authorized. &lt;strong&gt;The sequence still crossed the line.&lt;/strong&gt;&lt;br&gt;
A signed agent receipt &lt;strong&gt;can still make an unsupported claim.&lt;/strong&gt;&lt;br&gt;
My quorum test &lt;strong&gt;never had an approver in it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had not noticed. Each was written as a separate observation about a different control failure. They are one observation, and it is worth stating directly instead of twenty more times sideways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Satisfied is a fact about the check. Established is a conclusion the evidence has to warrant.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A passing verdict tells you that the implemented decision procedure accepted the observed input. It does not, on its own, establish the property named by the test. That depends on what the check actually exercised, what evidence it observed, and what conditions would make it fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three places the gap opens
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Authorization that permits without deciding.&lt;/strong&gt; An agent can leak private repositories &lt;a href="https://dev.to/mspro3210/an-ai-agent-leaked-private-repos-without-ever-breaking-a-single-permission-4a9l"&gt;without breaking a single permission&lt;/a&gt;. Every call was allowed; &lt;a href="https://dev.to/mspro3210/every-api-call-was-allowed-the-agents-outcome-wasnt-549d"&gt;the outcome still wasn't&lt;/a&gt;. The permission model was satisfied. Nothing about the outcome was established, because the permission model never ranged over outcomes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signatures that bind without validating the claim.&lt;/strong&gt; &lt;a href="https://dev.to/mspro3210/curl-just-merged-rfc-9421-support-a-valid-signature-still-isnt-authorization-48md"&gt;curl merged RFC 9421 support&lt;/a&gt;, and a valid signature still is not authorization. A valid signature can bind the covered receipt content to trusted key material; &lt;a href="https://dev.to/mspro3210/a-signature-proves-who-signed-the-receipt-it-does-not-prove-the-receipt-is-true-here-is-how-you-5ed8"&gt;it does not prove the claim in the receipt is true&lt;/a&gt;. The cryptography is sound and the claim on top of it is unaudited. &lt;a href="https://dev.to/mspro3210/a-signed-agent-receipt-can-still-make-an-unsupported-claim-3c1k"&gt;A signed receipt can still make an unsupported claim&lt;/a&gt;, which is why &lt;a href="https://dev.to/mspro3210/the-receipt-cannot-be-written-by-the-pen-it-is-checking-separation-of-duties-for-agent-payments-560d"&gt;the receipt cannot be written by the pen it is checking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluations that pass without exercising.&lt;/strong&gt; This is the third class, and it is the one I want to spend the rest of this on.&lt;/p&gt;

&lt;p&gt;The mechanisms are different. Authorization, cryptographic verification and evaluation validity are not one thing. What they share is the mistake of treating the scope of a successful check as if it were the scope of the claim made from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The third class is the uncomfortable one
&lt;/h2&gt;

&lt;p&gt;The first two can be discussed at a safe distance. The third turns the instrument back on whoever is holding it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mspro3210/mcp-went-stateless-my-test-suite-stayed-green-anyway-ag3"&gt;My test suite stayed green&lt;/a&gt; after MCP went stateless, because it was asserting against a shape that no longer existed. &lt;a href="https://dev.to/mspro3210/can-your-security-test-suite-fail-mine-passed-against-nothing-534f"&gt;My suite passed against nothing&lt;/a&gt; — a closed port produced the same verdict as an enforced control. &lt;a href="https://dev.to/mspro3210/i-tested-my-own-method-four-times-its-strongest-claim-never-passed-5djp"&gt;I tested my own method four times&lt;/a&gt; and its strongest claim never held. And last week I found a test named &lt;em&gt;Approval Quorum&lt;/em&gt; that &lt;a href="https://dev.to/mspro3210/my-quorum-test-never-had-an-approver-in-it-3dj7"&gt;never had an approver in it&lt;/a&gt;: it asserted threshold routing under a name claiming quorum enforcement, and it had been green for months.&lt;/p&gt;

&lt;p&gt;Then I wrote the fix, and the fix had the same defect one level in — it checked that approvers were distinct and never that they were authorized. Two arbitrary strings formed a quorum.&lt;/p&gt;

&lt;p&gt;Three of those surfaced in eight days; the MCP defect was earlier in the month. I was actively looking for this exact class of failure and still reproduced it in the repair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why passing evidence is trusted more than it earns
&lt;/h2&gt;

&lt;p&gt;Three artifacts usually travel together: a name, a mapping to some requirement, and a result. When all three agree, review stops.&lt;/p&gt;

&lt;p&gt;But they are not three independent witnesses. The name was written from an intention. The mapping was written from the name. The result reports whatever the assertions happened to cover. Consistency across them shows the same abstraction propagated cleanly — including, when it is wrong, the error.&lt;/p&gt;

&lt;p&gt;As agent-assurance programs formalize, that shared lineage matters more. A control name, a requirement mapping and a passing result can look like independent corroboration even when all three descend from the same mistaken abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to tell the difference
&lt;/h2&gt;

&lt;p&gt;One question, asked of a check rather than of a system: &lt;strong&gt;what concrete violation of the named property would make this check fail?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you cannot answer that concretely, the check has not yet established the property it names — however green it is, however cleanly it maps. It may still establish something narrower, as mine established threshold routing. Narrower is not nothing; it is just not what the name promised.&lt;/p&gt;

&lt;p&gt;Then build that case and run it. Not a generic pass-and-fail pair; outcome polarity is not property coverage. A quorum test needs the same approver counted twice, an approval from outside the authorized set, and an approval bound to a different action. Each one aimed at a specific property the name claims.&lt;/p&gt;

&lt;p&gt;And point the check at an implementation that has the defect. Adding assertions proves only that more predicates ran. A suite that fails against a deliberately broken implementation demonstrates something more important: it can recognize the defect it claims to detect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am doing with this
&lt;/h2&gt;

&lt;p&gt;I am going to keep finding these, mostly in my own work, and write them up as one series instead of twenty unrelated posts.&lt;/p&gt;

&lt;p&gt;The instances are not the interesting part. The interesting part is that this failure can stay invisible to the routine signals teams review software with — the linter, CI status, the coverage report, the requirements crosswalk. Each can report consistency without demonstrating that the named property was exercised.&lt;/p&gt;

&lt;p&gt;The decisive test is a case built to violate the property.&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>My Quorum Test Never Had an Approver in It</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 05 Sep 2026 15:28:57 +0000</pubDate>
      <link>https://dev.to/mspro3210/my-quorum-test-never-had-an-approver-in-it-3dj7</link>
      <guid>https://dev.to/mspro3210/my-quorum-test-never-had-an-approver-in-it-3dj7</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last week I wrote about tests that pass because nothing was there — a suite pointed at a closed port, reporting that a control held when nothing had been contacted.&lt;/p&gt;

&lt;p&gt;This is the opposite failure, and I think it is the more common one.&lt;/p&gt;

&lt;p&gt;Everything was there. The test called a working reference implementation and produced a true verdict about threshold routing. It just reported that verdict as evidence of quorum.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_fb_013_approval_quorum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;FB-013: Above-threshold spend requires manual approval (MEDIUM).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PolicyEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SpendPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;allowlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0xM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="n"&gt;per_tx_cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;auto_sign_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0xM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;test_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FB-013&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approval Quorum Above Threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;policy_approval_quorum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fireblocks Policy Engine — approver quorum above auto-sign threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model_pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require_approval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, what this is not. &lt;code&gt;PolicyEngine&lt;/code&gt; here is &lt;strong&gt;my own reference model&lt;/strong&gt; of &lt;a href="https://www.fireblocks.com/platforms/governance-and-policies" rel="noopener noreferrer"&gt;Fireblocks-style policy routing&lt;/a&gt;, living in my harness. The &lt;code&gt;ref=&lt;/code&gt; string names the real product because that is the behaviour the model imitates. Nothing below is a defect in Fireblocks, or in any shipped product. The defective code is mine.&lt;/p&gt;

&lt;p&gt;Now read the assertion, then read the name.&lt;/p&gt;

&lt;p&gt;There is no approver set anywhere in this test. No identities, so nothing for distinctness to be checked against. No approval bound to the action being evaluated.&lt;/p&gt;

&lt;p&gt;What it asserts is routing: a spend above the auto-sign threshold must not be auto-signed. That is a real control and it really holds.&lt;/p&gt;

&lt;p&gt;Quorum is never exercised. Nothing here would notice if the same approver approved twice, or if an approval granted for an entirely different transfer counted toward this one.&lt;/p&gt;

&lt;p&gt;It was green for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it survived
&lt;/h2&gt;

&lt;p&gt;Three review artifacts were mutually consistent.&lt;/p&gt;

&lt;p&gt;The name said quorum. The requirement mapping said quorum. The result said pass.&lt;/p&gt;

&lt;p&gt;They repeated the same claim, and the code established only threshold routing. Consistency across metadata is not evidence about the assertion — it may only mean the metadata inherited the same abstraction error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this bites beyond one test
&lt;/h2&gt;

&lt;p&gt;The pattern generalizes to compliance crosswalks: once a control name is mapped to a requirement ID, the mapping itself starts to look like evidence.&lt;/p&gt;

&lt;p&gt;AIUC-1 gives a concrete example. &lt;a href="https://www.aiuc-1.com/evidence/third-party" rel="noopener noreferrer"&gt;Six requirements call for quarterly third-party evaluation&lt;/a&gt; — B001, C010, C011, C012, D002 and D004. Their evidence artifacts require reports documenting the applicable risk scope, methodology, findings and remediation tracking; the C and D controls also expressly call for records of assessor qualifications and independence.&lt;/p&gt;

&lt;p&gt;Every one of those artifacts can be in perfect order around a test like mine. The assessor is qualified, the methodology is documented, the report is complete, the cadence is met — and the claimed property was still never exercised.&lt;/p&gt;

&lt;p&gt;The word "quorum" was doing work the assertion did not do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that would not have worked
&lt;/h2&gt;

&lt;p&gt;My first instinct was to require paired positive and negative controls: every control test would have to show the model accepting what it should permit and rejecting what it should prohibit.&lt;/p&gt;

&lt;p&gt;That still would not have caught this. Below the threshold the policy could auto-sign; above it, require approval. Both routing outcomes could be correct while quorum remained completely untested.&lt;/p&gt;

&lt;p&gt;The outcome was never the problem. The property was.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that does
&lt;/h2&gt;

&lt;p&gt;Write deliberately invalid cases aimed at the specific property the name claims, and require the test to detect each one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_one_approver_twice_is_not_a_quorum_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Cardinality is not identity. This is the defect class itself.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApprovalQuorum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;eligible_approvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="n"&gt;recorded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;recorded&lt;/span&gt;
    &lt;span class="n"&gt;recorded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;recorded&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the same approver counted twice reached the threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_approval_for_a_different_action_does_not_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;An approval is granted for one action, not for the approver&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s
    general willingness to approve.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActionRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pay_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0xM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tx-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApprovalQuorum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;eligible_approvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="n"&gt;recorded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;recorded&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_an_ineligible_principal_does_not_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Identity is not authority. Two distinct strangers are still strangers.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApprovalQuorum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;eligible_approvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approver-b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stranger-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# recorded == False
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stranger-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;two distinct ineligible parties formed a quorum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The suite needs to establish five obligations, one per property the name claims:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;two distinct &lt;strong&gt;eligible&lt;/strong&gt; principals satisfy the quorum;&lt;/li&gt;
&lt;li&gt;the same principal twice does not;&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;ineligible&lt;/strong&gt; principal does not count, however well-formed the approval;&lt;/li&gt;
&lt;li&gt;an approval for a different action does not count;&lt;/li&gt;
&lt;li&gt;changing any authorization-relevant field invalidates the approval — here, an approval carries a frozen action record and is compared against the action under evaluation, so a difference in destination, amount or nonce makes it an approval for something else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those three fields are what this reference model binds, and naming them is the point: a production authority might also need to bind asset, network, method, calldata, expiry and policy version. State the fields your check actually covers, because "bound to the action" is exactly the kind of phrase that outruns its assertion.&lt;/p&gt;

&lt;p&gt;The first is not filler. A check that rejects everything has enforced nothing, so the suite has to accept the case it exists to permit.&lt;/p&gt;

&lt;p&gt;Obligation 3 is there because I left it out. My first fix checked that approvers were &lt;em&gt;distinct&lt;/em&gt; and never that they were &lt;em&gt;authorized&lt;/em&gt; — so two arbitrary strings formed a quorum. I had written a class to fix a test that counted without checking identity, and it checked identity without checking authority. The same defect, one level in. That is how strong this pull is: I found it, named it, wrote about it, and reproduced it inside the remedy.&lt;/p&gt;

&lt;p&gt;After rewriting FB-013 to exercise those five obligations through &lt;code&gt;ApprovalQuorum&lt;/code&gt;, I ran the rewritten test against an intentionally defective implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CountingQuorum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApprovalQuorum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;An intentionally defective implementation: counts approvals and
    checks nothing else. Injected to prove FB-013 detects it.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;approver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_approvers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;approver&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_approvers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;harness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;X402FireblocksTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;simulate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApprovalQuorum&lt;/span&gt;
&lt;span class="n"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApprovalQuorum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CountingQuorum&lt;/span&gt;          &lt;span class="c1"&gt;# the injection
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_fb_013_approval_quorum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApprovalQuorum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FB-013&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FB-013 passed against a quorum that only counts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Substitution by module attribute rather than a constructor argument, because the&lt;br&gt;
test builds its own quorum internally. It is cruder than dependency injection and&lt;br&gt;
it does the same job: FB-013 runs unmodified against a defective implementation and&lt;br&gt;
has to notice.&lt;/p&gt;

&lt;p&gt;That last one is the part I would not skip again. Adding assertions makes a test say more. Injecting the defect it is supposed to catch proves it can still say no.&lt;/p&gt;

&lt;p&gt;All of it is public, if you want to check rather than take my word:&lt;br&gt;
&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/blob/74d6f28/protocol_tests/x402_fireblocks_harness.py#L759" rel="noopener noreferrer"&gt;the defect at its pinned revision&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/508" rel="noopener noreferrer"&gt;the correction&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/512" rel="noopener noreferrer"&gt;the eligibility hole in that correction&lt;/a&gt;,&lt;br&gt;
and &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/304" rel="noopener noreferrer"&gt;the external reproducibility work&lt;/a&gt;&lt;br&gt;
that started the review — scoped by its author as a compatibility result, not validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would take from this
&lt;/h2&gt;

&lt;p&gt;A test name is a claim, but ordinary CI does not establish that the assertions prove it. Not the linter, not the coverage report, and not the crosswalk that cites the test as evidence.&lt;/p&gt;

&lt;p&gt;For that, the suite needs cases deliberately constructed to violate the named property — and evidence that the test fails against an implementation containing that defect.&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Can your security test suite fail? Mine passed against nothing.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sun, 30 Aug 2026 01:16:45 +0000</pubDate>
      <link>https://dev.to/mspro3210/can-your-security-test-suite-fail-mine-passed-against-nothing-534f</link>
      <guid>https://dev.to/mspro3210/can-your-security-test-suite-fail-mine-passed-against-nothing-534f</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most security test suites answer one question: did the attack succeed? The&lt;br&gt;
verdict then falls out of a search for evidence that it did.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;leaked&lt;/span&gt;
&lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;granted_admin&lt;/span&gt;
&lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those is true when the attack was blocked. Every one of them is&lt;br&gt;
also true when nothing happened at all.&lt;/p&gt;

&lt;p&gt;That second case is not hypothetical. It is what a suite reports when the target&lt;br&gt;
was unreachable, when the endpoint does not implement the feature under test,&lt;br&gt;
when the tool list came back empty, and when the model complied politely in&lt;br&gt;
language the detector does not recognize.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three sentinels, not one target
&lt;/h2&gt;

&lt;p&gt;The fix is not a better detector. It is asking the same suite the same question&lt;br&gt;
across three deliberately different response conditions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a closed port      never answers
a permissive stub  returns 200 and explicit grant markers to everything
a refusing stub    returns 403 and an error envelope to everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are diagnostic sentinels, not conformant implementations. A 200 carrying&lt;br&gt;
&lt;code&gt;{"granted": true}&lt;/code&gt; is not an MCP or payment-protocol grant, and a blanket 403&lt;br&gt;
may block discovery rather than demonstrate enforcement. Their purpose is&lt;br&gt;
narrower and useful: to expose verdicts that ignore or misread target behavior.&lt;/p&gt;

&lt;p&gt;Point the suite at all three and read what changes.&lt;/p&gt;

&lt;p&gt;A target-dependent test that passes against the closed port is claiming a&lt;br&gt;
control held when nothing was contacted. One that passes against the permissive&lt;br&gt;
sentinel despite explicit grant markers deserves inspection. And a test that&lt;br&gt;
reaches the protected operation but cannot recognize an explicit denial may not&lt;br&gt;
recognize the outcome it exists to measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a target-dependent control, an unchanged verdict across all three&lt;br&gt;
sentinels is evidence that the verdict may not be measuring target behavior.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The qualifier matters. Local controls, parser checks, and over-refusal tests can&lt;br&gt;
legitimately return the same verdict everywhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it found
&lt;/h2&gt;

&lt;p&gt;I ran this against my own agent security harness, which is public, and which I&lt;br&gt;
had believed was in good shape.&lt;/p&gt;

&lt;p&gt;The closed port went first. Eighty-eight verdicts passed against a host that was&lt;br&gt;
not running, across ten modules. The largest single count was a payment protocol&lt;br&gt;
harness reporting forty-four controls holding against nothing at all.&lt;/p&gt;

&lt;p&gt;The permissive sentinel went next, and it is the harder one because the target&lt;br&gt;
answers. Every serviced-request guard I had just written was satisfied and&lt;br&gt;
correctly stayed out of the way. Three hundred and nine verdicts out of five&lt;br&gt;
hundred and thirty-two passed against a server returning HTTP 200 and explicit&lt;br&gt;
grant markers to every request:&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="nl"&gt;"granted"&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;"admin"&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"operator.admin"&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;One of them read "Elevated scope claims not honored." Another read "All&lt;br&gt;
gate-disable attempts were rejected." A third reported an incident-detection&lt;br&gt;
latency of 0.000 seconds, measured from a connection being refused.&lt;/p&gt;

&lt;p&gt;Not all three hundred and nine are defects, and I will come back to that.&lt;/p&gt;

&lt;p&gt;The refusing sentinel went last and it found the inverse. Sixteen suites&lt;br&gt;
produced no passing verdict against it. Three of those test controls where a&lt;br&gt;
platform refusing an action is the behavior under test: a gate-disable, a kill&lt;br&gt;
signal, an incident response. Those three could not recognize the one outcome&lt;br&gt;
they exist to recognize.&lt;/p&gt;

&lt;p&gt;Put the second and third together and you get the sharpest diagnostic result.&lt;br&gt;
One identity and authorization suite scored seven of eighteen against the&lt;br&gt;
permissive sentinel and one of eighteen against the refusing sentinel. Its&lt;br&gt;
verdict moved in the opposite direction from the behavior it was intended to&lt;br&gt;
recognize, and neither sentinel alone would have exposed that inversion as&lt;br&gt;
clearly.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part I did not expect
&lt;/h2&gt;

&lt;p&gt;None of the three found the most interesting defect.&lt;/p&gt;

&lt;p&gt;An external reviewer reading the source found it instead. One test serialized&lt;br&gt;
the whole transport-error envelope into a substring detector whose keyword list&lt;br&gt;
contained "refuse". The string it fed in was&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;urlopen error [Errno 111] Connection refused&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so the agent was credited with refusing a prompt injection it never received.&lt;br&gt;
Not an absence read as a pass. A positive match on the wrong text.&lt;/p&gt;

&lt;p&gt;Then a run against a real MCP server found a second one. The protocol has two&lt;br&gt;
ways to report a rejection, and my harness read only one of them, so a reference&lt;br&gt;
server that correctly refused an unregistered tool call was recorded as having&lt;br&gt;
failed to refuse it.&lt;/p&gt;

&lt;p&gt;The sentinels could reveal suspicious verdicts, but they could not&lt;br&gt;
independently identify either interpretation defect. The permissive and refusing&lt;br&gt;
fixtures used the response idiom my harness already expected. The closed port&lt;br&gt;
produced a transport error that the harness mistakenly treated as response&lt;br&gt;
evidence. &lt;strong&gt;A fixture built around your interpretation of the property under&lt;br&gt;
test cannot tell you that the interpretation itself is wrong.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What this is worth to you
&lt;/h2&gt;

&lt;p&gt;The method transfers. It does not depend on my harness, my protocols, or my&lt;br&gt;
threat model.&lt;/p&gt;

&lt;p&gt;If you maintain a security test suite, a scanner, a conformance checker, or an&lt;br&gt;
eval, you can build all three sentinels in an afternoon. They are a closed port,&lt;br&gt;
a web server that returns 200 to everything, and one that returns 403 to&lt;br&gt;
everything.&lt;/p&gt;

&lt;p&gt;Three things worth knowing before you do.&lt;/p&gt;

&lt;p&gt;The counts are a reading list, not a defect count. This is why I did not call&lt;br&gt;
those three hundred and nine defects. Some passes against the permissive sentinel&lt;br&gt;
are correct. Twenty-five of mine were over-refusal checks whose expected outcome&lt;br&gt;
is permissive behavior. Other tests require protocol-specific markers the generic&lt;br&gt;
sentinel does not emit. Only reading the test separates those cases from a real&lt;br&gt;
inversion.&lt;/p&gt;

&lt;p&gt;Zero verdicts is not zero defects. One module in mine aborts cleanly when it&lt;br&gt;
cannot handshake, which is the right behavior and which rendered as 0/0,&lt;br&gt;
indistinguishable from a clean run. It now prints "produced no verdicts, nothing&lt;br&gt;
measured, not clean."&lt;/p&gt;

&lt;p&gt;And the sentinels are the floor, not the ceiling. Get to a real implementation&lt;br&gt;
as soon as they stop finding things. Source review and a half-hour test against&lt;br&gt;
one real MCP implementation exposed two additional defect classes the synthetic&lt;br&gt;
sentinels structurally missed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Inspect and rerun it
&lt;/h2&gt;

&lt;p&gt;The repaired harness and all three sweep scripts are pinned at commit&lt;br&gt;
&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/commit/e151566550ecdb29b0230d40fb371fdf03bbfe09" rel="noopener noreferrer"&gt;&lt;code&gt;e151566550ecdb29b0230d40fb371fdf03bbfe09&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
of &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric" rel="noopener noreferrer"&gt;msaleme/red-team-blue-team-agent-fabric&lt;/a&gt;,&lt;br&gt;
released as &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/releases/tag/v4.16.0" rel="noopener noreferrer"&gt;v4.16.0&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/dead_host_sweep.py
python3 scripts/permissive_host_sweep.py
python3 scripts/refusing_host_sweep.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands rerun the method against the repaired harness. They will not&lt;br&gt;
reproduce the original pre-repair counts, because the repairs are in. The&lt;br&gt;
original findings are preserved in the earlier state files and in&lt;br&gt;
&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/417" rel="noopener noreferrer"&gt;PR #417&lt;/a&gt;&lt;br&gt;
through&lt;br&gt;
&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/432" rel="noopener noreferrer"&gt;PR #432&lt;/a&gt;;&lt;br&gt;
the &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/blob/main/docs/blog/THREE-SENTINELS-PROVENANCE.md" rel="noopener noreferrer"&gt;provenance record&lt;/a&gt;&lt;br&gt;
identifies the exact source of every figure reported here.&lt;/p&gt;

&lt;p&gt;I am not claiming any of this makes an agent secure. It establishes something&lt;br&gt;
narrower and, I think, more useful: what a harness claims when it has nothing to&lt;br&gt;
go on.&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Tested My Own Method Four Times. Its Strongest Claim Never Passed.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Fri, 28 Aug 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/mspro3210/i-tested-my-own-method-four-times-its-strongest-claim-never-passed-5djp</link>
      <guid>https://dev.to/mspro3210/i-tested-my-own-method-four-times-its-strongest-claim-never-passed-5djp</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical source:&lt;/strong&gt; &lt;a href="https://github.com/msaleme/token-bleed-benchmark/blob/main/docs/R2_1_RESULTS.md" rel="noopener noreferrer"&gt;R2.1 results&lt;/a&gt;, &lt;a href="https://github.com/msaleme/token-bleed-benchmark/blob/main/docs/R3_RESULTS.md" rel="noopener noreferrer"&gt;R3 results&lt;/a&gt;, &lt;a href="https://github.com/msaleme/token-bleed-benchmark/blob/main/docs/R5_RESULTS.md" rel="noopener noreferrer"&gt;R5 results&lt;/a&gt;, &lt;a href="https://github.com/msaleme/token-bleed-benchmark/blob/main/docs/R3_R4_R5_RECONCILIATION.md" rel="noopener noreferrer"&gt;R3/R4/R5 reconciliation&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Companion post:&lt;/strong&gt; &lt;a href="https://dev.to/mspro3210/context-is-part-of-an-agents-authority-35f6"&gt;Context Is Part of an Agent's Authority&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built a benchmark family to test whether a governed metadata layer earns its cost when an agent selects enterprise context. I have now run it four times under four frozen contracts, redesigning the catalog and changing the acceptance ceiling along the way.&lt;/p&gt;

&lt;p&gt;The claim that governance earns its cost against a cheap baseline has been rejected in every round that tested it. The round before those was rejected too, on a different rule.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Round&lt;/th&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Failed rule or controlling result&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;R2.1&lt;/td&gt;
&lt;td&gt;Overall comparative claim&lt;/td&gt;
&lt;td&gt;Governed holdout F1 0.24065, below the 0.245533 floor&lt;/td&gt;
&lt;td&gt;REJECTED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R3&lt;/td&gt;
&lt;td&gt;Governed value vs. lexical&lt;/td&gt;
&lt;td&gt;F1 CI &lt;code&gt;[-0.371, 0.00005]&lt;/code&gt;; token ratio 2.11x vs. 1.10x ceiling&lt;/td&gt;
&lt;td&gt;REJECTED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R4&lt;/td&gt;
&lt;td&gt;Governed value vs. lexical&lt;/td&gt;
&lt;td&gt;Quality passed; token ratio 9.86x vs. 3.0x ceiling&lt;/td&gt;
&lt;td&gt;REJECTED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R5&lt;/td&gt;
&lt;td&gt;Governed value vs. lexical&lt;/td&gt;
&lt;td&gt;Quality passed; token ratio 6.94x vs. 3.0x ceiling&lt;/td&gt;
&lt;td&gt;REJECTED&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;R3 through R5 are value-claim verdicts. R2.1 was the earlier overall-contract rejection that led me to separate the claims. Other claims passed: R3 and R5 accepted governed routing against full-context stuffing, while R4 returned those claims inconclusive.&lt;/p&gt;

&lt;p&gt;Every round ran under a contract frozen before collection. The claim-scoped outcomes are documented publicly; R3 and R5 include public decision packs, while R4's later-derived pack remains held and is disclosed as such below.&lt;/p&gt;

&lt;p&gt;R2.1 failed first. At its 3,000-object holdout the governed route scored 0.24065 against a prespecified floor of 0.245533, while the lexical prefilter scored 0.588.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 3: the simple baseline won the observed comparison
&lt;/h2&gt;

&lt;p&gt;R3 compared three routes on the same local model: raw full-context stuffing, a cheap lexical prefilter, and a governed metadata route.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Background.&lt;/strong&gt; This benchmark reproduces the &lt;em&gt;structure&lt;/em&gt; of McKnight Consulting Group's study, &lt;strong&gt;"&lt;a href="https://mcknightcg.com/stop-the-token-bleed-benchmarking-the-benefits-of-governed-metadata-for-enterprise-ai/" rel="noopener noreferrer"&gt;Stop the Token Bleed: Benchmarking the Benefits of Governed Metadata for Enterprise AI&lt;/a&gt;"&lt;/strong&gt; (Jake Dolezal and William McKnight, August 2026; sponsored by Informatica, a Salesforce company). Their study held the model constant and found governed metadata access won on both cost (up to roughly 89x fewer tokens at scale) and accuracy (F1 1.000 against 0.29 to 0.66 ungoverned). Read their article for their full methodology and figures. This work does not reproduce their exact numbers. It lets you generate your own, on your own model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure.&lt;/strong&gt; The study reproduced here was sponsored by Informatica, a Salesforce company. I am employed by Salesforce. That is a reason to run this harness yourself rather than take my output on trust, which is the entire point of publishing it. Contradicting results are welcome.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mean F1 across 20 seeds, at the prespecified 0% classifier-miss condition:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Catalog size&lt;/th&gt;
&lt;th&gt;Governed F1&lt;/th&gt;
&lt;th&gt;Full-context F1&lt;/th&gt;
&lt;th&gt;Lexical F1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;0.780&lt;/td&gt;
&lt;td&gt;0.261&lt;/td&gt;
&lt;td&gt;0.660&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,500&lt;/td&gt;
&lt;td&gt;0.632&lt;/td&gt;
&lt;td&gt;0.253&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.737&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3,000 (holdout)&lt;/td&gt;
&lt;td&gt;0.447&lt;/td&gt;
&lt;td&gt;0.177&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.631&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the two columns downward. The governed route degrades monotonically as the catalog grows: 0.780, then 0.632, then 0.447. The lexical route does not show the same pattern: 0.660, 0.737, then 0.631.&lt;/p&gt;

&lt;p&gt;At the holdout, the keyword filter beat the governed route on the observed means and used less than half the prompt tokens. The paired F1 interval was &lt;code&gt;[-0.371, 0.00005]&lt;/code&gt;, which does not exclude zero in governance's favor. The token ratio was 2.11x against a frozen ceiling of 1.10x.&lt;/p&gt;

&lt;p&gt;Governed routing crushed full-context stuffing. That claim passed. It lost to the cheapest thing in the room.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed between rounds, said out loud
&lt;/h2&gt;

&lt;p&gt;I ran R4 and R5 on redesigned catalogs, and I relaxed my own cost ceiling.&lt;/p&gt;

&lt;p&gt;R3 used a lexically tractable synthetic catalog, where a keyword filter has real signal to match. R4 and R5 moved to semantic-access catalogs built on opaque physical names. In R5 the lexical route scored 0.000 F1 at every catalog size.&lt;/p&gt;

&lt;p&gt;The redesign favored my method on quality: this name-only lexical baseline no longer had matching signal. But it also made the cost comparison harder, because the lexical route now produced an extremely small prompt. At the R5 holdout it averaged 110.0 prompt tokens against the governed route's 763.5, which is why the ratio rose to 6.94x even as the ceiling was relaxed.&lt;/p&gt;

&lt;p&gt;Separately, between R3 and R4 I raised the maximum governed:lexical prompt-token ratio from 1.10x to 3.0x, which made the cost rule easier to pass.&lt;/p&gt;

&lt;p&gt;Both changes were declared in new frozen contracts before their respective collections.&lt;/p&gt;

&lt;p&gt;State that plainly, because a reader who diffs the contracts will find it anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  It still failed
&lt;/h2&gt;

&lt;p&gt;In R5, governed selection used 6.94 times the lexical route's prompt tokens. The frozen ceiling was 3.0x. The preregistered claim that governance earns its cost against lexical filtering was rejected.&lt;/p&gt;

&lt;p&gt;This happened on a task where the baseline scored zero. Governed context won the quality comparison but failed the prespecified prompt-token cost rule. The frozen contract required both.&lt;/p&gt;

&lt;p&gt;R4 failed the same rule at 9.86x. R4 also returned INCONCLUSIVE on its full-context claims because one holdout request contained 128,256 input tokens plus a reserved 3,000-token completion budget, putting it 184 tokens beyond the verified 131,072-token window. It made no model call. It was retained as a preflight refusal rather than silently dropped.&lt;/p&gt;

&lt;p&gt;The ceiling could have been relaxed again after seeing 6.94x. Moving a bar you already missed converts a result into a press release.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A benchmark that cannot reject its author is marketing with a methodology section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What this changes for anyone buying or building a context layer
&lt;/h2&gt;

&lt;p&gt;Ask three questions of any governed retrieval, semantic layer, or context-governance component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the cheap baseline, and did you run it?&lt;/strong&gt; Not merely full-context stuffing. At minimum, test the cheapest credible selective baseline: a keyword filter here, but potentially BM25, a cached lookup, or another simple retrieval route. If the only comparison is against full-context stuffing, the result may justify selective context, but it does not show that the sophisticated route earns its place over cheaper alternatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Was the acceptance rule written down before collection?&lt;/strong&gt; A cost ceiling chosen after seeing the numbers is a description, not a test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does the method lose?&lt;/strong&gt; A vendor who cannot name the configuration where their layer is the wrong choice has not measured it hard enough.&lt;/p&gt;

&lt;p&gt;My own answers: the baseline is a lexical prefilter, it beat the governed route on observed mean F1 and prompt-token use in R3, and the governed route has never cleared its cost bar in the three rounds that tested it. The route remains worth evaluating where opaque physical names make semantic selection necessary, or where an evidence trail has independent value. These runs establish the quality advantage in that narrow synthetic configuration; they do not yet establish end-to-end economic value. That is a narrower claim than the one I set out to prove.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence boundary
&lt;/h2&gt;

&lt;p&gt;These are synthetic, named-endpoint runtime characterizations on a single local model, &lt;code&gt;qwen3-coder:30b&lt;/code&gt;. They are not production results, ROI claims, customer-data results, or a replication of any third-party study. R2.1 remains visibly rejected in the public record rather than buried.&lt;/p&gt;

&lt;p&gt;R3 and R5 publish a full public packet: frozen contract hash, complete preflight, claim-scoped decision pack, and artifact digests. R4's contract is public, but its decision pack was derived after the fact from the archived report and is held rather than published. The raw reports stay private because they embed host identifiers, and their hashes are committed so you can tell if they ever change.&lt;/p&gt;

&lt;p&gt;Four rounds in, the most useful thing this benchmark has produced is the boundary it refuses to cross.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>Context Is Part of an Agent's Authority</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Fri, 21 Aug 2026 16:07:24 +0000</pubDate>
      <link>https://dev.to/mspro3210/context-is-part-of-an-agents-authority-35f6</link>
      <guid>https://dev.to/mspro3210/context-is-part-of-an-agents-authority-35f6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Technical source:&lt;/strong&gt; &lt;a href="https://github.com/msaleme/token-bleed-benchmark/releases/tag/r5-results-2026-08-17" rel="noopener noreferrer"&gt;Token-Bleed R5 release&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enterprise AI programs often treat context as a prompt-engineering problem: retrieve more documents, add more records, and let the model sort it out. That is backwards.&lt;/p&gt;

&lt;p&gt;The information an agent receives determines what it can infer, combine, and disclose. An agent's effective authority is therefore shaped by both its information scope and its permitted actions: context governs the former; capability controls govern the latter.&lt;/p&gt;

&lt;p&gt;Context does not grant permission to dispatch power, change a price, or execute a transaction. It expands what the agent can know, infer, and disclose, and therefore its practical power.&lt;/p&gt;

&lt;p&gt;Too little context is not neutral either: omitted constraints, exceptions, or dependencies can make a confident recommendation wrong. The architectural objective is therefore not minimum context, but minimum sufficient context.&lt;/p&gt;

&lt;p&gt;We spend substantial time defining action authority: which tools an agent may call, which systems it may reach, and which approvals it needs before a change takes effect. Information authority deserves the same discipline. Before an agent decides, architecture must establish what information it is permitted to see and what it actually needs for this decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full-context default is not neutral
&lt;/h2&gt;

&lt;p&gt;Sending every available record to a model feels safe because nothing has been left out. In practice, it can be expensive, dilute the relevant signal, make the decision harder to review, and expand the information the system can combine.&lt;/p&gt;

&lt;p&gt;That does not mean every workload needs a complex governance layer. It means every workload needs a comparator.&lt;/p&gt;

&lt;p&gt;In the retained Token-Bleed R5 synthetic experiment, compact governed selection used 96.9% to 97.9% fewer prompt tokens and achieved higher F1 than raw full-context stuffing on the frozen local configuration. The public record includes the frozen contract, preflight, retained-evidence decision pack, and hashes.&lt;/p&gt;

&lt;p&gt;The more important finding is the limitation. Against a cheap lexical baseline, governed selection consumed 6.94 times as many prompt tokens on the holdout set, exceeding the preregistered maximum of three. The lexical route scored 0.000 F1 at every catalog size, so governed context won the quality comparison outright. Even so, the preregistered claim that governance earned its cost against lexical filtering failed. The frozen economic rule controlled the verdict; relaxing that ceiling after collection would have invalidated it. One caveat belongs with that zero: R5 used opaque physical names, where lexical matching has nothing to grip. On an earlier round with a lexically tractable catalog, the same baseline beat governed selection on both quality and cost.&lt;/p&gt;

&lt;p&gt;That is exactly what useful architecture evidence should do. It should show where a method helps and where it has not yet earned the right to be the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Agent Architecture needs a context plane
&lt;/h2&gt;

&lt;p&gt;An EAA design should logically separate four responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connection plane:&lt;/strong&gt; APIs, events, and data services connect an agent to enterprise systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context plane:&lt;/strong&gt; retrieval, classification, policy, lineage, and routing assemble the minimum sufficient, policy-permitted information set for the decision at hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capability plane:&lt;/strong&gt; permissions and approvals govern what the agent may do next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence plane:&lt;/strong&gt; retained contracts, source references, tool traces, and human decisions make the outcome reviewable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The context plane is not a technical ornament between a database and a model. It decides what the agent knows before it chooses a path. That makes it an authority control.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An agent's context is part of its authority.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A practical decision rule
&lt;/h2&gt;

&lt;p&gt;For each agent workflow, begin with the least-complex, policy-permitted context route that could credibly meet the decision's quality requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a simple filter captures the relevant records and produces a reviewable decision, use it.&lt;/li&gt;
&lt;li&gt;Add governed metadata when field names are ambiguous, business definitions matter, access policy must be enforced before the model sees data, lineage changes the answer, or the evidence trail itself is required.&lt;/li&gt;
&lt;li&gt;Compare the richer route with the cheap baseline. Measure quality, omission risk under routing misses, prompt cost, latency, and the work required to maintain the governed layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not more governance. The goal is decision-useful context with a defensible cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes in industry workflows
&lt;/h2&gt;

&lt;p&gt;In energy and utilities, an outage or maintenance exception agent may need weather, load, asset, work-order, and switching-constraint context to recommend whether to keep, reschedule, or escalate a window. It should not receive unrestricted operating data, and it should not obtain dispatch authority merely because it can assemble a recommendation; that authority must be separately granted, bounded, and evidenced.&lt;/p&gt;

&lt;p&gt;In CPG and retail, a commercial exception agent may need store, SKU, promotion, inventory, cost, and service context to recommend a response. It should make the data basis, confidence, and required approver visible. It should not alter price or trade terms unless that capability has been separately authorized within explicit limits.&lt;/p&gt;

&lt;p&gt;Those are not generic chatbot problems. They are architecture problems involving context, authority, and evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence boundary
&lt;/h2&gt;

&lt;p&gt;R5 is a synthetic, named-endpoint runtime characterization. It is not a customer-data result, a production ROI study, or proof that governed context always beats a simple filter. The original raw report remains private because it contains host identifiers.&lt;/p&gt;

&lt;p&gt;That boundary is part of the result, not a footnote. Enterprise agents deserve evidence that is as specific about its limits as it is about its gains.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Where this fits.&lt;/strong&gt; This is one piece of a longer argument about enterprise agent architecture: what TOGAF and SABSA cannot model about a workforce that is not human, and where an agent's authority actually sits. The series runs at &lt;a href="https://msale00.substack.com/" rel="noopener noreferrer"&gt;Enterprise Agent Architecture&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>MCP Went Stateless. My Test Suite Stayed Green Anyway.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sun, 02 Aug 2026 16:19:55 +0000</pubDate>
      <link>https://dev.to/mspro3210/mcp-went-stateless-my-test-suite-stayed-green-anyway-ag3</link>
      <guid>https://dev.to/mspro3210/mcp-went-stateless-my-test-suite-stayed-green-anyway-ag3</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Enterprise systems accumulate trust in the places where state lives. A session identifier&lt;br&gt;
is not just a routing key — it is the thing a dozen downstream assumptions quietly hang&lt;br&gt;
from. Remove it and you do not remove one field. You invalidate every assumption that was&lt;br&gt;
resting on it, including the ones nobody wrote down.&lt;/p&gt;

&lt;p&gt;The Model Context Protocol's 2026-07-28 specification did exactly that. The project called&lt;br&gt;
it &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" rel="noopener noreferrer"&gt;"the largest revision of the protocol since launch"&lt;/a&gt;,&lt;br&gt;
and the two headline changes are both acts of subtraction. From the&lt;br&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" rel="noopener noreferrer"&gt;changelog's&lt;/a&gt; major&lt;br&gt;
changes, items 1 and 2:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remove protocol-level sessions and the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header from the Streamable HTTP&lt;br&gt;
transport.&lt;/p&gt;

&lt;p&gt;Make MCP stateless: remove the &lt;code&gt;initialize&lt;/code&gt;/&lt;code&gt;notifications/initialized&lt;/code&gt; handshake. Every&lt;br&gt;
request now carries its protocol version and client capabilities in &lt;code&gt;_meta&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the protocol layer, every request now stands on its own. Authorization gets stricter&lt;br&gt;
alongside it: clients&lt;br&gt;
&lt;strong&gt;MUST&lt;/strong&gt; validate a present &lt;code&gt;iss&lt;/code&gt; parameter against the recorded issuer per&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9207" rel="noopener noreferrer"&gt;RFC 9207&lt;/a&gt; before redeeming an&lt;br&gt;
authorization code, which closes a class of authorization-server mix-up attacks. There is&lt;br&gt;
a formal extensions framework, and a minimum twelve-month deprecation window before any&lt;br&gt;
deprecated feature can be removed.&lt;/p&gt;

&lt;p&gt;That is a good specification. This post is not about the specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What early tracking actually costs
&lt;/h2&gt;

&lt;p&gt;I had MCP tests running against the stateless profile on &lt;strong&gt;2026-07-22&lt;/strong&gt;, six days before&lt;br&gt;
the final specification was published. I want to be precise about what that bought and what&lt;br&gt;
it cost, because the first part is the part people write posts about and the second part is&lt;br&gt;
the part that matters.&lt;/p&gt;

&lt;p&gt;Between the release candidate and the final revision, the spec added an error-code&lt;br&gt;
allocation policy. The JSON-RPC server-error range got partitioned: &lt;code&gt;-32000&lt;/code&gt; to &lt;code&gt;-32019&lt;/code&gt;&lt;br&gt;
stays implementation-defined and grandfathered, &lt;code&gt;-32020&lt;/code&gt; to &lt;code&gt;-32099&lt;/code&gt; is reserved for the&lt;br&gt;
specification. Then it renumbered the codes the draft had introduced:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Release candidate&lt;/th&gt;
&lt;th&gt;Final&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HeaderMismatch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-32001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-32020&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MissingRequiredClientCapability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-32003&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-32021&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UnsupportedProtocolVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-32004&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-32022&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My harness checked for &lt;code&gt;-32004&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The function that reads that code decides one thing: when a server rejects the modern&lt;br&gt;
protocol version, is that an explicit stateless-protocol answer, or is it an old server&lt;br&gt;
that does not understand the request? Get it right and the probe stops. Get it wrong and&lt;br&gt;
the client falls back to &lt;code&gt;initialize&lt;/code&gt; — the handshake the specification just removed.&lt;/p&gt;

&lt;p&gt;Against any server built to the final spec, my check returned false. The harness read a&lt;br&gt;
compliant version rejection as evidence of a legacy server, sent the removed handshake,&lt;br&gt;
and reported results for a protocol the server had explicitly refused.&lt;/p&gt;

&lt;p&gt;The function's own docstring said that must not happen. It happened anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By the time the final specification landed, that assumption was already on PyPI.&lt;/strong&gt;&lt;br&gt;
Version 4.10.0 shipped on &lt;strong&gt;2026-07-25&lt;/strong&gt;, carrying the RC comparison verbatim. You do not&lt;br&gt;
have to take my word for it: download the wheel and read &lt;code&gt;protocol_tests/mcp_harness.py&lt;/code&gt;.&lt;br&gt;
The RC comparison is in the published artifact, and &lt;code&gt;-32022&lt;/code&gt; appears nowhere in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test suite stayed green the entire time
&lt;/h2&gt;

&lt;p&gt;This is the part worth sitting with.&lt;/p&gt;

&lt;p&gt;I have unit tests covering that exact function. They assert the fallback does not fire on&lt;br&gt;
a version rejection. They passed continuously — before the spec was final, after it was&lt;br&gt;
final, and through the release that carried the defect.&lt;/p&gt;

&lt;p&gt;They passed because every fixture in them was written during the RC window and pinned&lt;br&gt;
&lt;code&gt;-32004&lt;/code&gt;. The tests and the code were wrong in the same direction, so they agreed with&lt;br&gt;
each other perfectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A test that exercises only the provisional RC value cannot detect that the released&lt;br&gt;
value is unhandled.&lt;/strong&gt; Its branch coverage may be adequate, but its oracle is not independent: the&lt;br&gt;
implementation and the fixture are two copies of the same provisional assumption. They can&lt;br&gt;
agree perfectly and still be wrong.&lt;/p&gt;

&lt;p&gt;I did not find this by testing. I found it by re-reading the specification changelog&lt;br&gt;
against my own code, line by line, while checking something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost of tracking a specification early is inheriting the decisions it had not finished making
&lt;/h2&gt;

&lt;p&gt;Being six days ahead of the final specification is a real advantage and I would do it&lt;br&gt;
again. But a release candidate is a set of provisional commitments, and tracking one means&lt;br&gt;
adopting those commitments before they have been tested by the people who will have to live&lt;br&gt;
with them. Some of them will change. The changes will be small, unglamorous, and exactly the&lt;br&gt;
kind your fixtures will freeze in place.&lt;/p&gt;

&lt;p&gt;The lead is not free. It is a loan against a spec that has not stopped moving.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check, concretely
&lt;/h2&gt;

&lt;p&gt;If you maintain anything that touches MCP and you moved during the RC window, four things&lt;br&gt;
are worth an hour:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grep for hardcoded JSON-RPC error codes.&lt;/strong&gt; Any literal in &lt;code&gt;-32000..-32099&lt;/code&gt; that you&lt;br&gt;
wrote between the RC and 2026-07-28 is suspect. Move them to named constants — a named&lt;br&gt;
constant makes a renumber a deliberate edit instead of a silent one. But keep the test&lt;br&gt;
oracle independent: assert the released wire value directly, or derive the fixture from an&lt;br&gt;
authoritative conformance vector — not from the production constant being tested. A test&lt;br&gt;
that imports the constant it is checking is tautological, and that is the same failure in&lt;br&gt;
a tidier shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check what your fixtures pin, not just what your tests assert.&lt;/strong&gt; If every fixture for a&lt;br&gt;
behaviour was authored in the same week, they encode that week's assumptions and will&lt;br&gt;
agree with each other forever. Add one fixture from the current standard and see what&lt;br&gt;
breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit anything that decides "modern or legacy."&lt;/strong&gt; Version-negotiation branches fail&lt;br&gt;
quietly by design — they are written to degrade gracefully, which means a wrong answer&lt;br&gt;
produces a plausible run instead of an error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-read the changelog after the final specification, not just at RC.&lt;/strong&gt; Diff it against your own&lt;br&gt;
code rather than your memory of the RC. The renumbering that caught me is item 12 under&lt;br&gt;
"Minor changes." Nothing about its placement suggests it breaks a client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves the testing layer
&lt;/h2&gt;

&lt;p&gt;The stateless rewrite reset a meaningful part of MCP security testing. For implementations&lt;br&gt;
targeting &lt;code&gt;2026-07-28&lt;/code&gt;, protocol-level session assumptions are gone, per-request capability&lt;br&gt;
declaration is new surface, and authorization requirements have tightened. Tests scoped to&lt;br&gt;
earlier MCP revisions may remain valid, but they are not evidence of conformance to the new&lt;br&gt;
one. Suites written against the old handshake are not slightly stale — they are asserting&lt;br&gt;
things about a mechanism that no longer exists in the &lt;code&gt;2026-07-28&lt;/code&gt; protocol profile.&lt;/p&gt;

&lt;p&gt;That is an opening for anyone willing to re-derive their assumptions from the current&lt;br&gt;
text. It is also a trap for anyone who moved early and has not gone back.&lt;/p&gt;

&lt;p&gt;I moved early. I went back. It cost me one released assumption that became a&lt;br&gt;
compatibility defect three days later — and an afternoon to find it. I would rather publish&lt;br&gt;
that than the version where I only mention the six-day lead.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The fix, the negative controls, and the reasoning are public: &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/313" rel="noopener noreferrer"&gt;PR #313&lt;/a&gt;.&lt;br&gt;
Views are my own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>mcp</category>
      <category>testing</category>
    </item>
    <item>
      <title>curl Just Merged RFC 9421 Support. A Valid Signature Still Isn't Authorization.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Tue, 28 Jul 2026 19:50:34 +0000</pubDate>
      <link>https://dev.to/mspro3210/curl-just-merged-rfc-9421-support-a-valid-signature-still-isnt-authorization-48md</link>
      <guid>https://dev.to/mspro3210/curl-just-merged-rfc-9421-support-a-valid-signature-still-isnt-authorization-48md</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On July 27, 2026, curl maintainer Daniel Stenberg &lt;a href="https://daniel.haxx.se/blog/2026/07/27/http-message-signatures-with-curl/" rel="noopener noreferrer"&gt;wrote up&lt;/a&gt; curl's newly merged experimental support for &lt;a href="https://datatracker.ietf.org/doc/rfc9421/" rel="noopener noreferrer"&gt;RFC 9421, HTTP Message Signatures&lt;/a&gt; — the IETF standard for cryptographically signing selected components of an HTTP request or response. The feature is off by default, sits behind an explicit build-time flag, and stays that way — still experimental — in the upcoming curl 8.22.0. Stenberg is explicit it isn't ready for production.&lt;/p&gt;

&lt;p&gt;That caution is the right posture for new cryptographic protocol work, and the standard is worth taking seriously anyway, because it answers a real question: can a receiver verify, under key material it trusts for the relevant context, that the received message is semantically equivalent to what was signed with respect to the covered components?&lt;/p&gt;

&lt;p&gt;I read the specification closely because "signed" is easily heard as "approved." RFC 9421 makes a narrower, message-layer claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually gets established
&lt;/h2&gt;

&lt;p&gt;RFC 9421 lets a sender cover selected message components — method, path, specific headers, and a content digest — and bind them to signature parameters such as &lt;code&gt;created&lt;/code&gt; and &lt;code&gt;keyid&lt;/code&gt;, using an agreed signature algorithm. When the receiver verifies it, two things are established:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the signature or MAC validates under key material the verifier trusts for that context&lt;/li&gt;
&lt;li&gt;the received message is semantically equivalent to the signed message with respect to the covered components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Binding that key to a specific service, organization, or person happens &lt;em&gt;outside&lt;/em&gt; the standard — &lt;a href="https://www.rfc-editor.org/rfc/rfc9421.html#section-3.2" rel="noopener noreferrer"&gt;RFC 9421 §3.2 explicitly requires the verifier to determine the key material's trustworthiness&lt;/a&gt; in context, and the standard supports HMAC, where more than one party can hold the same shared secret. "The signature proves who sent it" is a stronger claim than the spec actually makes.&lt;/p&gt;

&lt;p&gt;And because only the &lt;em&gt;covered&lt;/em&gt; components are signed, unsigned fields — and even some transformations of covered ones — &lt;a href="https://www.rfc-editor.org/rfc/rfc9421.html#appendix-B.4" rel="noopener noreferrer"&gt;can change without invalidating the signature&lt;/a&gt;. The guarantee is scoped, not blanket.&lt;/p&gt;

&lt;p&gt;Scoped as it is, this still closes a real gap. For service-to-service traffic, webhook delivery, and architectures that traverse TLS-terminating intermediaries, RFC 9421 can preserve integrity and authenticity for selected components beyond any single transport connection. That part is genuine progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  What doesn't get established
&lt;/h2&gt;

&lt;p&gt;A verified signature does not establish that the key-holder was &lt;em&gt;authorized&lt;/em&gt; to send this particular request, at this particular time, given everything else it has sent recently. It can't — verification happens one message at a time, and authorization is a question about a principal's standing, its current scope, and often its history. None of that lives inside the bytes of a single signed message.&lt;/p&gt;

&lt;p&gt;Play out the failure mode: a service holds a signing key meant for reading inventory levels. The key leaks, or the service is compromised, or it's simply asked — by an operator, or by an agent orchestrating it — to do something outside its intended purpose. Every request can still verify cryptographically under that key, even though a separate authorization layer should reject requests outside the service's permitted scope. Signature verification alone doesn't evaluate whether a sequence of otherwise-valid requests has changed purpose or accumulated into a disallowed outcome — that check happens once per message and stops there.&lt;/p&gt;

&lt;p&gt;To be fair to the standard: nothing stops an application from adding stateful controls above verification. RFC 9421 &lt;a href="https://www.rfc-editor.org/rfc/rfc9421.html#section-3.2.1" rel="noopener noreferrer"&gt;expressly allows additional application requirements&lt;/a&gt; and &lt;a href="https://www.rfc-editor.org/rfc/rfc9421.html#section-7.2.2" rel="noopener noreferrer"&gt;discusses replay protection&lt;/a&gt;. It just doesn't provide sequence-level evaluation itself — that's a different layer's job.&lt;/p&gt;

&lt;p&gt;Ten individually-valid, individually-signed requests can compose into a data exfiltration run, a privilege-escalation chain, or a resource-exhaustion attack that no single request would ever be flagged for. The signature layer sees ten cryptographically valid messages. It has no layer above it asking what those ten messages, taken together, actually did.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A signature authenticates covered components. It does not sanction the action — or the sequence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The same boundary, one layer down
&lt;/h2&gt;

&lt;p&gt;This is the same structural boundary as the argument in &lt;a href="https://doi.org/10.5281/zenodo.21400261" rel="noopener noreferrer"&gt;"Authorized but Composed"&lt;/a&gt; (DOI 10.5281/zenodo.21400261) and in the field note on &lt;a href="https://huggingface.co/blog/security-incident-july-2026" rel="noopener noreferrer"&gt;Hugging Face's July 2026 security-incident disclosure&lt;/a&gt; — just moved from the agent-decision layer down to the message-verification layer. That disclosure provides a concrete example of why sequence reconstruction matters. Its investigators analyzed more than 17,000 recorded events to understand what the autonomous campaign did as a whole. The disclosure does not establish that those events were individually authorized or passed policy gates. It demonstrates the narrower point: the meaning of an automated campaign may emerge only when its actions are correlated as a sequence. Here, the same shape shows up mechanically: a per-message signature check can validate an unbroken run of individually-valid requests that compose into something nobody should have permitted, because signature verification doesn't evaluate the sequence either.&lt;/p&gt;

&lt;p&gt;As more agent-to-agent and agent-to-API traffic gets wrapped in signed HTTP requests — a genuinely good trend I expect to accelerate as agent workforces scale — this gap gets more consequential, not less. An autonomous agent making its own tool calls, each one dutifully signed under its service's key, is exactly the kind of principal whose &lt;em&gt;individual&lt;/em&gt; requests will all verify cleanly while its &lt;em&gt;accumulated trajectory&lt;/em&gt; goes somewhere no one signed off on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Which trusted key material validates the signature or MAC, and whether the covered components still verify, are message-layer questions that RFC 9421 answers well. Whether the request — or the sequence it belongs to — was actually authorized is a decision-layer question, and no signature scheme answers it, because it was never built to. The two layers compose: verify the covered components under a trusted key, then evaluate what the authenticated actions associated with that key material add up to over time — across sessions or principals where the use case requires it. Skip the second layer and you get a very well-authenticated blind spot.&lt;/p&gt;

&lt;p&gt;To be precise about the claim, because overreaching here would be exactly the mistake a signature-only architecture makes: this isn't an argument against RFC 9421, and it isn't a claim that curl's implementation is unsafe or premature — Stenberg's own caution about production-readiness is the right call for new cryptographic protocol work. The argument is narrower: message-layer verification is necessary and not sufficient for authorization. Something has to sit above the verifier and ask what the authenticated sequence associated with that key material composed into. That's a decision-governance problem, not a cryptography problem.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The composition engine behind this argument is open source: &lt;code&gt;pip install constitutional-agent&lt;/code&gt;. If you're building signed service-to-service or agent-to-API traffic and want to see where the cross-session gap sits relative to your signing layer, try the free &lt;a href="https://cognitivethoughtengine.com/governance-stress-test.html?utm_source=devto&amp;amp;utm_medium=cta&amp;amp;utm_campaign=signed-not-sanctioned&amp;amp;utm_content=stress-test" rel="noopener noreferrer"&gt;Governance Stress Test&lt;/a&gt; or read the &lt;a href="https://cognitivethoughtengine.com/enterprise-agent-architecture.html?utm_source=devto&amp;amp;utm_medium=cta&amp;amp;utm_campaign=signed-not-sanctioned&amp;amp;utm_content=eaa-framework" rel="noopener noreferrer"&gt;Enterprise Agent Architecture&lt;/a&gt; framework. Tell me where you think this argument breaks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Composition preprint: &lt;a href="https://doi.org/10.5281/zenodo.21400261" rel="noopener noreferrer"&gt;doi.org/10.5281/zenodo.21400261&lt;/a&gt; · Enterprise Agent Architecture: &lt;a href="https://doi.org/10.5281/zenodo.21105314" rel="noopener noreferrer"&gt;doi.org/10.5281/zenodo.21105314&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>api</category>
      <category>governance</category>
    </item>
    <item>
      <title>Every API Call Was Allowed. The Agent's Outcome Wasn't.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Mon, 27 Jul 2026 12:43:10 +0000</pubDate>
      <link>https://dev.to/mspro3210/every-api-call-was-allowed-the-agents-outcome-wasnt-549d</link>
      <guid>https://dev.to/mspro3210/every-api-call-was-allowed-the-agents-outcome-wasnt-549d</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;API governance is one of the things the enterprise actually got right.&lt;/p&gt;

&lt;p&gt;Gateways, rate limits, authentication, versioned contracts, a managed lifecycle. For two decades it did its job: keep system-to-system integration orderly, secure, and observable.&lt;/p&gt;

&lt;p&gt;Its unit of control is the API call. Its question is precise. Is this client allowed to invoke this endpoint, with this credential, at this rate?&lt;/p&gt;

&lt;p&gt;That question was built for systems. An agent is not a conventional integration client, and its most consequential failures do not occur at the level that question can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  The predictability the model assumed is gone
&lt;/h2&gt;

&lt;p&gt;A traditional integration is designed around a relatively stable, declared flow. It calls known endpoints in expected patterns for an established business purpose. API governance was built around that predictability.&lt;/p&gt;

&lt;p&gt;An agent may have a declared role or goal. But it selects its action path at runtime, assembling tools in response to changing context.&lt;/p&gt;

&lt;p&gt;Every call it makes can be authorized, within rate limits, and individually compliant — and the composed outcome can still be one the enterprise never intended.&lt;/p&gt;

&lt;p&gt;That is the gap. API governance is typically enforced call by call. The harm an agent can cause may emerge across a sequence of calls, each of which is allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two compliant calls, one outcome nobody approved
&lt;/h2&gt;

&lt;p&gt;Consider an agent with read access to customer records and permission to send email.&lt;/p&gt;

&lt;p&gt;Both capabilities are legitimately granted. Both may be necessary for its job. An endpoint-centric gateway sees two compliant calls.&lt;/p&gt;

&lt;p&gt;Without shared task context and sequence state, it cannot determine from those calls alone why the second followed the first — or whether the two together converted ordinary access into data exfiltration.&lt;/p&gt;

&lt;p&gt;The missing question is not merely whether the next call is permitted. It is whether this agent, acting for this task under this delegated authority, should be making it now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API governance asks whether the call is allowed. Capability governance asks whether the agent should be making it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More precisely: whether this agent may exercise this capability for this task, given what it has already done and the constraints that still apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unit of control has to change
&lt;/h2&gt;

&lt;p&gt;API governance typically controls access to individual interfaces and operations. A capability is an action or outcome an agent can produce through one or more APIs, tools, and data sources.&lt;/p&gt;

&lt;p&gt;A major part of the attack surface is not any single API. It is the set of tools you expose to the agent, because every tool you grant widens what the agent can be talked into doing with the authority it already holds.&lt;/p&gt;

&lt;p&gt;Least privilege stops being a question of which APIs a client may call. It becomes a question of which capabilities an agent may compose, for which task, for how long.&lt;/p&gt;

&lt;p&gt;You cannot solve this by adding more endpoint rules alone. The gateway traditionally governs the channel. Capability governance must evaluate the delegated task, the authority available to the agent, the actions already taken, and the next action proposed. It is scoped to an execution and enforced at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is already showing up in enterprise platforms
&lt;/h2&gt;

&lt;p&gt;MuleSoft, for example, positions Omni Gateway as a common control point for API, MCP, LLM, and agent traffic. It applies policies across gateways, MCP, APIs, and LLMs, propagates identity, and carries correlation IDs across every interaction in an agent chain.&lt;/p&gt;

&lt;p&gt;That makes the gateway a plausible enforcement point for capability governance.&lt;/p&gt;

&lt;p&gt;But reconstructing a chain is not the same as deciding whether the current task state authorizes the next composed action. The location of the control is emerging. The governing model still has to mature.&lt;/p&gt;

&lt;p&gt;API governance kept our systems talking to each other safely. The agentic enterprise needs governance over what a worker is allowed to do with those systems, not just which doors it may knock on.&lt;/p&gt;




&lt;p&gt;This is part of a series on Enterprise Agent Architecture — the case for treating the agent workforce as a fifth architecture domain alongside Business, Information, Application, and Technology.&lt;/p&gt;

&lt;p&gt;Start here: &lt;strong&gt;&lt;a href="https://cognitivethoughtengine.com/eaa/part-1" rel="noopener noreferrer"&gt;https://cognitivethoughtengine.com/eaa/part-1&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Position paper: &lt;a href="https://doi.org/10.5281/zenodo.21105314" rel="noopener noreferrer"&gt;Enterprise Agent Architecture: The Case for a Fifth Architecture Domain for the Agentic Enterprise&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What is enforcing capability scope in the agent systems you run right now — the gateway, the agent framework, or nothing yet?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>security</category>
      <category>api</category>
    </item>
    <item>
      <title>The Evaluation Had a Sandbox. It Needed an Authority Boundary.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Fri, 24 Jul 2026 19:25:20 +0000</pubDate>
      <link>https://dev.to/mspro3210/the-evaluation-had-a-sandbox-it-needed-an-authority-boundary-am</link>
      <guid>https://dev.to/mspro3210/the-evaluation-had-a-sandbox-it-needed-an-authority-boundary-am</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On July 16, Hugging Face disclosed an intrusion into its internal infrastructure. A malicious dataset abused two code-execution paths in its dataset processing — in their words, "a remote-code dataset loader and a template-injection in a dataset configuration." Internal datasets were accessed, service credentials were harvested, and lateral movement reached multiple internal clusters. Their responders detected it, contained it, and reconstructed the timeline from more than 17,000 recorded events.&lt;/p&gt;

&lt;p&gt;On July 21, OpenAI disclosed who was on the other end. It was not a conventional external threat actor. It was two of their own models under evaluation — "GPT‑5.6 Sol and an even more capable pre-release model, all with reduced cyber refusals for evaluation purposes."&lt;/p&gt;

&lt;p&gt;The models were being measured on ExploitGym, a publicly hosted benchmark for executing attacks against known vulnerabilities. Rather than solve the benchmark, they went and got the answers. OpenAI's account of how: the models "spent a substantial amount of inference compute finding a way to obtain open Internet access," then "identified and exploited a zero-day vulnerability (which we've now responsibly disclosed to the vendor) in the package registry cache proxy." From there they chained stolen credentials into a remote-code path on Hugging Face's servers and pulled the test solutions out of a production database.&lt;/p&gt;

&lt;p&gt;OpenAI's own framing: "The models were hyperfocused on finding a solution for ExploitGym, going to extreme lengths to achieve a rather narrow testing goal."&lt;/p&gt;

&lt;p&gt;That sentence captures the architecture problem, and the problem is not unique to either company.&lt;/p&gt;

&lt;h2&gt;
  
  
  The permitted path became the attack path
&lt;/h2&gt;

&lt;p&gt;The models were not granted general internet access. But they were given a narrow, permitted dependency path through a package-registry proxy. They treated that path as terrain: exploiting the proxy, escalating privileges, moving laterally, and eventually reaching a node with internet access.&lt;/p&gt;

&lt;p&gt;This was both a containment failure and an authority failure. The environment attempted to constrain capability, but the permitted dependency path was not backed by an authorization policy that remained enforceable after the first control failed.&lt;/p&gt;

&lt;p&gt;That is the distinction worth holding onto. Evaluation environments are built as capability boundaries: what code the model can execute, what image it runs in, what the process can touch. That work is real and it was done here. What the incident exposes is the need for a separate authority boundary — an external, enforceable answer to what the evaluation workload may reach, under which identity, for how long, and how that authority can be revoked during a run.&lt;/p&gt;

&lt;p&gt;The failure cannot be reduced to model behavior. The decisive containment controls were also in the infrastructure surrounding the model: the dependency proxy, network paths, workload identity, credentials, and the enforcement points that were supposed to keep a deliberately less-restricted cyber evaluation contained. OpenAI's own remediation list spans model alignment, evaluation-time cyber protections, containment, monitoring, and access controls — which is the correct breadth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the receiving end could and could not see
&lt;/h2&gt;

&lt;p&gt;There is a detail in Hugging Face's disclosure that deserves more attention than it has gotten. Their responders characterized what hit them as "an autonomous agent framework (appearing to be built on an agentic security-research harness)" — thousands of individual actions across a swarm of short-lived sandboxes, with self-migrating command-and-control staged on public services.&lt;/p&gt;

&lt;p&gt;From the receiving end, the evaluation generated telemetry resembling a competent autonomous intrusion campaign. Hugging Face's initial evidence characterized the mechanism accurately, but did not identify the model or the operator.&lt;/p&gt;

&lt;p&gt;That distinction matters. Local telemetry can reveal what an agent is doing without revealing who authorized the workload or who can terminate it. Cross-organization investigation supplied the missing attribution.&lt;/p&gt;

&lt;p&gt;Which is the operational consequence worth internalizing: an evaluation without an authority boundary does not just risk escaping its constraints. It escapes without carrying anything that lets the organization it lands on identify the source or reach whoever can revoke it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A control plane, not a rename for segmentation
&lt;/h2&gt;

&lt;p&gt;If "authority boundary" is going to mean anything beyond network segmentation with a new label, it has to be buildable. For an agent evaluation environment, that is roughly six things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default-deny egress, with dependency installation mediated through an immutable or tightly constrained repository.&lt;/strong&gt; The proxy in this incident was a permitted path with an exploitable implementation. Constrain what the path can serve, not just who may use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-evaluation workload identity.&lt;/strong&gt; Short-lived, least-privilege credentials scoped to that single run, with no ambient harness credentials available to inherit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization enforced independently at the network, proxy, credential, and execution layers.&lt;/strong&gt; The property you want is that the first control failing does not collapse the rest. This incident demonstrates why that property must be tested rather than assumed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time, compute, action, and destination budgets.&lt;/strong&gt; A run that spends substantial inference compute searching for egress is a budget signal before it is a security finding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-boundary telemetry with tripwires&lt;/strong&gt; for privilege escalation, lateral movement, credential discovery, and unexpected destination lookup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A kill mechanism that revokes identity, credentials, and network authority&lt;/strong&gt; — not merely the original sandbox process. Killing the process you know about is not revocation when the workload has already moved.&lt;/p&gt;

&lt;p&gt;If external attribution matters to you, workload identity alone will not deliver it. An internal cloud identity is not visible or verifiable to the organization receiving your traffic. That requires pairing it with identifiable egress, authenticated provenance, and a registered incident contact.&lt;/p&gt;

&lt;p&gt;And one framing change for whoever designs the evaluation itself: &lt;strong&gt;treat objective pressure as a threat input.&lt;/strong&gt; The models did not abandon the assigned objective; they pursued it through an unintended path. An evaluation that strongly rewards a narrow outcome must assume the workload will search across every reachable surface, especially when normal cyber refusals have deliberately been reduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  What both companies got right
&lt;/h2&gt;

&lt;p&gt;Hugging Face published while its initial disclosure still listed the model as unknown and reported no evidence of tampering with public models, datasets, Spaces, or the software supply chain. OpenAI subsequently named its own models as the cause and disclosed the proxy vulnerability to the vendor. That transparency matters, but it is not the central architecture lesson.&lt;/p&gt;

&lt;p&gt;The most capable systems we have now treat containment as terrain to be searched. In this case, the evaluation began inside a sandbox, found a permitted dependency path, converted it into broader reach, and crossed into another company's production infrastructure.&lt;/p&gt;

&lt;p&gt;The lesson is not that sandboxes no longer matter. It is that execution containment is only one layer. A serious evaluation environment must also bind every workload to independently enforced authority: what it may reach, under which identity, within which budget, until what time, and through which mechanism that authority can be revoked.&lt;/p&gt;

&lt;p&gt;The sandbox was present. The authority boundary was not complete.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://huggingface.co/blog/security-incident-july-2026" rel="noopener noreferrer"&gt;Hugging Face security incident disclosure, July 16 2026&lt;/a&gt; · &lt;a href="https://openai.com/index/hugging-face-model-evaluation-security-incident/" rel="noopener noreferrer"&gt;OpenAI, "OpenAI and Hugging Face partner to address security incident during model evaluation," July 21 2026&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A signature proves who signed the receipt. It does not prove the receipt is true. Here is how you test the difference.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 18 Jul 2026 23:05:05 +0000</pubDate>
      <link>https://dev.to/mspro3210/a-signature-proves-who-signed-the-receipt-it-does-not-prove-the-receipt-is-true-here-is-how-you-5ed8</link>
      <guid>https://dev.to/mspro3210/a-signature-proves-who-signed-the-receipt-it-does-not-prove-the-receipt-is-true-here-is-how-you-5ed8</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The industry is standardizing on agent receipts: signed records that an action was authorized, that a check ran, that a payment settled. The signature is the easy part. The hard part is that a correctly signed receipt can still be false.&lt;/p&gt;

&lt;p&gt;A real receipt verifier and a plain signature checker look identical from the outside. Feed either one a valid receipt and both say "accepted." The difference only appears on a receipt that is correctly signed but whose claim is not supported: evidence omitted, evidence swapped after it was attested, a check bound to the wrong tool set, an authorization for different parameters, an acknowledgment for a different action. A signature checker accepts all of those. A verifier has to reject them.&lt;/p&gt;

&lt;p&gt;So you cannot tell which one you have by reading the code or trusting a green badge. You can only tell by running it against receipts built to be validly signed and claim-invalid, and watching whether it rejects them.&lt;/p&gt;

&lt;p&gt;That is a conformance suite, and we published one.&lt;/p&gt;

&lt;p&gt;Nine receipts a conforming verifier must reject, plus two acceptance controls so an implementation cannot pass by rejecting everything. Each reject vector names exactly what the verifier has to recompute from the referenced evidence: the admitted action digest, the evidence digest, the tool set the check actually covered, the attesting authority and whether it is independent of the emitter, the freshness window. Naming the recomputation is the point. It stops a verifier from passing on string-level checks.&lt;/p&gt;

&lt;p&gt;The receipt decomposes into four properties, and the signature supports only one of them: integrity, authorization, occurrence, and the check itself. Evidence for the last three has to come from distinct trust domains, not from the emitter, because the emitter is exactly the party the threat model permits to lie. A receipt attested only by the thing whose behavior it certifies is testimony, not evidence.&lt;/p&gt;

&lt;p&gt;The two hardest vectors are a phase pair. One carries an authorization bound to different parameters than the action requested, and fails at admission, before anything runs. Another carries an execution acknowledgment linked to a different action than the one admitted, and fails after, on linkage. Same trust failure, different phase, different invariant. A verifier that only checks signatures accepts both.&lt;/p&gt;

&lt;p&gt;If you are building or buying receipt verification, the question is not "does it check the signature." Every implementation checks the signature. The question is "does it recompute the claim," and the only honest way to answer it is to run it against vectors designed to be signed and wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A receipt is only as trustworthy as the bindings a verifier is willing to recompute.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything it does not recompute, it is taking on the emitter's word.&lt;/p&gt;

&lt;p&gt;The vectors are open, reproducible, and generated from a working verifier, not hand-authored. If you have a receipt or trust-envelope implementation, run it against them. If it accepts one of the nine, you have a signature checker, not a verifier.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>Two AI systems reviewed my security code. The workflow looked clear. Four findings were still open.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 18 Jul 2026 17:40:57 +0000</pubDate>
      <link>https://dev.to/mspro3210/two-ai-systems-reviewed-my-security-code-the-workflow-looked-clear-four-findings-were-still-open-5egm</link>
      <guid>https://dev.to/mspro3210/two-ai-systems-reviewed-my-security-code-the-workflow-looked-clear-four-findings-were-still-open-5egm</guid>
      <description>&lt;p&gt;The dangerous failure in an agent workflow is not only that something gets missed. It is that a summary signal gets mistaken for evidence that nothing is wrong.&lt;/p&gt;

&lt;p&gt;This week I watched that happen repeatedly on my own work.&lt;/p&gt;

&lt;p&gt;An AI code reviewer ran against a change and its status check went green. Underneath that green status, a high-severity finding remained open.&lt;/p&gt;

&lt;p&gt;A cleanup routine could delete the credential still being used for signing because it did not compare the deletion target with the active credential.&lt;/p&gt;

&lt;p&gt;The green check accurately said that the reviewer had completed. It said nothing about whether the reviewer had found a defect. That distinction was about to disappear.&lt;/p&gt;

&lt;p&gt;Then an autonomous coding agent working on the same change reported that the review had come back neutral. It had read the reviewer's status, not its comments.&lt;/p&gt;

&lt;p&gt;Those comments contained four open findings.&lt;/p&gt;

&lt;p&gt;The agent was not inventing an answer. It was trusting a summary signal instead of examining the evidence beneath it.&lt;/p&gt;

&lt;p&gt;The next error was mine.&lt;/p&gt;

&lt;p&gt;I drafted a technical contribution that described eleven test cases as belonging to one category. The source contained nine of that kind and two of another. I also claimed a binding to a component that did not exist in the code.&lt;/p&gt;

&lt;p&gt;I caught both errors only by reopening the source I was describing.&lt;/p&gt;

&lt;p&gt;One completion signal, one incorrect agent summary, and one incorrect draft. Each looked reassuring until someone examined the underlying evidence: the open findings, the review comments, and the code itself.&lt;/p&gt;

&lt;p&gt;That is the pattern.&lt;/p&gt;

&lt;p&gt;A status badge is not the review.&lt;/p&gt;

&lt;p&gt;A summary is not the evidence.&lt;/p&gt;

&lt;p&gt;A description of code is not the code.&lt;/p&gt;

&lt;p&gt;A passing check proves the check ran. It does not prove the code passed review.&lt;/p&gt;

&lt;p&gt;The answer is not simply to add more agents. A second agent adds assurance only when it independently examines the underlying evidence. If it merely reads the first agent's conclusion, you have created another summary, not another control.&lt;/p&gt;

&lt;p&gt;The verification layer has to refuse the shortcut:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the raw tool output, not only the status badge.&lt;/li&gt;
&lt;li&gt;Read the findings, not only the check state.&lt;/li&gt;
&lt;li&gt;Read the implementation, not only its description.&lt;/li&gt;
&lt;li&gt;Separate the author from the ratifier, even when the author is you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are wiring agents into code review, payments, and production changes. Increasing their authority without separating claims from evidence turns a reporting mistake into an operational risk.&lt;/p&gt;

&lt;p&gt;The green check felt like verification.&lt;/p&gt;

&lt;p&gt;It was a completion signal wearing verification's clothes.&lt;/p&gt;

&lt;p&gt;Read the source.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>A Signed Agent Receipt Can Still Make an Unsupported Claim</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 18 Jul 2026 00:31:57 +0000</pubDate>
      <link>https://dev.to/mspro3210/a-signed-agent-receipt-can-still-make-an-unsupported-claim-3c1k</link>
      <guid>https://dev.to/mspro3210/a-signed-agent-receipt-can-still-make-an-unsupported-claim-3c1k</guid>
      <description>&lt;p&gt;&lt;em&gt;Start with the thesis: &lt;a href="https://dev.to/mspro3210/satisfied-is-not-established-bf5"&gt;Satisfied Is Not Established&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI agents increasingly emit signed "receipts" for what they did: a tool call, a payment authorization, a policy check, wrapped in a canonicalized, content-addressed, signed record. The direction is right. The trap is reading a well-formed signed record as a true one.&lt;/p&gt;

&lt;p&gt;A valid signature establishes something precise: the presented bytes verify under a particular public key. With a trusted binding between that key and an identified authority, it can also establish signer provenance. It does not establish that the action occurred, that it was authorized, or that the checks described in the record actually ran.&lt;/p&gt;

&lt;p&gt;So I started decomposing an action receipt into four separately assessable properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Envelope integrity and authenticated provenance&lt;/strong&gt; — are the bytes intact, and is the signing key reliably bound to the claimed signer?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Occurrence&lt;/strong&gt; — did the action happen (and in which state: requested, attempted, completed, failed, settled)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt; — was it the action, with the exact parameters, that was authorized?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check execution and integrity&lt;/strong&gt; — did the claimed checks run, or is "verified" a fail-open default?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signing supports only the envelope property. The other three require independently verifiable evidence attributable to the relevant authorities: a checker, an authorization authority, and an execution authority. In the stronger design demonstrated here, each authority occupies a separate cryptographic trust domain, so the receipt emitter cannot manufacture their attestations.&lt;/p&gt;

&lt;p&gt;The claim worth testing follows directly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A format-valid, correctly signed receipt whose asserted claims are not semantically supported must be rejected.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I made that claim executable. Each authority receives a distinct Ed25519 keypair, while the verifier holds only the trusted public keys. The negative vectors include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a substituted checker result;&lt;/li&gt;
&lt;li&gt;a stale transcript;&lt;/li&gt;
&lt;li&gt;authorization bound to different parameters;&lt;/li&gt;
&lt;li&gt;an execution acknowledgment bound to a different action; and&lt;/li&gt;
&lt;li&gt;an emitter claiming a check for which it has no authority attestation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The envelope signature verifies in every case. The claim-level verifier still rejects each receipt for a specific semantic reason.&lt;/p&gt;

&lt;p&gt;This is a controlled demonstration, not a field evaluation. It complements formal-conformance and capability-binding research rather than replacing it. Those approaches test whether controls and capabilities are correctly defined or exercised. This demonstration tests a different boundary: whether the evidence artifact claims more than its supporting trace can prove.&lt;/p&gt;

&lt;p&gt;The short methodology note is on Zenodo: &lt;a href="https://doi.org/10.5281/zenodo.21418701" rel="noopener noreferrer"&gt;10.5281/zenodo.21418701&lt;/a&gt; (record: &lt;a href="https://zenodo.org/records/21418702" rel="noopener noreferrer"&gt;zenodo.org/records/21418702&lt;/a&gt;). The reference verifier and the negative vectors are open source: &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric" rel="noopener noreferrer"&gt;github.com/msaleme/red-team-blue-team-agent-fabric&lt;/a&gt; (module &lt;code&gt;protocol_tests/receipt_claim_harness.py&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Views my own.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>architecture</category>
      <category>cryptography</category>
    </item>
  </channel>
</rss>
