<?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>A correct subtraction is not an explanation</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sun, 08 Nov 2026 19:19:44 +0000</pubDate>
      <link>https://dev.to/mspro3210/a-correct-subtraction-is-not-an-explanation-j5f</link>
      <guid>https://dev.to/mspro3210/a-correct-subtraction-is-not-an-explanation-j5f</guid>
      <description>&lt;p&gt;&lt;em&gt;A synthetic, reproducible example by Michael K. Saleme. Not a live integration, independent validation or vendor comparison.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An agent sees $1.2 million in contract bookings and $900,000 in recognized revenue. It subtracts correctly and then says the $300,000 difference is caused by unfulfilled performance obligations.&lt;/p&gt;

&lt;p&gt;The arithmetic did not establish that explanation.&lt;/p&gt;

&lt;p&gt;That distinction is the point of this small semantic decision testbed. It separates three questions: which metric did you mean, which read plan matches that definition, and what evidence supports explaining the difference?&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the distinction locally
&lt;/h2&gt;

&lt;p&gt;Use Python 3.10 or later. No account, credentials, model endpoint or third-party packages are needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/msaleme/salesforce-governed-ontology.git
&lt;span class="nb"&gt;cd &lt;/span&gt;salesforce-governed-ontology
git checkout 9747a4810b5312a62dce75b9b75bbce5a9e1f4a8
git rev-parse HEAD
python3 explore.py scenario ambiguity
python3 explore.py scenario binding-mismatch
python3 explore.py scenario difference-only
python3 explore.py scenario explained
python3 evaluate.py
python3 scripts/verify.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands were verified at the pinned merged commit above. Record that commit when reporting a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ambiguity:&lt;/strong&gt; “revenue” produces &lt;code&gt;clarification_required&lt;/code&gt;, offering bookings and recognized revenue rather than silently choosing one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binding mismatch:&lt;/strong&gt; a recognized-revenue definition pointed at the bookings fixture produces &lt;code&gt;binding_mismatch&lt;/code&gt;. A known source is not necessarily the right source for a metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difference only:&lt;/strong&gt; the aligned cohort produces &lt;code&gt;difference_only&lt;/code&gt;, &lt;code&gt;currency: USD&lt;/code&gt; and &lt;code&gt;difference_minor: 30000000&lt;/code&gt;. Amounts are integer minor units, so that value is $300,000. There is no explanation field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explained:&lt;/strong&gt; a separately supplied bridge with matching dimensions and amount produces &lt;code&gt;explained_by_fixture_bridge&lt;/code&gt;. The result includes the bridge's source and stated reason. That bridge is synthetic. The program does not authenticate it or prove the accounting policy is correct.&lt;/p&gt;

&lt;p&gt;A correct subtraction is not an explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why another semantic example?
&lt;/h2&gt;

&lt;p&gt;Existing semantic layers already define metrics and generate queries. Operational ontology platforms connect business objects to actions. Knowledge-graph systems support graph interchange and inference. Reimplementing those capabilities would not make this example stronger.&lt;/p&gt;

&lt;p&gt;The narrower contribution is an inspectable acceptance test: make the decision and its evidence easy to challenge. The &lt;a href="https://github.com/msaleme/salesforce-governed-ontology/blob/9747a4810b5312a62dce75b9b75bbce5a9e1f4a8/docs/COMPARISON.md" rel="noopener noreferrer"&gt;comparison&lt;/a&gt; distinguishes these categories and documents where to reuse existing platforms.&lt;/p&gt;

&lt;p&gt;The intended role is complementary. Keep authoritative metric definitions in the platform that owns them. Eventually, reproduce these acceptance cases against that native model instead of introducing a second source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  A passing label can hide a failing answer
&lt;/h2&gt;

&lt;p&gt;An evaluator that checks only &lt;code&gt;difference_only&lt;/code&gt; can pass a result with the wrong amount. It can also miss an invented explanation appended to that result.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/msaleme/salesforce-governed-ontology/blob/9747a4810b5312a62dce75b9b75bbce5a9e1f4a8/docs/EVALUATION.md" rel="noopener noreferrer"&gt;fixed decision contract&lt;/a&gt; now checks complete result objects: source, operation, amount, currency, evidence markers and unexpected fields. Mutation tests deliberately keep the status correct while corrupting important payload fields. The evaluator must reject those results.&lt;/p&gt;

&lt;p&gt;This is regression evidence, not a vendor leaderboard or model accuracy score. The expected cases were authored with the prototype. They are not held out. Exact fixture agreement does not establish production correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next useful contribution is a counterexample
&lt;/h2&gt;

&lt;p&gt;Pick a case where this decision rule is insufficient. Could a matching bridge still describe the wrong accounting treatment? Could a metric mapping preserve its name but change its grain? What should happen when a lifecycle date is confused with the data period?&lt;/p&gt;

&lt;p&gt;Use the repository's &lt;a href="https://github.com/msaleme/salesforce-governed-ontology/issues/new?template=decision-counterexample.yml" rel="noopener noreferrer"&gt;counterexample issue form&lt;/a&gt;. Include the commit, command, minimal synthetic input, actual result and the expected decision with a reason. A conceptual gap is welcome; distinguish it from an executable reproduction.&lt;/p&gt;

&lt;p&gt;Do not include customer data, tokens or private metadata. One reproducible disagreement is more useful than a general endorsement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What remains unproven
&lt;/h2&gt;

&lt;p&gt;No live metric execution, native-platform interoperability, authenticated authorization, independent reproduction or general accounting validation is demonstrated here. The &lt;a href="https://github.com/msaleme/salesforce-governed-ontology/blob/9747a4810b5312a62dce75b9b75bbce5a9e1f4a8/docs/COMPARISON.md" rel="noopener noreferrer"&gt;gap ledger&lt;/a&gt; keeps those boundaries explicit. This is a reference prototype, not an official vendor product; see &lt;a href="https://github.com/msaleme/salesforce-governed-ontology/blob/9747a4810b5312a62dce75b9b75bbce5a9e1f4a8/LICENSE-STATUS.md" rel="noopener noreferrer"&gt;license status&lt;/a&gt; for the unresolved distribution terms.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>data</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your x402 tests may be passing against a server that never reads the payment</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 31 Oct 2026 18:24:49 +0000</pubDate>
      <link>https://dev.to/mspro3210/my-x402-tests-passed-against-a-server-that-accepted-nothing-g1b</link>
      <guid>https://dev.to/mspro3210/my-x402-tests-passed-against-a-server-that-accepted-nothing-g1b</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;Payment-protocol test suites are usually written while the protocol is still moving. The protocol ships, its wire format settles, and the suite keeps speaking the draft. Every check still runs. Every check still returns a verdict.&lt;/p&gt;

&lt;p&gt;The way to find out is to point the suite at a server that cannot be paid, and count the passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement
&lt;/h2&gt;

&lt;p&gt;The Agent Security Harness carries 54 tests for x402, the HTTP 402 payment protocol for agents. This week I ran them against a minimal server that follows the published x402 v2 HTTP transport: every request without a payment gets a &lt;code&gt;402&lt;/code&gt; and a base64 &lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; header describing how to pay. The server has no facilitator. It accepts no payment, ever.&lt;/p&gt;

&lt;p&gt;The result: &lt;strong&gt;43 of 54 PASS, 11 FAIL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Neither number was evidence. X4-045 reported "Replay correctly handled (status 402, 402)". X4-046 reported "At most 1 settlement accepted ([402, 402]) — safe". The server never read a payment, so there was nothing to replay and nothing to settle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;The probes were written against a pre-spec header format. They read the challenge from &lt;code&gt;X-Payment-Amount&lt;/code&gt; and &lt;code&gt;X-Payment-Recipient&lt;/code&gt;, and they send a forged payment as &lt;code&gt;X-Payment-Authorization&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The published transport does neither. Version 1 puts the challenge in a JSON body and takes the payment in &lt;code&gt;X-PAYMENT&lt;/code&gt; (&lt;a href="https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md" rel="noopener noreferrer"&gt;spec&lt;/a&gt;). Version 2 puts it in a &lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; header and takes the payment in &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; (&lt;a href="https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md" rel="noopener noreferrer"&gt;spec&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A spec server ignores the forged header and answers every probe with the &lt;code&gt;402&lt;/code&gt; it sends to everyone. The test sees a non-200 status and records the attack as rejected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 402 the server would have sent anyway is not a rejection.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The failures were wrong in the mirror direction. X4-001 failed the server for missing &lt;code&gt;x-payment-*&lt;/code&gt; headers, which the spec does not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the usual guards stay quiet
&lt;/h2&gt;

&lt;p&gt;The harness already refuses a verdict when the target never answers: a closed port, a host returning &lt;code&gt;404&lt;/code&gt; to everything, an empty &lt;code&gt;2xx&lt;/code&gt;. Each of those shapes has a guard and a test that holds it.&lt;/p&gt;

&lt;p&gt;This shape passes all of them. The target answered, correctly, in a protocol the probes did not speak. Any guard that asks "did the target respond?" stays quiet, because it did. The only instrument that catches it is a target that follows the spec and cannot be paid.&lt;/p&gt;

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

&lt;p&gt;An outside review flagged the format mismatch. The measurement and the fix shipped the same day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/672" rel="noopener noreferrer"&gt;PR #672&lt;/a&gt; teaches the module to recognise a spec challenge. Against a spec target, five tests keep a verdict, because their evidence is the server's own response: the challenge is well-formed, the payee is stable, two leak scans, and behaviour under load. The other 49 report INCONCLUSIVE and say why.&lt;/p&gt;

&lt;p&gt;Against the same server, the run now reads &lt;strong&gt;5 PASS, 49 INCONCLUSIVE.&lt;/strong&gt; A smaller number, and one a reader can rely on.&lt;/p&gt;

&lt;p&gt;A guard test runs the suite against spec v1 and v2 targets and fails if any of the 49 returns a verdict. It also checks that X4-001 can still fail a spec challenge with no payee.&lt;/p&gt;

&lt;p&gt;The next step is probes that speak the spec. Each needs a correctly signed payment the server must accept, as the positive control, before any rejection means anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the same check on your suite
&lt;/h2&gt;

&lt;p&gt;Stand up a server that returns the spec challenge and accepts nothing. Run your payment tests against it. Every PASS on a test that claims to exercise a payment is a test that never reached one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Michael K. Saleme maintains the &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric" rel="noopener noreferrer"&gt;Agent Security Harness&lt;/a&gt;. The counts come from runs on 30 October 2026: before the fix against harness &lt;code&gt;main&lt;/code&gt; at &lt;code&gt;4d7528c&lt;/code&gt;, after it against the fix merged as &lt;code&gt;76ef629&lt;/code&gt;. The guard is &lt;code&gt;testing/test_x402_spec_target.py&lt;/code&gt;, and the CHANGELOG entry records the correction.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>testing</category>
      <category>payments</category>
    </item>
    <item>
      <title>Who approved this? Several agents, one account</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 24 Oct 2026 12:41:39 +0000</pubDate>
      <link>https://dev.to/mspro3210/who-approved-this-several-agents-one-account-59eo</link>
      <guid>https://dev.to/mspro3210/who-approved-this-several-agents-one-account-59eo</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;Pull request #666 on my repository was a small docs fix. An agent session opened it, CI passed, and an automated reviewer, Cursor Bugbot, posted a summary in the PR body.&lt;/p&gt;

&lt;p&gt;When the agent tried to merge, a safety classifier in its own runtime stopped it, because the change had no human review. I told the agent, in a chat window, to run the command. It merged.&lt;/p&gt;

&lt;p&gt;GitHub records that &lt;code&gt;msaleme&lt;/code&gt; opened the PR and &lt;code&gt;msaleme&lt;/code&gt; merged it. There is no formal review on it. The block and my instruction happened, but neither is in that record. Both exist only in a session transcript on my laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The record knows which account acted. It does not know who decided.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The study
&lt;/h2&gt;

&lt;p&gt;A paper published this week measures this gap where it matters most, inside an approval gate.&lt;/p&gt;

&lt;p&gt;Adam Manji reports twelve weeks of agent-driven development at a fintech startup preparing for a SOC 2 audit (&lt;a href="https://arxiv.org/abs/2610.28683" rel="noopener noreferrer"&gt;arXiv 2610.28683&lt;/a&gt;). From 3 October, every non-bot merge passed a non-author review gate. Yet "71 of those 263 merges (27%) rested only on an approval that a coding agent had submitted through the technical lead's GitHub account, and the platform recorded each one as an ordinary approval by the lead."&lt;/p&gt;

&lt;p&gt;Each of those merges satisfied the gate, and the record showed an approval. What it could not show was who made the decision.&lt;/p&gt;

&lt;p&gt;The paper's lesson: "Record who decided, not only who acted." And the part that is easy to skip: "A decision record the agent can write itself is not independent evidence."&lt;/p&gt;

&lt;h2&gt;
  
  
  My repository has the earlier version of the problem
&lt;/h2&gt;

&lt;p&gt;My case is different, and the difference matters. Manji's team had recorded approvals whose decision-maker was uncertain. My repository has no recorded approvals at all, and its attribution is uncertain.&lt;/p&gt;

&lt;p&gt;The Agent Security Harness has one maintainer and several agents. I pulled its merge history from the GitHub API on 23 October.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;477 pull requests merged since March. The account &lt;code&gt;msaleme&lt;/code&gt; opened and merged 474 of them; three came from outside contributors.&lt;/li&gt;
&lt;li&gt;274 of those 474 carry a "Generated with Claude Code" footer or a Claude co-author line. The agent writes that footer itself.&lt;/li&gt;
&lt;li&gt;200 carry no footer. That establishes neither human nor agent authorship. They are unclassified.&lt;/li&gt;
&lt;li&gt;None of the 474 has an approving review. An automated reviewer, Cursor Bugbot, left 131 comment reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The missing reviews are not a lapse. GitHub does not let an author approve their own pull request, and every agent and I act as the same author. A shared account removes the possibility of a recorded second decision, before any gate is configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assumption I got wrong
&lt;/h2&gt;

&lt;p&gt;Yesterday another of my agents filed an issue on the repo. I identified which one from a single field, &lt;code&gt;performed_via_github_app&lt;/code&gt;, which said Cursor. I wrote that down as a rule: every agent posts as me, and the app field is how to tell them apart.&lt;/p&gt;

&lt;p&gt;Then I counted. Of 598 issues and pull requests created as &lt;code&gt;msaleme&lt;/code&gt;, the field names an app on 18. On the other 580 it provides no app attribution at all.&lt;/p&gt;

&lt;p&gt;The rule held for the one case I checked. It does not tell anything apart across the record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;Manji's recommendations transfer, and the paper is precise about their limit: on GitHub, the permission that lets an identity open pull requests also lets it submit reviews. Separate identities matter only if the rule that counts approvals relies on them. So I changed both the identity and the rule, the same day.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The agent has its own identity.&lt;/strong&gt; Claude Code sessions now act through a GitHub App, &lt;code&gt;msaleme-claude-agent&lt;/code&gt;, using installation tokens. Activity is attributed to &lt;code&gt;msaleme-claude-agent[bot]&lt;/code&gt;, not to me. A user token would have attributed it to both of us, which recreates the problem. The tokens are minted per command and scoped to this one repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent commits and pushes as itself.&lt;/strong&gt; Its commits are authored by the bot and left unsigned on purpose, because signing them with my key would have my key vouch for work I did not write. Its pushes go through the App.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The decision comes through a channel the agent cannot use.&lt;/strong&gt; A ruleset on &lt;code&gt;main&lt;/code&gt; requires a pull request and one approval from a code owner, with no bypass for anyone, me included. Any new push dismisses an earlier approval, so the approval always covers the exact revision that merges. &lt;code&gt;CODEOWNERS&lt;/code&gt; lists only people. The App can comment on its own pull request, or even approve it, but that approval does not count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent cannot quietly route around it.&lt;/strong&gt; A hook in the agent's runtime refuses &lt;code&gt;gh pr create&lt;/code&gt;, &lt;code&gt;gh pr merge&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt; and &lt;code&gt;git push&lt;/code&gt; against this repository unless they go through the App. I tested it by asking the agent to merge as me, and the hook refused.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first pull request under the new rules is &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/pull/667" rel="noopener noreferrer"&gt;#667&lt;/a&gt;. The agent committed it, pushed it, opened it and queued it for auto-merge. I approved it from my own account. The App then executed the merge, which the rule allowed only after my approval of that revision. The record reads: author, the bot; approver, me; merger, the bot. Who acted and who decided are now different identities, and the record says which is which.&lt;/p&gt;

&lt;p&gt;GitHub's record has a seam of its own. I had also required approval from someone other than the last pusher. The merge box refused: it named me as the last pusher, while the repository's activity log records the push as the bot's. I dropped that rule rather than leave every agent pull request unmergeable. Dismissing approvals on any new push still forces my approval onto the code that merges.&lt;/p&gt;

&lt;p&gt;Two limits remain, and both are visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Other agents still act as me.&lt;/strong&gt; They are listed in &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/668" rel="noopener noreferrer"&gt;#668&lt;/a&gt;. Until each has its own App, the ruleset leaves their pull requests unable to merge, which is the intended effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I am still the repository's admin.&lt;/strong&gt; I can edit the ruleset, and I did, twice, while setting this up. GitHub keeps the ruleset's history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this proves that I read every diff. It makes the record claim no more than it can show: which agent acted, and that a person holding an identity no agent holds approved the exact revision that merged.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Michael K. Saleme maintains the &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric" rel="noopener noreferrer"&gt;Agent Security Harness&lt;/a&gt;. The counts in this post come from the GitHub API on 23 October 2026; the queries, with what each number does and does not establish, are in &lt;a href="https://gist.github.com/msaleme/eafaa3a8b3608b891eb53e5853c99501" rel="noopener noreferrer"&gt;this gist&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>github</category>
      <category>agents</category>
    </item>
    <item>
      <title>A signed mandate is not an enforced one</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Thu, 22 Oct 2026 11:06:24 +0000</pubDate>
      <link>https://dev.to/mspro3210/a-signed-mandate-is-not-an-enforced-one-4b5m</link>
      <guid>https://dev.to/mspro3210/a-signed-mandate-is-not-an-enforced-one-4b5m</guid>
      <description>&lt;p&gt;Card networks are standardizing the record of what a person allowed an agent to buy.&lt;/p&gt;

&lt;p&gt;This pattern exists in public specifications now. For example, Mastercard's Verifiable Intent, published with Google in March as an open draft (v0.1, Apache-2.0), describes its job as "cryptographic proof that an agent acted within delegated authority" (&lt;a href="https://github.com/agent-intent/verifiable-intent/blob/356c296/spec/security-model.md" rel="noopener noreferrer"&gt;security model, §2.4&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In its autonomous flow, where the user sets limits and is not present at purchase time, the record is a signed chain of three layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First layer:&lt;/strong&gt; the issuer, a bank or payment network, vouches for the user's key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second layer:&lt;/strong&gt; the user signs a mandate binding the agent's key, with limits: which merchants, which items, what amount, how often.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third layer:&lt;/strong&gt; the agent presents the final checkout and payment, signed with the key the user delegated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spec is also explicit about what the chain cannot do alone (&lt;a href="https://github.com/agent-intent/verifiable-intent/blob/356c296/spec/security-model.md" rel="noopener noreferrer"&gt;§4.2&lt;/a&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Transaction count and budget enforcement cannot be done cryptographically within VI alone — they require stateful tracking by the payment network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is the most important one in the spec, and it deserves credit for being written down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;A signed record proves what was allowed. It does not, by itself, stop anything.&lt;/p&gt;

&lt;p&gt;Some properties live in the signature: the constraints the user set, the agent key they bound, the checkout the payment refers to. Others live in a party's behaviour:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a verifier that remembers which nonces it has seen;&lt;/li&gt;
&lt;li&gt;a verifier that checks the credential was addressed to it;&lt;/li&gt;
&lt;li&gt;a network that counts how many payments one mandate has produced and how much it has spent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are properties of whoever receives the credential, not of the credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reference verifier returned
&lt;/h2&gt;

&lt;p&gt;I probed the reference implementation at commit &lt;code&gt;356c296&lt;/code&gt;, offline. I built chains with the library's own issuance API and changed one thing per case. I exercised seven of the &lt;a href="https://github.com/agent-intent/verifiable-intent/blob/356c296/spec/security-model.md" rel="noopener noreferrer"&gt;nine attack categories&lt;/a&gt; the spec lists: replay and audience, count and amount, constraint stripping, checkout-payment mismatch, algorithm confusion, clock skew, and split agent. I did not exercise disclosure withholding or type confusion. Code, raw results and reproduction steps: &lt;a href="https://github.com/msaleme/approval-binding-vectors/tree/main/research/verifiable-intent-probe" rel="noopener noreferrer"&gt;approval-binding-vectors/research/verifiable-intent-probe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The cryptography held:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a mandate re-signed by another key was rejected;&lt;/li&gt;
&lt;li&gt;so was a credential with its signature stripped and &lt;code&gt;alg&lt;/code&gt; set to &lt;code&gt;none&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;so was a mandate that bound two different agent keys;&lt;/li&gt;
&lt;li&gt;so was a payment that pointed at a different checkout, when one verifier held both halves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expiry is policy, not cryptography. An expired credential was accepted inside the default 300-second clock-skew tolerance, which the spec recommends, and rejected once the verifier tightened skew to 60 seconds.&lt;/p&gt;

&lt;p&gt;The other checks returned what the reference API's separation of checks predicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audience is a parameter.&lt;/strong&gt; Called the way the repository's own integration tests call it, &lt;code&gt;verify_chain&lt;/code&gt; returned &lt;code&gt;valid=True&lt;/code&gt; for a payment credential addressed to another party. It checks the audience when the caller supplies the expected value, and otherwise lists the check under &lt;code&gt;checks_skipped&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits are a second call.&lt;/strong&gt; For a payment above the mandate's cap, &lt;code&gt;verify_chain&lt;/code&gt; returned &lt;code&gt;valid=True&lt;/code&gt;. A separate &lt;code&gt;check_constraints&lt;/code&gt; call rejected it. Whether the API should expose a combined verdict is what &lt;a href="https://github.com/agent-intent/verifiable-intent/issues/38" rel="noopener noreferrer"&gt;upstream issue #38&lt;/a&gt; asks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay and counting are assigned elsewhere.&lt;/strong&gt; The same payment credential returned &lt;code&gt;valid=True&lt;/code&gt; twice, and so did a second payment from the same mandate. The spec assigns both to the verifier's nonce store and the network's issuance log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A careful integrator reads that contract. A hurried one reads &lt;code&gt;valid: true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two halves, two owners
&lt;/h2&gt;

&lt;p&gt;In the autonomous flow the agent splits its final credential in two. The payment network receives the payment half. The merchant receives the checkout half. The spec states the consequence plainly: "Each party verifies its own L3 independently" (&lt;a href="https://github.com/agent-intent/verifiable-intent/blob/356c296/spec/README.md" rel="noopener noreferrer"&gt;spec overview, §8.4&lt;/a&gt;). The comparison between the two references happens "when a verifier has access to both L3 credentials (e.g., during dispute resolution)."&lt;/p&gt;

&lt;p&gt;I changed only the payment half's reference, so it matched no checkout. The network-side verifier returned &lt;code&gt;valid=True&lt;/code&gt;, and so did the merchant-side verifier. Neither is wrong. Each checked what it holds, and neither holds what it would need to compare.&lt;/p&gt;

&lt;p&gt;That moves the question out of the verifier and into the deployment. Which component holds both halves, and does its comparison run before the payment is authorized or the order is fulfilled, or only afterwards, in a dispute? My probe shows the limits of a partial view. It does not show how any deployed payment flow answers that question, and that is the evidence worth asking for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A signed mandate proves what the agent was allowed to do. Adversarial tests show whether the deployed system rejects the violations you exercise.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to test
&lt;/h2&gt;

&lt;p&gt;If you integrate a mandate chain like this, as a merchant, a network, a wallet or an agent platform, the spec tells you what to check. A test tells you whether your deployment did, for the cases you ran.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Negative controls against your running system, not the library.&lt;/strong&gt; Each must be refused by the system you deploy:

&lt;ul&gt;
&lt;li&gt;a replayed credential;&lt;/li&gt;
&lt;li&gt;a second payment from a single-use mandate;&lt;/li&gt;
&lt;li&gt;cumulative spend across merchants over the budget;&lt;/li&gt;
&lt;li&gt;an amount over the cap;&lt;/li&gt;
&lt;li&gt;a credential addressed to someone else.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A positive control.&lt;/strong&gt; A valid chain must still pass. A system that refuses everything also passes every negative test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declare where the cross-reference runs, then test it there.&lt;/strong&gt; Name the component that compares the two halves and when it runs. Present a mismatched pair to that component and see whether the purchase stops at the point you declared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequences, not single calls.&lt;/strong&gt; Count and budget are state. The test is the second and third payment, not the first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore, then pay again.&lt;/strong&gt; If the network's issuance log can be rolled back with the rest of the system, a single-use mandate can be used twice. Checkpoint before use, authorize the first payment, restore, then attempt a second payment under the same single-use mandate. Give the second payment a fresh nonce, so you are testing mandate-use tracking rather than ordinary replay, and keep every credential inside its validity window. Count the authorizations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A contributor has published cross-implementation vectors and linked them in &lt;a href="https://github.com/agent-intent/verifiable-intent/issues/38" rel="noopener noreferrer"&gt;upstream issue #38&lt;/a&gt;. What I have not found is a public set of deployment tests for role-specific decisions and stateful enforcement. I opened a proposal for one in the open-source harness I maintain: &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/664" rel="noopener noreferrer"&gt;#664&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The record is getting standardized fast. Enforcement still lives with each party that receives it, and it has to be tested there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>payments</category>
      <category>agents</category>
    </item>
    <item>
      <title>Instructions Are Not Boundaries</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sat, 17 Oct 2026 20:17:10 +0000</pubDate>
      <link>https://dev.to/mspro3210/instructions-are-not-boundaries-1252</link>
      <guid>https://dev.to/mspro3210/instructions-are-not-boundaries-1252</guid>
      <description>&lt;p&gt;On 9 October, Anthropic published a report on actions its models took during evaluations and internal use that it did not intend. One of the four categories it lists: "Claude submitting a sensitive form on a real website when it should not have."&lt;/p&gt;

&lt;p&gt;In one case, Claude Haiku 4.5 "was instructed to complete a form up until the final submission stage, but to stop before actually submitting. Several times Claude mistakenly submitted the form instead, expecting there to be an additional confirmation page."&lt;/p&gt;

&lt;p&gt;In another, a run working through randomly selected webpages reached a police department's tip form. "Claude was instructed never to log in, create accounts, enter personal data, make purchases, or submit anything destructive, but the instructions did not rule out form submissions." The model submitted an invented tip. Per the report, it "was flagged as spam and was never forwarded for investigation."&lt;/p&gt;

&lt;p&gt;Anthropic disclosed this voluntarily, described the impact as minimal, notified the organizations involved, and corrected the post three days later. That disclosure is what lets the rest of us learn from it, and it deserves plain credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Any deployment that relies on prompt instructions alone leaves enforcement dependent on the model's interpretation.&lt;/p&gt;

&lt;p&gt;An instruction travels with the task. The model reads it, weighs it against the goal, and interprets it. When the goal and the rule pull apart, interpretation decides what executes.&lt;/p&gt;

&lt;p&gt;Anthropic names the dominant mechanism. Most of the cases are forms of persistence, "in which Claude, when it cannot complete a task as given, works around a restriction instead of stopping."&lt;/p&gt;

&lt;p&gt;The form cases show it is not the only one. Anthropic describes the Haiku case as a mistaken expectation of another confirmation page. That illustrates a different failure: misjudging when an action becomes final.&lt;/p&gt;

&lt;p&gt;These cases expose different failures: misunderstanding when an action becomes final, acting outside an incompletely specified scope, and finding another route around a restriction.&lt;/p&gt;

&lt;p&gt;The tip-form case shows the second. A list of prohibited actions can leave consequential actions unspecified. That one named five things, and "did not rule out form submissions."&lt;/p&gt;

&lt;p&gt;Anthropic is fair about the counterargument: some of these failures "could have been avoided if the evaluation questions had more clearly stated what was in and out of scope." It answers it in the next breath. Claude meets ambiguous and impossible tasks every day in real use.&lt;/p&gt;

&lt;p&gt;Clearer instructions narrow the gap. They do not close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the enforcement moved
&lt;/h2&gt;

&lt;p&gt;Read the remediation section as an architecture diagram.&lt;/p&gt;

&lt;p&gt;Anthropic is extending its suspension of live internet access to all internal evaluations until it verifies the relevant safeguards.&lt;/p&gt;

&lt;p&gt;Internet access tools such as web fetch get tighter guardrails on what the model can do with them.&lt;/p&gt;

&lt;p&gt;New tooling detects and blocks these behaviors. It "now runs on most of our evaluations and on internal agentic use of frontier models," and "When we tested it against the cases described in this post, it blocked all of them."&lt;/p&gt;

&lt;p&gt;Internal agents move to centrally managed infrastructure with strong containment, and far more of what agents do is monitored.&lt;/p&gt;

&lt;p&gt;None of these is a sentence addressed to the model. Each one acts outside it: containment, monitoring, and restrictions on the tools themselves.&lt;/p&gt;

&lt;p&gt;Training stays in the plan. Anthropic is direct about its limits: "alignment training is not yet sufficient or fully robust on its own, at least in the short term, so we also rely on defense-in-depth approaches." Defense in depth only works when the outer layer holds whether or not the inner one does.&lt;/p&gt;

&lt;p&gt;One case sharpens where "outside" has to be. The fetch tool limits URL length "to reduce the risk from Claude using long URLs to take certain unwanted actions, such as exfiltrating larger amounts of data." Several models routed around it "by using free URL shortening services."&lt;/p&gt;

&lt;p&gt;That limit sat outside the model and still did not hold, because it constrained one tool's argument rather than the action. Placement outside the agent is necessary, and it is not sufficient. A check at the point of execution can still be bypassable, misconfigured, or aimed at the wrong property.&lt;/p&gt;

&lt;p&gt;An effective boundary must mediate every available route to the protected action and enforce the relevant policy before the action takes effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rule the agent is told is not a rule the system enforces.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What an outside test looks like
&lt;/h2&gt;

&lt;p&gt;If enforcement lives outside the agent, the evidence has to come from outside too: an observation of what executed, not the agent's account of what it did. Anthropic makes the adjacent point itself: "a model's own account of its reasoning is not necessarily reliable evidence of its beliefs or reasons for action."&lt;/p&gt;

&lt;p&gt;Moving a rule out of the prompt and into the runtime raises the next question: does the runtime actually enforce what it was told to deny?&lt;/p&gt;

&lt;p&gt;The open-source &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric" rel="noopener noreferrer"&gt;Agent Security Harness&lt;/a&gt; has one test of exactly that shape, &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/blob/6b3859eb484301e9223aecb6f299de75a71cf210/protocol_tests/approval_binding_harness.py#L231" rel="noopener noreferrer"&gt;&lt;code&gt;APB-003 | Declared Deny Rule Enforced at Execution&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Declare a deny rule for one call to the runtime.&lt;/li&gt;
&lt;li&gt;Approve an allowed call, and approve the denied call too, so a missing approval cannot be the reason it does not run.&lt;/li&gt;
&lt;li&gt;Make both calls, and diff the runtime's execution record around each one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The oracle is what executed. A refusal message never counts. The allowed call is the positive control: if nothing executes, the result is INCONCLUSIVE, because a runtime that runs nothing looks identical to one that enforces.&lt;/p&gt;

&lt;p&gt;The oracle is only as good as the record. For a real deployment, the execution record must reliably capture the relevant side effect. An absent entry in an incomplete log is not evidence that an action was blocked.&lt;/p&gt;

&lt;p&gt;Output against the module's own reference targets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BOUND              PASS          the allowed call executed and the denied call did not; it was approved and still blocked at execution
POLICY_UNENFORCED  FAIL          POLICY NOT ENFORCED: a call matching a declared deny rule executed (GHSA-5r6c-gj4g-r697 shape)
REFUSES_ALL        INCONCLUSIVE  the denied call did not execute, but neither did the approved, allowed one, so nothing distinguishes enforcement from a target that runs nothing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The limits matter as much as the shape.&lt;/p&gt;

&lt;p&gt;APB-003 tests a deny rule declared to the runtime, not an instruction in a prompt. Against Anthropic's cases it is adjacent, not a match.&lt;/p&gt;

&lt;p&gt;It needs a caller-written adapter that maps approvals and executions onto a real runtime, and a wrong adapter produces a wrong verdict. The repository evidence reviewed for this post contains no real-runtime validation. The output above comes from reference targets the module itself defines, and says nothing about any real system.&lt;/p&gt;

&lt;p&gt;It matches the denied call exactly. It would not catch the URL-shortener route: an equivalent action reached through a different tool.&lt;/p&gt;

&lt;p&gt;APB-003 asks a narrower question: does an explicit runtime deny still hold when approval is present? Test it with an allowed positive control, then inspect execution evidence. Extend that testing to alternative routes before claiming the action is contained.&lt;/p&gt;

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

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your agent asked for approval once. What did it actually approve?</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:07:34 +0000</pubDate>
      <link>https://dev.to/mspro3210/your-agent-asked-for-approval-once-what-did-it-actually-approve-4cpd</link>
      <guid>https://dev.to/mspro3210/your-agent-asked-for-approval-once-what-did-it-actually-approve-4cpd</guid>
      <description>&lt;p&gt;Agent runtimes put a human in front of dangerous tools. Before the agent writes a file, deletes data, or runs a command, someone has to say yes.&lt;/p&gt;

&lt;p&gt;Asking on every call does not survive contact with real work. People stop reading the prompt and start clicking through it. So runtimes remember approvals.&lt;/p&gt;

&lt;p&gt;That is a reasonable design. It hides a question most teams never write down: &lt;strong&gt;what is the approval remembered as?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are three common answers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tool name.&lt;/li&gt;
&lt;li&gt;The tool and its arguments.&lt;/li&gt;
&lt;li&gt;A digest of the exact call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the last two connect what executes to what a person actually saw.&lt;/p&gt;

&lt;p&gt;They are two ways of writing down the same binding. Enforcing an exact-call binding at dispatch prevents argument substitution at that boundary. It does not keep the call's downstream effects inside what was approved, and that needs separate controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like when the answer is "the tool name"
&lt;/h2&gt;

&lt;p&gt;This pattern exists in shipping agent frameworks. For example, in PraisonAI: &lt;a href="https://github.com/advisories/GHSA-29r9-67vg-qj56" rel="noopener noreferrer"&gt;CVE-2026-60087&lt;/a&gt;, published by the maintainer in June and added to GitHub's Advisory Database on 8 October.&lt;/p&gt;

&lt;p&gt;The approval cache was "keyed on tool name only — arguments are not part of the key." It lasted for the whole run, and child tasks inherited it. Critical-tier tools such as &lt;code&gt;execute_command&lt;/code&gt; and &lt;code&gt;execute_code&lt;/code&gt; were exempt from the cache. File writes were not.&lt;/p&gt;

&lt;p&gt;The advisory's proof of concept is two calls. A person approves &lt;code&gt;write_file&lt;/code&gt; to &lt;code&gt;/tmp/safe.txt&lt;/code&gt;. A second &lt;code&gt;write_file&lt;/code&gt;, to &lt;code&gt;/etc/crontab&lt;/code&gt;, is then approved without another prompt. The runtime records the reason as &lt;code&gt;"Already approved in context"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The person approved writing a scratch file. The runtime recorded approval of writing files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An approval that does not name its arguments authorizes a tool, not an action.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A second failure: declared policy without enforcement
&lt;/h2&gt;

&lt;p&gt;The maintainer published a related advisory alongside it, &lt;a href="https://github.com/advisories/GHSA-5r6c-gj4g-r697" rel="noopener noreferrer"&gt;GHSA-5r6c-gj4g-r697&lt;/a&gt;. &lt;code&gt;SecurityPolicy.strict()&lt;/code&gt;, documented as "a strict security policy for untrusted code," was enforced by the default subprocess sandbox only for network access and output size. Its command, path and subprocess restrictions were never checked.&lt;/p&gt;

&lt;p&gt;These are two different failures with one lesson: declared controls must constrain what actually executes.&lt;/p&gt;

&lt;p&gt;Both are fixed, in &lt;code&gt;praisonaiagents&lt;/code&gt; 1.6.78 and &lt;code&gt;praisonai&lt;/code&gt; 4.6.78. If you ran an affected version with approval prompts, upgrading closes the door going forward. It does not tell you what already went through it. Identify the tool calls that executed after the first approval in each run, and review the ones whose arguments no person saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a binding approval commits to
&lt;/h2&gt;

&lt;p&gt;Two recent preprints use the term "approval laundering" for failures in this family, with different definitions (&lt;a href="https://arxiv.org/abs/2609.38983" rel="noopener noreferrer"&gt;Wang, arXiv 2609.38983&lt;/a&gt;; &lt;a href="https://arxiv.org/abs/2609.28586" rel="noopener noreferrer"&gt;Zhang et al., arXiv 2609.28586&lt;/a&gt;). The property underneath it can be stated as a check on a record.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/msaleme/approval-binding-vectors/tree/ed1ae0c7cde55a7a6b3378b2c7a120cba13b259f" rel="noopener noreferrer"&gt;Approval Binding Vectors&lt;/a&gt; corpus (ABV v0.1.3) states it as predicates. Two matter here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P2, arguments:&lt;/strong&gt; "The approval's scope commits to the executed argument bytes."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P6, single use:&lt;/strong&gt; one approval authorises at most one execution. ABV's own spec calls this "a profile choice, not a universal requirement." Some systems legitimately want standing approvals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PraisonAI behavior conflicts with P2. Under a single-use contract, it also conflicts with P6.&lt;/p&gt;

&lt;p&gt;The reference checker rejects records with exactly this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 check.py
  PASS  NEG-P2-01    P2: executed arguments are not the approved arguments
  PASS  NEG-P6-01    P6: one approval, 2 executions
  ...
15/15 vectors behaved as specified
acceptance controls: 4 accepted (a run with 0 is not a result, regardless of the negatives)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PASS&lt;/code&gt; means the checker correctly rejected an invalid record. It says nothing about PraisonAI. &lt;a href="https://github.com/msaleme/approval-binding-vectors/blob/ed1ae0c7cde55a7a6b3378b2c7a120cba13b259f/vectors/NEG-P2-01.json" rel="noopener noreferrer"&gt;NEG-P2-01&lt;/a&gt; approves &lt;code&gt;deploy.scale&lt;/code&gt; with &lt;code&gt;replicas: 3&lt;/code&gt; and executes it with &lt;code&gt;replicas: 300&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The limit matters. A record check needs trustworthy evidence linking request, approval and execution. Without that evidence, nobody can verify these properties after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it against a live runtime
&lt;/h2&gt;

&lt;p&gt;A record check is not a runtime test. In the &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/tree/2a529b51fea5a68842619382a4aa866b71091e23" rel="noopener noreferrer"&gt;Agent Security Harness&lt;/a&gt; at the referenced commit, related tests exist: a denied decision replayed, a forged approval, sandbox escape. None of them approves one argument set and executes another, or checks that a declared policy is enforced. That gap is filed as &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/643" rel="noopener noreferrer"&gt;#643&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The proposed tests observe execution, not refusal messages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Argument binding, for call-specific approvals.&lt;/strong&gt; Approve tool T with arguments X, then call T with arguments Y. Pass only if Y does not execute before a fresh approval. (An approval that explicitly grants a range of arguments can legitimately cover both.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single use, where the profile requires it.&lt;/strong&gt; Approve X, then call X twice. Pass only if the second call does not execute without a fresh approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declared-policy enforcement.&lt;/strong&gt; Declare a deny rule, then attempt the denied operation. Pass only if the prohibited operation does not occur.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each has a positive control: the approved call, or the allowed operation, must actually complete. Otherwise a runtime that refuses everything would pass.&lt;/p&gt;

&lt;p&gt;And each needs evidence that the test reached the enforcement point. An operation that was never attempted is not a pass. It is inconclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question to ask this week
&lt;/h2&gt;

&lt;p&gt;Find the place where your agent runtime remembers an approval.&lt;/p&gt;

&lt;p&gt;Read the cache key.&lt;/p&gt;

&lt;p&gt;If it is a tool name, and nothing downstream re-checks the arguments, a person approving one call is signing for every argument the agent chooses for as long as that cached approval remains valid.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Advisory GHSA-29r9-67vg-qj56 (CVE-2026-60087), PraisonAI approval cache: &lt;a href="https://github.com/advisories/GHSA-29r9-67vg-qj56" rel="noopener noreferrer"&gt;https://github.com/advisories/GHSA-29r9-67vg-qj56&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Advisory GHSA-5r6c-gj4g-r697 (CVE-2026-60085), PraisonAI SecurityPolicy enforcement: &lt;a href="https://github.com/advisories/GHSA-5r6c-gj4g-r697" rel="noopener noreferrer"&gt;https://github.com/advisories/GHSA-5r6c-gj4g-r697&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Approval Binding Vectors v0.1.3, commit ed1ae0c: &lt;a href="https://github.com/msaleme/approval-binding-vectors/tree/ed1ae0c7cde55a7a6b3378b2c7a120cba13b259f" rel="noopener noreferrer"&gt;https://github.com/msaleme/approval-binding-vectors/tree/ed1ae0c7cde55a7a6b3378b2c7a120cba13b259f&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agent Security Harness gap issue #643: &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/643" rel="noopener noreferrer"&gt;https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/643&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wang, "Approval Laundering: Systematizing Approval–Execution Binding Failures in AI Coding-Agent Harnesses," arXiv 2609.38983: &lt;a href="https://arxiv.org/abs/2609.38983" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2609.38983&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Zhang et al., "Agent Approval Laundering: Transitive Effects Beyond the Approved Invocation," arXiv 2609.28586: &lt;a href="https://arxiv.org/abs/2609.28586" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2609.28586&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your security gate always passes. Can it actually block a release?</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Fri, 02 Oct 2026 22:11:52 +0000</pubDate>
      <link>https://dev.to/mspro3210/your-security-gate-always-passes-can-it-actually-block-a-release-522n</link>
      <guid>https://dev.to/mspro3210/your-security-gate-always-passes-can-it-actually-block-a-release-522n</guid>
      <description>&lt;p&gt;Agent developers have started publishing oversight numbers.&lt;/p&gt;

&lt;p&gt;For example, Anthropic reports that of more than a billion decisions by its research and engineering agents in August 2026, about 1 in 47,000 were blocked by an online monitor.&lt;/p&gt;

&lt;p&gt;A block rate tells you how often the gate said no. It does not tell you how often the gate should have said no and did not.&lt;/p&gt;

&lt;p&gt;The same publication points toward a harder measurement: "Developers should share how often known agent misbehavior is caught by monitors, and should have third parties do this kind of testing independently."&lt;/p&gt;

&lt;p&gt;Detection on known cases is not a count of every real-world miss. But it is the part you can measure, and it requires feeding the monitor misbehavior on purpose and watching whether it fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a quiet miss looks like
&lt;/h2&gt;

&lt;p&gt;Controls can fail quietly, in ways a block rate alone would not reveal.&lt;/p&gt;

&lt;p&gt;For example, in OpenAI's report on a September 20 training incident, an agent found that its sandbox's DNS resolver could reach the public internet, and used it to send questions to an external chatbot.&lt;/p&gt;

&lt;p&gt;The monitoring worked in part. It flagged the behavior, and a person acknowledged the alert three minutes after it fired. The report is candid about what did not work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The run "did not stop automatically as expected." It was stopped by hand two and a half hours later.&lt;/li&gt;
&lt;li&gt;"An infrastructure detector for anomalous DNS activity excluded the affected environment." The detector existed. It was not on the path it was meant to guard.&lt;/li&gt;
&lt;li&gt;A retrospective review found other cases of external DNS access that the monitor had not flagged at the expected severity. It "sometimes treated the failure to obtain useful information as evidence that the attempt to access the internet had failed."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A block rate alone would not establish any of those gaps. They show up when someone checks whether each control acts on the path that matters.&lt;/p&gt;

&lt;p&gt;Your own pipeline has the same shape, at a smaller scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one line that decides
&lt;/h2&gt;

&lt;p&gt;Most security pipelines end with one line that decides whether the build ships.&lt;/p&gt;

&lt;p&gt;A scanner writes a report. A small script reads it, counts the findings above some severity, and exits non-zero if the count is too high. Everything upstream (the tests, the payloads, the coverage mapping) reaches the release decision through that one count.&lt;/p&gt;

&lt;p&gt;That script is easy to leave untested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two contracts that nobody reconciles
&lt;/h2&gt;

&lt;p&gt;The scanner has an output contract: field names, status values, severity spellings. The gate has an input contract: the fields it looks for and the values it compares against.&lt;/p&gt;

&lt;p&gt;They are often written by different people, at different times, sometimes in different repositories. Nothing forces them to agree.&lt;/p&gt;

&lt;p&gt;Without schema validation, contract drift can fail silently, and in the worst direction. A gate that looks for &lt;code&gt;result == "failed"&lt;/code&gt; against a report that now writes &lt;code&gt;"FAILED"&lt;/code&gt; counts zero failures. A gate that compares a &lt;code&gt;severity&lt;/code&gt; string against a report that switched to a numeric CVSS score also counts zero.&lt;/p&gt;

&lt;p&gt;The build is green. The dashboard is green. The gate has stopped being a control and become a formality.&lt;/p&gt;

&lt;p&gt;No error is raised, because nothing is wrong with the syntax. The gate does exactly what it was written to do, against a report it was not written for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Green is not evidence
&lt;/h2&gt;

&lt;p&gt;A gate that has only ever passed tells you one of two things: the system is clean, or the gate cannot see. From the outside, those look identical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Until you have seen a gate block a known violation, you have not shown that it can.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix is not more scanning. It is proving that the last line can say no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three checks that make a gate falsifiable
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Seed a known finding through the real writer.&lt;/strong&gt; Feed the gate a report, produced by the scanner's own writer, that contains one critical finding. The gate must exit non-zero. Run this in CI on every change to either side.&lt;/p&gt;

&lt;p&gt;This tests the writer-to-gate contract. It does not test whether the scanner detects anything, or whether the pipeline honours the gate's exit code. Those need their own checks. But if someone renames a field in the scanner, this one breaks the same day, not the day an incident review asks why nothing blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Share one classifier, and pin its answers.&lt;/strong&gt; If the scanner's summary and the gate each decide what counts as a failure, they can drift apart. Put the classification (pass, fail, inconclusive) in one function that both call, so there is only one definition to drift.&lt;/p&gt;

&lt;p&gt;One definition can still be consistently wrong. Pin its expected outcomes with test cases written independently of the classifier itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Give "could not tell" its own state.&lt;/strong&gt; When a response does not show that the capability or control under test was exercised, it is not a pass, and it is not a fail. It is inconclusive.&lt;/p&gt;

&lt;p&gt;That is a statement about the assessment, not about release policy. A team can reasonably block a release because required evidence is missing, while the report still says inconclusive rather than fail. What goes wrong is folding the two together: counting inconclusive as pass ships things nobody tested, and relabelling it as fail hides why the build is red.&lt;/p&gt;

&lt;h2&gt;
  
  
  A check on the checks
&lt;/h2&gt;

&lt;p&gt;The same reasoning applies one level down. A security test that needs a live application response, and returns PASS against a server that is not there, has not tested anything.&lt;/p&gt;

&lt;p&gt;Run every test against a few targets that give it nothing to judge: a closed port, a server that answers 404 to everything, a server that answers 403 to everything, a server that answers with an empty body.&lt;/p&gt;

&lt;p&gt;For most tests, none of those responses shows that the capability under test was exercised, so the honest verdict is inconclusive. Some tests are different. For an access-control check a 403 can be the evidence. For a network-isolation check a closed port can be part of it, but only alongside evidence that the service is meant to be listening, so isolation is not confused with a service that is simply down. Those should be declared exceptions, each tied to the specific assertion it satisfies. Any other PASS or FAIL there is a test that is not reading its target.&lt;/p&gt;

&lt;p&gt;In the Agent Security Harness, as of v4.26.1, both checks run in CI. The GitHub Action's gate and the harness summaries share one row classifier. A test feeds the Action's own gate step a report from the MCP harness's writer with one critical failure and requires a non-zero exit.&lt;/p&gt;

&lt;p&gt;A separate guard runs every URL-taking harness against nine such targets, from a closed port and a 404-everywhere server to empty 200s, empty 500s and redirect loops. It fails the build on any verdict there outside a short, documented exception list. The guard also proves it can fire: a seeded target-blind harness must turn it red.&lt;/p&gt;

&lt;p&gt;That guard's claim has been reproduced externally, against the published v4.26.0 package, by an outside party using their own targets and result parser. They classify it as external execution of the published harness, not an independent implementation: &lt;a href="https://github.com/VrtxOmega/veritas-agent-trust-lab/blob/cb061dc9930cee456250ecab71b16d5e977f073d/evidence/ASH_NO_SURFACE_RETEST_20260926.md" rel="noopener noreferrer"&gt;https://github.com/VrtxOmega/veritas-agent-trust-lab/blob/cb061dc9930cee456250ecab71b16d5e977f073d/evidence/ASH_NO_SURFACE_RETEST_20260926.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try to break it yourself: &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/discussions/619" rel="noopener noreferrer"&gt;https://github.com/msaleme/red-team-blue-team-agent-fabric/discussions/619&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The question to ask this week
&lt;/h2&gt;

&lt;p&gt;Pick the one line in your pipeline that decides whether security findings block a release.&lt;/p&gt;

&lt;p&gt;Then ask: when did it last block on purpose?&lt;/p&gt;

&lt;p&gt;If the answer is never, that is the first test to write.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, "Measurements for understanding the pace of AI development inside frontier labs": &lt;a href="https://www.anthropic.com/institute/measuring-pace-of-ai-development" rel="noopener noreferrer"&gt;https://www.anthropic.com/institute/measuring-pace-of-ai-development&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI, "An agent used DNS to reach an external chatbot" (report updated 25 September 2026): &lt;a href="https://alignment.openai.com/misalignment-reports/an-agent-used-dns-to-reach-an-external-chatbot/" rel="noopener noreferrer"&gt;https://alignment.openai.com/misalignment-reports/an-agent-used-dns-to-reach-an-external-chatbot/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agent Security Harness v4.26.1 changelog: &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/blob/v4.26.1/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/msaleme/red-team-blue-team-agent-fabric/blob/v4.26.1/CHANGELOG.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your agent has a valid token. Should this call still be blocked?</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Thu, 24 Sep 2026 14:34:34 +0000</pubDate>
      <link>https://dev.to/mspro3210/your-agent-has-a-valid-token-should-this-call-still-be-blocked-2k09</link>
      <guid>https://dev.to/mspro3210/your-agent-has-a-valid-token-should-this-call-still-be-blocked-2k09</guid>
      <description>&lt;p&gt;Agent identity is being standardized quickly.&lt;/p&gt;

&lt;p&gt;On August 24, Okta made Agent SSO generally available. In Okta's words, when an agent that supports&lt;br&gt;
Cross App Access connects to an enterprise application, Okta &lt;em&gt;"registers it as a first-class&lt;br&gt;
identity in Universal Directory alongside human employees, then issues short-lived,&lt;br&gt;
identity-governed tokens in place of stored credentials."&lt;/em&gt; The same announcement says Cross App&lt;br&gt;
Access &lt;em&gt;"is formally incorporated as the official Enterprise-Managed Authorization extension for&lt;br&gt;
the Model Context Protocol."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The IETF is moving too, though this is work in progress, not finished standards. An individual&lt;br&gt;
draft on AI agent authentication and authorization, dated July 6, has since been replaced by&lt;br&gt;
&lt;em&gt;AI Identity Management System&lt;/em&gt; (&lt;code&gt;draft-ietf-wimse-aims-00&lt;/code&gt;, September 15), now a working-group&lt;br&gt;
draft in WIMSE. Separately, the individual draft &lt;em&gt;Transaction Tokens for Agents&lt;/em&gt; carries the agent&lt;br&gt;
and the principal it acts for through a call chain, including &lt;em&gt;"chain-level metadata required for&lt;br&gt;
multi-agent flow integrity,"&lt;/em&gt; so that services can &lt;em&gt;"make more granular access control decisions."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And fine-grained authorization is not new: OAuth 2.0 Rich Authorization Requests (RFC 9396,&lt;br&gt;
2023) supports detailed authorization requests and makes the granted details available to&lt;br&gt;
resource servers, through token claims or introspection.&lt;/p&gt;

&lt;p&gt;This is real progress. Static API keys shared across agents were indefensible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the new layer does, and what it hands to you
&lt;/h2&gt;

&lt;p&gt;These mechanisms settle &lt;strong&gt;who&lt;/strong&gt; the agent is, &lt;strong&gt;on whose behalf&lt;/strong&gt; it acts, and &lt;strong&gt;what it has&lt;br&gt;
been granted&lt;/strong&gt;, and they carry that context, with constraints, to the service it calls.&lt;/p&gt;

&lt;p&gt;What they cannot do on their own is check those constraints against the request that actually&lt;br&gt;
arrives, in the state the system is actually in. That check happens in your deployment, at the&lt;br&gt;
moment of the call, or it does not happen.&lt;/p&gt;

&lt;p&gt;A narrative review of 89 sources posted to arXiv on September 14 (&lt;em&gt;Authorization Architectures for&lt;br&gt;
Tool-Using AI Agents&lt;/em&gt;) reaches a similar conclusion. The authors find the literature gives&lt;br&gt;
&lt;em&gt;"little attention to the authorization decision point itself, the moment a tool invocation&lt;br&gt;
occurs,"&lt;/em&gt; and identify &lt;em&gt;"runtime enforcement and aggregation bounds as the principal unresolved&lt;br&gt;
gaps."&lt;/em&gt; That is their finding from the literature they reviewed, not proof that nobody enforces&lt;br&gt;
anything. But it points at the right place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A token can carry the constraints. Your deployment has to enforce them, and prove they held.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more for agents
&lt;/h2&gt;

&lt;p&gt;For a human user, the space between "authorized to use the app" and "should perform this action"&lt;br&gt;
is filled by the person's judgement and by the application's business rules. An agent changes&lt;br&gt;
three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The arguments are generated.&lt;/strong&gt; A request accompanied by a valid token can contain parameters
shaped by a prompt injection in a document the agent read several steps earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegation can widen.&lt;/strong&gt; Each hop in a multi-agent chain can pass on more authority than its
task needs unless something checks it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actions aggregate.&lt;/strong&gt; Every call can be individually within bounds while the sequence is not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A concrete version of the third: an agent is permitted to issue refunds below $500, against a&lt;br&gt;
delegated budget of $5,000. It submits thirty refunds of $400. Every single call passes the&lt;br&gt;
per-refund rule. The total is $12,000.&lt;/p&gt;

&lt;p&gt;The moment of execution is where all three must be checked, provided the component doing the&lt;br&gt;
checking has trustworthy context and history. A runtime check can see the arguments without&lt;br&gt;
seeing that an injection shaped them, and it can only bound the total if it can see what the&lt;br&gt;
chain has already done and what it has reserved but not yet completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to verify in your deployment
&lt;/h2&gt;

&lt;p&gt;Vendors do not stop at identity. Okta's own announcement describes its broader &lt;em&gt;Okta for AI&lt;br&gt;
Agents&lt;/em&gt; offering as one that &lt;em&gt;"includes agent-to-agent connections and runtime enforcement"&lt;/em&gt;.&lt;br&gt;
So the question is not whether you must build enforcement yourself. It is whether the&lt;br&gt;
enforcement you have, bought or built, covers what your agents actually do: their actions,&lt;br&gt;
arguments, delegation limits and aggregate budgets.&lt;/p&gt;

&lt;p&gt;Adopt the new identity layer. Then verify each of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate the decision from the enforcement.&lt;/strong&gt; A policy decision point evaluates each call
(this action, these arguments, this principal, this context). A policy enforcement point sits
in the execution path and blocks the call when the answer is no. One service can do both, but
the enforcement must be on the path the agent cannot route around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep downstream authority within upstream authority.&lt;/strong&gt; Each hop may hold at most what the hop
above it held, and only what its task needs. It does not have to shrink at every hop. It must
never grow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bound the aggregate, including in flight.&lt;/strong&gt; Track the running total for a delegated chain,
and atomically reserve against the budget before a call executes, not after it completes. Otherwise
several concurrent requests can each see room in the budget and together exceed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove coverage, not just activity.&lt;/strong&gt; A log of refused calls, with the rule that refused each
one, shows that enforcement fired. It does not show that every execution path goes through it.
Test for bypass, and correlate decisions with the effects that actually happened downstream.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The question to ask your architecture
&lt;/h2&gt;

&lt;p&gt;Even when an agent presents a valid, short-lived, identity-governed token, authentication is&lt;br&gt;
only the first question.&lt;/p&gt;

&lt;p&gt;The next question is: when an authenticated agent makes a call it should not make, what stops&lt;br&gt;
it, and where is the evidence?&lt;/p&gt;

&lt;p&gt;If the answer is "the token", look again at what the token carried, and at whether anything&lt;br&gt;
checked it against the call that was actually made.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://www.okta.com/newsroom/press-releases/okta-brings-first-class-identity-to-ai-agents-with-agent-sso/" rel="noopener noreferrer"&gt;Okta, Agent SSO announcement, 24 August 2026&lt;/a&gt;;&lt;br&gt;
&lt;a href="https://www.okta.com/newsroom/articles/cross-app-access-extends-mcp-to-bring-enterprise-grade-security-to-ai-agents/" rel="noopener noreferrer"&gt;Okta, Cross App Access extends MCP&lt;/a&gt;;&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-ietf-wimse-aims/" rel="noopener noreferrer"&gt;draft-ietf-wimse-aims-00&lt;/a&gt; (replaces draft-klrc-aiagent-auth);&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-araut-oauth-transaction-tokens-for-agents/" rel="noopener noreferrer"&gt;draft-araut-oauth-transaction-tokens-for-agents-02&lt;/a&gt;;&lt;br&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc9396" rel="noopener noreferrer"&gt;RFC 9396, OAuth 2.0 Rich Authorization Requests&lt;/a&gt;;&lt;br&gt;
&lt;a href="https://arxiv.org/abs/2609.15906" rel="noopener noreferrer"&gt;Authorization Architectures for Tool-Using AI Agents, arXiv:2609.15906&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>identity</category>
    </item>
    <item>
      <title>What my seventeen AI attack tests missed about the evaluator</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Sun, 20 Sep 2026 14:48:22 +0000</pubDate>
      <link>https://dev.to/mspro3210/what-my-seventeen-ai-attack-tests-missed-about-the-evaluator-3k75</link>
      <guid>https://dev.to/mspro3210/what-my-seventeen-ai-attack-tests-missed-about-the-evaluator-3k75</guid>
      <description>&lt;p&gt;Two threat-actor designations from two Anthropic threat reports. In the first, &lt;strong&gt;Claude Code&lt;/strong&gt;&lt;br&gt;
was the weapon used against other organisations. In the second, &lt;strong&gt;another AI vendor's evaluation&lt;br&gt;
sandbox&lt;/strong&gt; was the target; Anthropic's report states its own systems were not compromised.&lt;/p&gt;

&lt;p&gt;I have a seventeen-test module for the first pattern and nothing in it for the second, and I only&lt;br&gt;
noticed because I went looking to prove the opposite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first one
&lt;/h2&gt;

&lt;p&gt;In September 2025 a likely China-nexus actor manipulated Claude Code into running a multi-stage&lt;br&gt;
intrusion campaign. MITRE tracks it as &lt;a href="https://attack.mitre.org/campaigns/C0062/" rel="noopener noreferrer"&gt;Campaign C0062&lt;/a&gt;, attributing the&lt;br&gt;
campaign to the actor Anthropic designated &lt;strong&gt;GTG-1002&lt;/strong&gt;. The targets were &lt;em&gt;"approximately 30&lt;br&gt;
entities in the technology, financial, chemical, and government sectors."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;MITRE's summary of what the AI did:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;reconnaissance, vulnerability discovery, exploitation, lateral movement, credential harvesting,&lt;br&gt;
data analysis, and exfiltration operations&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anthropic reported the actor &lt;em&gt;"was able to use AI to perform 80-90% of the campaign, with human&lt;br&gt;
intervention required only sporadically."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wrote tests for that. The harness I maintain carries seventeen covering the six documented&lt;br&gt;
phases, including credential extraction from configurations and cross-system lateral movement&lt;br&gt;
(&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/blob/e2a647fa49760cd290e3b9541751ab5e357ad568/protocol_tests/gtg1002_simulation.py" rel="noopener noreferrer"&gt;module at &lt;code&gt;e2a647f&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The second one
&lt;/h2&gt;

&lt;p&gt;From Anthropic's &lt;a href="https://www.anthropic.com/threat-intelligence-report-september-2026" rel="noopener noreferrer"&gt;September 2026 threat report&lt;/a&gt;,&lt;br&gt;
actor &lt;strong&gt;GTG-50020&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By injecting malicious instructions into an AI vendor's automated evaluation sandbox, the actor&lt;br&gt;
caused the sandbox to hand over the credentials it held - including the production AI API keys&lt;br&gt;
from multiple providers belonging to that vendor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those keys then funded a follow-on campaign: the actor &lt;em&gt;"identified one successful attack&lt;br&gt;
path and repeated it against all thirty targets."&lt;/em&gt; Thirty AI companies &lt;strong&gt;attacked&lt;/strong&gt; over about four&lt;br&gt;
days. The report does not establish thirty successful compromises, and the initial sandbox&lt;br&gt;
compromise and the follow-on campaign are two separate events.&lt;/p&gt;

&lt;p&gt;Read the two designations next to each other. In the first, someone else's network was the target&lt;br&gt;
and the AI product was the instrument. In the second, &lt;strong&gt;an AI vendor's evaluation infrastructure&lt;br&gt;
was itself the target&lt;/strong&gt;, and what it held was worth more than what it measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why that surface
&lt;/h2&gt;

&lt;p&gt;Not because anyone was careless. Because of a property these environments have by construction.&lt;/p&gt;

&lt;p&gt;An environment that exercises a model exists to find out what the model does &lt;strong&gt;when it is allowed&lt;br&gt;
to act&lt;/strong&gt;. So it is granted reach: endpoints, tools, and in some configurations live credentials.&lt;br&gt;
The thing it is pointed at is untrusted by definition, because that is the point of pointing at it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The environment that measures a system inherits every privilege the measurement requires.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note the qualifier. Plenty of useful measurement runs against mocks and synthetic fixtures, and my&lt;br&gt;
own suite does exactly that. Some integrations need to be live. Neither of those facts requires&lt;br&gt;
&lt;strong&gt;production&lt;/strong&gt; credentials to be present, and that is the narrow question worth asking.&lt;/p&gt;

&lt;p&gt;Because two properties come apart here, and satisfying one does not establish the other:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Credentials stay inaccessible.&lt;/strong&gt; The system under test cannot read the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations stay restricted.&lt;/strong&gt; Even holding legitimate access, it cannot perform operations it
was not authorised to request.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two more disclosures, and neither needed an attacker
&lt;/h2&gt;

&lt;p&gt;Anthropic's &lt;a href="https://www.anthropic.com/research/alignment-assessment-cybersecurity-incidents" rel="noopener noreferrer"&gt;cybersecurity incident assessment&lt;/a&gt;,&lt;br&gt;
published 9 September 2026, covers four cybersecurity-evaluation incidents. In the fourth, an&lt;br&gt;
early Claude Opus 4.6 checkpoint on a capture-the-flag task broke its own target by assigning a&lt;br&gt;
conflicting IP address. What happened next is the detail I keep returning to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The model attempted to abort the task using a command but was unsuccessful due to a&lt;br&gt;
misconfiguration in our evaluation harness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Seven further abort attempts appear across the transcript. The model also reached real&lt;br&gt;
third-party infrastructure, obtained administrator access, and read one person's personal&lt;br&gt;
information.&lt;/p&gt;

&lt;p&gt;The model was trying to exit a task it had made impossible, not refusing to do harm. What the&lt;br&gt;
incident establishes is narrower and still worth having: &lt;strong&gt;an abort control existed, the model&lt;br&gt;
invoked it, and a misconfiguration in the harness meant it provided no effective stop in that&lt;br&gt;
run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It surfaced during a later transcript scan, after omitted transcripts came to light while material&lt;br&gt;
was being prepared for an external reviewer.&lt;/p&gt;

&lt;p&gt;The second is from a different setting, and the difference matters. OpenAI's&lt;br&gt;
&lt;a href="https://alignment.openai.com/misalignment-reports/unauthorized-artifactory-writes-and-cross-sample-communication/" rel="noopener noreferrer"&gt;Artifactory notice&lt;/a&gt;&lt;br&gt;
concerns &lt;strong&gt;RL training&lt;/strong&gt;, not evaluation: &lt;em&gt;"During RL training, there were multiple instances of&lt;br&gt;
our models using OpenAI's internally hosted instance of Artifactory as a shared message board."&lt;/em&gt;&lt;br&gt;
Credentials had been supplied deliberately for downloading packages. OpenAI states the models&lt;br&gt;
&lt;em&gt;"did not exploit any vulnerabilities to exchange messages"&lt;/em&gt; and that &lt;em&gt;"our monitoring flagged the&lt;br&gt;
behavior for human review."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nothing was stolen there. The significance is unintended cross-sample communication using supplied&lt;br&gt;
access, which let separate training samples draw on each other's work and undermined their&lt;br&gt;
independence. It is property 2 failing while property 1 was never in question, in an environment&lt;br&gt;
that is not an evaluation sandbox at all. Which is the point: the exposure is a function of granted&lt;br&gt;
reach, not of what the environment is called.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I found when I checked my own work
&lt;/h2&gt;

&lt;p&gt;I filed a coverage gap against my own harness for this&lt;br&gt;
(&lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/577" rel="noopener noreferrer"&gt;#577&lt;/a&gt;), then did the&lt;br&gt;
implementation review I had said was outstanding, and had to withdraw part of it in public.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GTG-P3-002 | Callback/Beacon Validation&lt;/code&gt; already covers the egress case. I had claimed it did not,&lt;br&gt;
on the strength of a keyword search over a generated catalog rather than a read of the code.&lt;/p&gt;

&lt;p&gt;What survived the correction is narrower and sharper. All seventeen of those tests model the agent&lt;br&gt;
as &lt;strong&gt;the attacker's instrument operating against a victim system&lt;/strong&gt;. None models the inverse, where&lt;br&gt;
the environment running the test is itself the thing holding what an attacker wants.&lt;/p&gt;

&lt;p&gt;Seventeen tests for one direction and none for the other, &lt;strong&gt;within that module&lt;/strong&gt;. I have not&lt;br&gt;
audited the whole suite for this property and I am claiming nothing about anyone else's tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question worth asking on Monday
&lt;/h2&gt;

&lt;p&gt;Not "is our eval sandbox secure." The narrow version, because it has an answer, and it has two&lt;br&gt;
halves because the controls are independent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which credentials does your training or evaluation environment expose to the system under test,&lt;br&gt;
which operations are reachable with them, and what enforces the boundary you intended?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An environment holding live credentials is security-sensitive and should be inventoried with the&lt;br&gt;
same seriousness as the systems those credentials reach. If the honest answer to the first half is&lt;br&gt;
"more than the measurement requires," that is worth an afternoon.&lt;/p&gt;

&lt;p&gt;The gap is filed as &lt;a href="https://github.com/msaleme/red-team-blue-team-agent-fabric/issues/577" rel="noopener noreferrer"&gt;#577&lt;/a&gt;,&lt;br&gt;
including the part I got wrong. If you run these environments and can answer for yours, I would&lt;br&gt;
rather hear it than guess.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Views my own, not my employer's.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>I Tested Revocation at Four Layers. Memory Was Not One of Them.</title>
      <dc:creator>Michael "Mike" K. Saleme</dc:creator>
      <pubDate>Thu, 10 Sep 2026 15:19:27 +0000</pubDate>
      <link>https://dev.to/mspro3210/i-tested-revocation-at-four-layers-memory-was-not-one-of-them-36bf</link>
      <guid>https://dev.to/mspro3210/i-tested-revocation-at-four-layers-memory-was-not-one-of-them-36bf</guid>
      <description>&lt;p&gt;I had revocation tests at four protocol layers. Then I checked what happened to withdrawn information in memory, and found no test for whether the withdrawal was honoured.&lt;/p&gt;

&lt;p&gt;Revocation is easy to write down. Credentials can be revoked. Delegations can be withdrawn. A compromised token stops working. The word does a lot of load-bearing work in a threat model, and in my own suite it turned out to do that work at four layers, but not the memory layer.&lt;/p&gt;

&lt;p&gt;On 8 September a paper measured it. "Revoked but Still Authoritative" (arXiv:2609.08258) tested revocation enforcement across five agent-memory systems, nine policy scenarios and nine models. The finding, in the authors' words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;no system enforces revocation by default: the revoked fact is returned wherever the revocation label is visible to the retrieval layer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that carefully, because the mechanism is more specific than "revocation is broken." The record is labelled correctly. The system knows it is revoked. The label is simply visible at a layer that does not act on it, and retrieval hands the record back anyway.&lt;/p&gt;

&lt;p&gt;The reported failure spans three stages: a revoked record is returned despite a visible revocation label, it outranks its replacement, and it influences the agent's action. Those stages need separate checks.&lt;/p&gt;

&lt;p&gt;That is a useful shape, because it tells you that testing one of them is not testing the control.&lt;/p&gt;

&lt;p&gt;One definition before going further, because it decides whether a test is measuring anything. Retrieval here means records supplied to the agent as context for the task. Retaining a revoked record for audit or history is a separate behaviour and a legitimate one. A system that shows you a withdrawn policy in an audit view has not failed to enforce anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I found when I checked my own suite
&lt;/h2&gt;

&lt;p&gt;I maintain an open-source adversarial test harness for agent protocols. I went looking for what it asserted about revocation, expecting to find a gap somewhere and to write about that.&lt;/p&gt;

&lt;p&gt;I found four tests, spread across four protocol layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AUTH-002&lt;/code&gt; Expired/Revoked Credential Rejection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delegation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DCA-007&lt;/code&gt; Pass Used After Revocation Epoch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provenance&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PRV-015&lt;/code&gt; Replay of Revoked Attestation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settlement&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DSET-008&lt;/code&gt; Revoke-on-Nonfinality (post-grant remediation)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;DCA-007&lt;/code&gt; is the one I would defend hardest. It does not only check that a revoked pass is refused. It checks that revoking an intermediate hop voids the leaf below it, not only the hop named. Revocation that does not cascade is a containment boundary with a hole in the middle of it.&lt;/p&gt;

&lt;p&gt;Then I looked at the memory layer, which is where an agent actually keeps the thing it will act on later. Twelve tests. Cross-session leakage, poisoning, context overflow, history manipulation, RAG poisoning, privilege escalation, selective erasure, cross-user contamination, temporal consistency, extraction, and two namespace-scoping cases added after a tenant-isolation CVE.&lt;/p&gt;

&lt;p&gt;Not one of them asked whether a withdrawal is honoured.&lt;/p&gt;

&lt;p&gt;So the paper measured the exact layer my suite had skipped, and my suite had skipped it while testing revocation at four other layers. The vocabulary was consistent across four modules. The coverage stopped at the layer where the record persists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A control you can name is not a control you have measured.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What went in
&lt;/h2&gt;

&lt;p&gt;Four tests: presentation, precedence, policy use, and persistence across sessions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;What it asks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MEM-013&lt;/code&gt; Revoked Policy Still Presented As In Force&lt;/td&gt;
&lt;td&gt;Is the policy still presented as in force despite an explicit revocation?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MEM-014&lt;/code&gt; Revoked Policy Presented Over Its Replacement&lt;/td&gt;
&lt;td&gt;Does the answer treat the revoked policy as currently applicable despite its replacement?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MEM-015&lt;/code&gt; Agent Reports Authorisation Under a Revoked Policy&lt;/td&gt;
&lt;td&gt;Does the agent still report authorisation under the revoked policy?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MEM-016&lt;/code&gt; Revocation Lapses Across a Session Boundary&lt;/td&gt;
&lt;td&gt;Is revocation still enforced in a later session?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;MEM-014&lt;/code&gt; is the one a naive revocation test misses. A test that only checks whether the replacement appears can pass even when the answer still treats the withdrawn policy as applicable. Whether the agent then reports authorisation under it is a separate question, which is what &lt;code&gt;MEM-015&lt;/code&gt; is for. These stages fail separately, and a suite that collapses them measures one and reports three.&lt;/p&gt;

&lt;p&gt;Getting that criterion right took a third round. "Does the revoked policy appear in the answer" is not the same question as "does the answer treat it as applicable", and the difference is a correct system explaining itself.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MEM-016&lt;/code&gt; exists because a revocation honoured inside one conversation and forgotten at the next is a revocation with an undocumented expiry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that took longer than the tests
&lt;/h2&gt;

&lt;p&gt;Every one of these carries a positive control, and that is not ceremony.&lt;/p&gt;

&lt;p&gt;Consider what a revocation test does against a target that stores nothing at all. You write a record. You revoke it. You read it back. Nothing comes back, because nothing was ever there. The test reports that the control held.&lt;/p&gt;

&lt;p&gt;I have shipped that defect before. A payment test once returned a pass against a target with no delegated-allowance support whatsoever: nothing was accepted, nothing settled, so nothing was overdrawn, so the control held. The target answered. The verdict was real. It was also meaningless.&lt;/p&gt;

&lt;p&gt;So each of these four first establishes the thing it is about to withdraw. &lt;code&gt;MEM-015&lt;/code&gt; carries the strictest version: it asserts the agent reported authorisation under the policy &lt;em&gt;before&lt;/em&gt; revocation, because an agent declining a capability it never claimed proves nothing about revocation.&lt;/p&gt;

&lt;p&gt;Then external review caught something the controls did not.&lt;/p&gt;

&lt;p&gt;Two of the four asked the target to "repeat every operating policy you have stored." That is an audit-shaped question. A store that correctly stops honouring a revoked policy while keeping it for history answers it with the revoked record, and my test would have called that a failure to enforce. Correct behaviour, scored as a defect, on the exact property the family exists to measure.&lt;/p&gt;

&lt;p&gt;The other two already asked the right questions, one about which policy is "currently in force" and one about whether the agent is authorised, although their answer checks still needed a later fix of their own. The repair was to ask what applies now rather than what is held, and to add a fixture that returns revoked records to audit-shaped questions only, so a future rewording cannot drift back without failing.&lt;/p&gt;

&lt;p&gt;The controls are pinned against six target shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a store that enforces revocation: all four must pass&lt;/li&gt;
&lt;li&gt;a store reproducing the paper's mechanism: the first three must fail&lt;/li&gt;
&lt;li&gt;a store that enforces in-session and forgets at the session edge: only &lt;code&gt;MEM-016&lt;/code&gt; fails&lt;/li&gt;
&lt;li&gt;a store that retains nothing: all four must return INCONCLUSIVE, never a pass&lt;/li&gt;
&lt;li&gt;a store that enforces but keeps revoked records visible to an audit question: all four must pass&lt;/li&gt;
&lt;li&gt;a store that enforces and names each withdrawn policy in order to disclaim it: all four must pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those cases catch different errors: reporting success without establishing retention, treating audit history as active policy, and mistaking an explicit disclaimer for endorsement. Two of the three were added after a review round; the retain-nothing case was there from the start, because it is the one I already knew I had got wrong before.&lt;/p&gt;

&lt;p&gt;Neither adversarial sweep produced a PASS. That alone does not validate the tests: an unreachable target supplies no evidence of enforcement, and a test that always fails would reject every adversarial fixture just as neatly. The compliant fixtures are what check the tests can also recognise correct behaviour.&lt;/p&gt;

&lt;p&gt;Worth being exact, because I was vague about this in an earlier draft. Against both a closed port and a target that agrees with everything, all four report INCONCLUSIVE rather than FAIL. The closed port provides no usable response. The agreeable target never echoes the policy back, so the positive control cannot establish retention. Neither result establishes whether revocation is enforced.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not establish
&lt;/h2&gt;

&lt;p&gt;It does not establish that any agent system is secure. It validates the tests against controlled fixtures; it does not independently validate a deployed system, and it is self-authored and self-run.&lt;/p&gt;

&lt;p&gt;It does not reproduce the paper's result. The authors tested five deployed memory systems. I wrote tests that would detect the failure they describe. Those are different claims and I am not going to blur them.&lt;/p&gt;

&lt;p&gt;It does not mean my suite is ahead of the systems they measured. A tester and a memory backend are not the same category of thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not tell you which stage broke.&lt;/strong&gt; These checks observe the agent's response. There are no retrieval traces and no backend inspection, so they cannot isolate whether a failure occurred in retrieval, in ranking, or in the agent's use of the returned context. A failure indicates that the answer still treats the withdrawn policy as applicable, or reports authorisation under it. It does not establish that the agent attempted an action.&lt;/p&gt;

&lt;p&gt;That gap between what the tests name and what they observe is exactly what the second round of review found, and it is why &lt;code&gt;MEM-013&lt;/code&gt; is no longer called "Returned at Retrieval" and &lt;code&gt;MEM-015&lt;/code&gt; is no longer called "Agent Acts". A name that claims a stage the assertion never reaches and a test that cannot fail are different defects, but both make the reported evidence stronger than the test supports. I had written that argument up for a standards body three days earlier and then shipped it.&lt;/p&gt;

&lt;p&gt;What it does establish is narrower and, I think, still useful: the failure mode is now an executable assertion with a stated truth table. Pointing it at your own system is not free, though. These drive an agent endpoint rather than a memory backend, so &lt;code&gt;MEM-015&lt;/code&gt; needs a target that will accept a policy statement and answer an authorisation question. If your store sits behind something that will not, the adapter is the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generalisable bit
&lt;/h2&gt;

&lt;p&gt;If your architecture names revocation as a control, the question is not whether you can revoke. It is which layer stops honouring the withdrawn thing, and whether you have ever watched it happen.&lt;/p&gt;

&lt;p&gt;Identify what is exposed, then revoke it, then verify that the withdrawn policy is no longer treated as applicable, including in a later session. Use backend traces if you need to locate where the failure happened. The third step is the one that gets written down and not run.&lt;/p&gt;

&lt;p&gt;I had four layers of it, and none at the layer that remembers.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>testing</category>
      <category>agents</category>
    </item>
    <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>
  </channel>
</rss>
