<?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: Imran Siddique</title>
    <description>The latest articles on DEV Community by Imran Siddique (@mosiddi).</description>
    <link>https://dev.to/mosiddi</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%2F3672280%2F7ad22e95-3054-4afd-ba09-0fdc0e9cf836.jpg</url>
      <title>DEV Community: Imran Siddique</title>
      <link>https://dev.to/mosiddi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mosiddi"/>
    <language>en</language>
    <item>
      <title>Approved is not the same as still admissible</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Tue, 29 Sep 2026 18:38:19 +0000</pubDate>
      <link>https://dev.to/mosiddi/approved-is-not-the-same-as-still-admissible-135o</link>
      <guid>https://dev.to/mosiddi/approved-is-not-the-same-as-still-admissible-135o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AeeneVKVEdFcxw009" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AeeneVKVEdFcxw009" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Say a finance lead approves an AI agent to pay supplier invoices for the next 90 days. On day three, one of those suppliers gets flagged for fraud. The agent keeps running, and the approval record still says approved. Signed, in date, valid. Nothing in that record is wrong. It is out of date, and it has no way of knowing.&lt;/p&gt;

&lt;p&gt;That is the problem I want to walk through this week, using our &lt;a href="https://github.com/agentrust-io/agent-manifest" rel="noopener noreferrer"&gt;Agent Manifest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An Agent Manifest is one signed record of everything that defines an agent when it is deployed: its prompt, its policies, its tools, its model, its data, its memory, its decision log, who it can hand work to, where it came from, and the humans who approved it. Anyone can check it later without trusting whoever runs the agent.&lt;/p&gt;

&lt;p&gt;Human approval is one of those pieces. Our &lt;a href="https://github.com/agentrust-io/agent-manifest/blob/main/docs/adr/0006-hitl-approval-mechanism.md" rel="noopener noreferrer"&gt;design decision on approvals&lt;/a&gt; checks four things: the person who approved is on the allowed list, the approval window has not run out, the approval is tied to this exact deployment, and the signature is real. The check runs offline. No call to an approval service, no dashboard to trust. Four yes-or-no answers from the record itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AeMKl3cWzCq9L3gZr" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AeMKl3cWzCq9L3gZr" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it stops working
&lt;/h3&gt;

&lt;p&gt;All four can still be yes after the approval stopped making sense.&lt;/p&gt;

&lt;p&gt;The record says a person approved something at a point in time. It does not say what they were relying on when they said yes, whether they can still stop the agent, or whether anybody objected. So in the invoice example the manifest verifies cleanly and the reason behind the approval is gone.&lt;/p&gt;

&lt;p&gt;The expiry date does not fix this. An approval does not expire when the facts change on day three, and it does expire when nothing has changed on day ninety. Time is a stand-in for “still current”, and a weak one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we already shipped
&lt;/h3&gt;

&lt;p&gt;Two small things, both on purpose.&lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://github.com/agentrust-io/agent-manifest/pull/355" rel="noopener noreferrer"&gt;PR 355&lt;/a&gt;, merged on 31 August, changed the spec to say plainly that an approval’s duration sets a window. It is not a promise that the approval is still current. No new field. It just stops people reading the field as something it never promised.&lt;/p&gt;

&lt;p&gt;Second, &lt;a href="https://github.com/agentrust-io/agent-manifest/pull/453" rel="noopener noreferrer"&gt;PR 453&lt;/a&gt;, which shipped in &lt;a href="https://github.com/agentrust-io/agent-manifest/releases/tag/python-v0.13.0" rel="noopener noreferrer"&gt;agent-manifest 0.13.0&lt;/a&gt; on 25 September. When all four checks pass, the verifier still says approved, and it now also says it cannot decide whether the approval still applies, because it has no evidence about the present. A result can be valid and undecided at the same time, and the README tells callers not to treat that as permission to go ahead. I like this one. The verifier now tells you what it does not know instead of staying quiet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who worked this out
&lt;/h3&gt;

&lt;p&gt;This started as &lt;a href="https://github.com/agentrust-io/agent-manifest/issues/348" rel="noopener noreferrer"&gt;issue 348&lt;/a&gt;, opened by &lt;a href="https://www.linkedin.com/in/ioanavalea" rel="noopener noreferrer"&gt;Ioana Valea&lt;/a&gt; on 27 August. The proposal was to record what the approver relied on, so a later check could notice when that changed.&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://github.com/solloek369-arch" rel="noopener noreferrer"&gt;@solloek369-arch&lt;/a&gt; raised the objection that changed the design. Everything in a manifest is fixed when it is issued; if a piece changes, you issue a new manifest. So comparing what the approver relied on against that same manifest will always match. It can never catch a change. What you compare against had to be decided first.&lt;/p&gt;

&lt;p&gt;That split what an approver relies on into three kinds of thing: parts of the agent itself, facts about the outside world, and the approver’s own standing. Each needs a different check, and one status for all three would hide exactly the difference you care about.&lt;/p&gt;

&lt;p&gt;Neither of them works for us. The person behind that objection described themselves as a snail on an F1 grid in this kind of work, and it is the reason the design changed. That is what an open problem is for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Ajr4jNYIriot5uK6W" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Ajr4jNYIriot5uK6W" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Still open
&lt;/h3&gt;

&lt;p&gt;Two things, and I would rather say so than wait for a tidy answer.&lt;/p&gt;

&lt;p&gt;Deciding whether an approval still applies is open. The verifier can now say “I can’t tell”. Turning that into a real answer needs evidence about the present that the verifier does not have yet.&lt;/p&gt;

&lt;p&gt;The words for “not established” are not settled, and that is bigger than this issue. &lt;a href="https://github.com/agentrust-io/trace-spec/issues/279" rel="noopener noreferrer"&gt;trace-spec issue 279&lt;/a&gt; is collecting the same question from six threads. A checker that cannot tell “this is false”, “this is unproven” and “nobody asked” apart will sooner or later be read as saying the first when it meant the third.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it on your own system
&lt;/h3&gt;

&lt;p&gt;Take any approval your system records and ask three questions. What was the approver relying on, and would you find out if it changed? Who can stop this right now, and has anyone actually tried? Did anybody object, and where is that written down?&lt;/p&gt;

&lt;p&gt;Most approval systems answer none of the three. None of them need cryptography to fix. They need you to treat an approval as a claim about today, not a receipt from last month.&lt;/p&gt;

&lt;p&gt;If you have run a human approval step in production long enough to watch one go stale, that is what &lt;a href="https://github.com/agentrust-io/agent-manifest/issues/348" rel="noopener noreferrer"&gt;issue 348&lt;/a&gt; needs next.&lt;/p&gt;

</description>
      <category>aigovernance</category>
      <category>cybersecurity</category>
      <category>opensource</category>
      <category>agents</category>
    </item>
    <item>
      <title>Functional Observation by default</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 28 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/functional-observation-by-default-efj</link>
      <guid>https://dev.to/mosiddi/functional-observation-by-default-efj</guid>
      <description>&lt;p&gt;&lt;em&gt;The App Defense Alliance AI Agent Specification defines three kinds of lab evidence and says an untagged requirement defaults to black-box testing. None of its 37 requirements is tagged, so two that need a configuration file or system logs read as black-box passes. Filed as issue 514.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The App Defense Alliance AI Profile gained a good agent control on 22 September. Section 2.4.2, Verifiable Tool Identity and Provenance, merged through pull request 437. The agent may invoke only allowlisted tools with pinned identities, verifies server identity and catalog integrity when a session opens, rejects a tool definition that changes after pinning, and fails closed on anything unknown or revoked. Those are enforcement requirements on the agent, before the call, and I have no quarrel with any of them.&lt;/p&gt;

&lt;p&gt;The same document has a sound idea about assessment too. Its Evidence Taxonomy, added in August for issue 411, sorts lab evidence into three kinds. Functional Observation is the assessor driving the production agent through its user and tool interfaces, with "No source-code or backend access." Attestation is a named artifact the developer supplies on request, and the taxonomy's example is log-file samples. Document Review covers policies that cannot be exercised.&lt;/p&gt;

&lt;p&gt;Then one sentence sets the default: "where a requirement can be satisfied purely by exercising the running application, it is Functional Observation by default."&lt;/p&gt;

&lt;h2&gt;
  
  
  The default does the tagging
&lt;/h2&gt;

&lt;p&gt;On develop the three type names occur only inside the taxonomy section. A word-boundary grep of the raw file finds Functional Observation three times, Attestation three times and Document Review twice, and none of them in any of the 37 requirement Evidence blocks. So all 37 read as &lt;strong&gt;Functional Observation&lt;/strong&gt; , including two that cannot be met that way.&lt;/p&gt;

&lt;p&gt;Section 2.4.2's Evidence block says: &lt;em&gt;"Agent application, including its tool allowlist/registry configuration. Access to the user interface and the tool interface."&lt;/em&gt; Its first test step is to review that allowlist. Neither interface exposes it. Section 5.2.1 lists "System log files", which is the taxonomy's own example of Attestation.&lt;/p&gt;

&lt;p&gt;The 2.4.2 step also ends "(AL0/AL1 static evidence)". The document says this version "only contains testing guidance and acceptance criteria of Assurance Level 2", and leaves AL0 and AL1 to future revisions. One file over, the AI Tool Testing Guide defines both levels as evidence "based on source code inspection", the access Functional Observation rules out.&lt;/p&gt;

&lt;p&gt;In practice, a lab report saying 2.4.2 passed does not say whether the assessor saw the allowlist, took the developer's word for it, or read a configuration file sent over the day before. Under the default, each of those is a black-box pass.&lt;/p&gt;

&lt;p&gt;TRACE has this problem written down about itself. The trace-spec limitations file has a section headed Platform state is not appraised, and its consequence is that "a report from a machine with SMT enabled and the alias check never completed verifies exactly as cleanly as one from a machine with neither condition." An untagged Evidence block does the same to a certification one layer up. The pass is valid. What it was bought with is not in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It was already proposed
&lt;/h2&gt;

&lt;p&gt;The idea is not mine. Issue 411 recommended tagging each requirement. Pull request 427, which added the taxonomy and closed 411, says: "Held for WG input (not in this PR): whether to tag &lt;em&gt;every&lt;/em&gt; requirement's Evidence block with a type (recommended)." Nothing tracked that item until issue 514. The AI Tool Testing Guide in the same profile already carries an evidence line on &lt;strong&gt;38 of its 38&lt;/strong&gt; Evidence blocks, per assurance level rather than per type, so the working group has the habit on one side and not the other.&lt;/p&gt;

&lt;p&gt;Timing matters because pull request 494, opened 22 September, splits the Agent Specification into a Specification and a Test Guide. On that branch the Test Guide holds 36 Evidence blocks and the Specification holds 16 Audit sections and none. Both files carry the taxonomy paragraph, identical to the byte, and both still say "Every AL2 test case in this specification". The branch was cut before 437 merged, so 2.4.2 is not in it yet; aeoess flagged that in review on 23 September.&lt;/p&gt;

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

&lt;p&gt;Issue 514 asks for a type line on each Evidence block, Functional Observation unless stated, with the named artifact for every Attestation or Document Review entry, starting with 2.4.2 and 5.2.1, and for the AL0/AL1 parenthetical to go. On 494 I asked the group to name one owner for the taxonomy before merge, since issue 400 already asks for a single source of truth on the Tool side.&lt;/p&gt;

&lt;p&gt;The counts are word-boundary greps of the raw files on develop at 97c9f94 and on the 494 head at 1be3d9e. This is a reading of the text, not an assessment I have run.&lt;/p&gt;

&lt;p&gt;If 494 merges first, the tags go into its Test Guide: 36 blocks now, 37 once 2.4.2 is carried over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/issues/514" rel="noopener noreferrer"&gt;appdefensealliance/ASA-WG issue 514, filed 28 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/pull/494#issuecomment-5873432330" rel="noopener noreferrer"&gt;Comment on pull request 494, the Specification and Test Guide split&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/blob/97c9f946295cedd21d533ac91910c6c4f5cc77bc/AI%20Profile/AI%20Agent%20Specification.md" rel="noopener noreferrer"&gt;AI Agent Specification on develop, commit 97c9f94&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/blob/97c9f946295cedd21d533ac91910c6c4f5cc77bc/AI%20Profile/AI%20Tool%20Testing%20Guide.md" rel="noopener noreferrer"&gt;AI Tool Testing Guide on develop, commit 97c9f94&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/pull/427" rel="noopener noreferrer"&gt;Pull request 427, the evidence taxonomy and the held tagging item&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/pull/437" rel="noopener noreferrer"&gt;Pull request 437, section 2.4.2 Verifiable Tool Identity and Provenance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appdefensealliance/ASA-WG/issues/400" rel="noopener noreferrer"&gt;Issue 400, one source of truth for the Tool documents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/agentrust-io/trace-spec/blob/e3111c7/LIMITATIONS.md" rel="noopener noreferrer"&gt;trace-spec LIMITATIONS.md, Platform state is not appraised&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>standards</category>
      <category>evidence</category>
    </item>
    <item>
      <title>Two caps, two enforcement points</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Fri, 25 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/two-caps-two-enforcement-points-355n</link>
      <guid>https://dev.to/mosiddi/two-caps-two-enforcement-points-355n</guid>
      <description>&lt;p&gt;&lt;em&gt;The MCP TypeScript SDK's 1.30.1 patch added a 4 MiB body limit and a 100 message batch limit. On the Express path the SDK documents, the batch limit holds and the body limit is never consulted. A 100 kilobyte parser default refuses first, and the transport never hears about it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;maxRequestBodySize&lt;/code&gt; to 20 MB on an MCP server built with the TypeScript SDK's own Express factory, send it 110 kilobytes of JSON-RPC, and it answers 413. The body of that 413 is an HTML page.&lt;/p&gt;

&lt;p&gt;Release 1.30.1 went to npm on 23 September, two months after 1.30.0. The package diff is small: three modules changed, one added, and the version string. The added module, &lt;code&gt;requestBody&lt;/code&gt;, sets two numbers. A POST body over 4 MiB gets 413. A JSON-RPC batch longer than 100 messages gets 400 with code -32600.&lt;/p&gt;

&lt;p&gt;Both work. They are checked in different places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each one sits
&lt;/h2&gt;

&lt;p&gt;The web standard transport takes a POST one of two ways. If the caller passes a pre parsed body, it uses that. Otherwise it reads the stream itself, refusing a declared Content-Length over the bound before reading anything and stopping the moment the bound is crossed. The byte cap lives inside that second branch. The batch cap sits after both branches close, so it runs on the array however it arrived.&lt;/p&gt;

&lt;p&gt;The pull request says so. A pre parsed body &lt;em&gt;"remains the way to opt out of the SDK's read entirely"&lt;/em&gt;, and the batch bound applies on both paths. Nothing is hidden. It is also the path the documented deployment takes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the documented server does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;createMcpExpressApp&lt;/code&gt; in the 1.x package mounts &lt;code&gt;express.json()&lt;/code&gt; with no options. The Node transport's doc comment shows the handler passing &lt;code&gt;req.body&lt;/code&gt; in as the pre parsed body. Express 5.2.1 resolves body-parser 2.3.0, whose limit defaults to 102,400 bytes.&lt;/p&gt;

&lt;p&gt;I ran that server on 1.30.1 with the transport option set to 20 MB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;90 KB body: 200.&lt;/li&gt;
&lt;li&gt;110 KB body: 413, &lt;code&gt;text/html&lt;/code&gt;, from Express. The route handler ran zero times.&lt;/li&gt;
&lt;li&gt;5 MiB body: the same HTML 413.&lt;/li&gt;
&lt;li&gt;101 message batch: 400, code -32600, from the transport.&lt;/li&gt;
&lt;li&gt;The same transport reading the body itself: 110 KB passes, 5 MiB gets a JSON-RPC 413 naming 4194304 bytes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 20 MB setting is accepted, validated (0, -1, NaN and the string "4mb" each throw a RangeError at construction) and then &lt;strong&gt;never read&lt;/strong&gt;. The ceiling that fires is 100 kilobytes, owned by a component the option cannot reach. On the 1.x factory nothing can reach it: its options type has two fields, &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;allowedHosts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the operator sees
&lt;/h2&gt;

&lt;p&gt;A client gets two different 413s for one condition. One is a JSON-RPC error object. The other is an Express error page, so a client that parses errors as JSON-RPC gets HTML instead.&lt;/p&gt;

&lt;p&gt;Monitoring splits the same way. The transport's &lt;code&gt;onerror&lt;/code&gt; hook fired once for the 101 message batch and &lt;strong&gt;zero times&lt;/strong&gt; for the 110 KB body, because the transport never ran. The only trace of that refusal is Express printing a stack to stderr through its default error handler. The parser prevented the request. The layer most teams instrument never recorded it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix exists, on the other line
&lt;/h2&gt;

&lt;p&gt;Issue 1354 asked for this, and pull request 1625 added a &lt;code&gt;jsonLimit&lt;/code&gt; option in March. That landed on main, which ships as the v2 split packages. &lt;code&gt;@modelcontextprotocol/express&lt;/code&gt; 2.0.1, published the same day as 1.30.1, has the option, and still defaults to the parser's 100 kilobytes when it is unset.&lt;/p&gt;

&lt;p&gt;The 1.x package is the one most people install. npm counted 40.3 million downloads of &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; in the week to 21 September, against 239,746 for the v2 Express package. Downloads are not deployments. The gap is still two orders of magnitude.&lt;/p&gt;

&lt;p&gt;On 1.x, skip the factory. Build the Express app with the exported &lt;code&gt;hostHeaderValidation&lt;/code&gt; middleware, mount &lt;code&gt;express.json({ limit })&lt;/code&gt; at the number you meant, and add an error handler that answers in JSON-RPC and logs where the rest of your refusals go. On v2, set &lt;code&gt;jsonLimit&lt;/code&gt; to match &lt;code&gt;maxRequestBodySize&lt;/code&gt; so the two numbers agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not survive checking
&lt;/h2&gt;

&lt;p&gt;"The factory offers no way to change the limit." True of 1.x only. v2 has had &lt;code&gt;jsonLimit&lt;/code&gt; since March.&lt;/p&gt;

&lt;p&gt;"The release reordered a Host check that is not there." Partly. 1.30.1 moved the parser below Host validation, with a shipped comment saying a disallowed Host gets 403 unread. With no &lt;code&gt;allowedHosts&lt;/code&gt; and any host other than the three loopback names, that validation is not installed. Issue 2843 already reports it on main, and a second contributor confirmed it across the Express, Hono and Fastify factories on 24 September. It is their finding, so it stays there.&lt;/p&gt;

&lt;p&gt;The release note describes two limits. On the deployment the SDK documents, one of them is 100 kilobytes and belongs to Express.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/sdk" rel="noopener noreferrer"&gt;"@modelcontextprotocol/sdk on npm, 1.30.0 and 1.30.1 publish times"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/releases/tag/1.30.1" rel="noopener noreferrer"&gt;Release 1.30.1 notes, three pull requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/pull/2717" rel="noopener noreferrer"&gt;Pull request 2717, the v1.x backport of the body and batch limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/issues/1354" rel="noopener noreferrer"&gt;Issue 1354, asking for a configurable parser limit on createMcpExpressApp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/pull/1625" rel="noopener noreferrer"&gt;Pull request 1625, adding jsonLimit on main, merged 5 March 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/express" rel="noopener noreferrer"&gt;"@modelcontextprotocol/express on npm, 2.0.1"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/issues/2843" rel="noopener noreferrer"&gt;Issue 2843, non-loopback host skips Host validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/expressjs/body-parser#limit" rel="noopener noreferrer"&gt;body-parser, the json limit default&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>mcp</category>
      <category>enforcement</category>
    </item>
    <item>
      <title>The evidence standard that needs a gate to count</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-evidence-standard-that-needs-a-gate-to-count-3kp4</link>
      <guid>https://dev.to/mosiddi/the-evidence-standard-that-needs-a-gate-to-count-3kp4</guid>
      <description>&lt;p&gt;&lt;em&gt;Proof-of-Control positions itself as the evidence half of agent assurance, not runtime enforcement. Its own threshold requires an in-path gateway that withholds actions, and its threat model has no row for that gateway. Filed as issue 78.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Enforcement decides what an agent may do; verification shows what it actually did."&lt;/p&gt;

&lt;p&gt;That line is from the Proof-of-Control crosswalk against CSA AARM, and it is a good division of labour. The README repeats it as a boundary: under what Proof-of-Control is not, one of four entries is "Not runtime enforcement." I use the same split in my own work. A gate prevents. A signed record proves. A log has never stopped anything.&lt;/p&gt;

&lt;p&gt;The standard, currently Working Draft v0.1.4 and open for public comment until 30 October, does not hold to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the line moves
&lt;/h2&gt;

&lt;p&gt;C7.1 is called Generation at the Action Boundary. Three of its requirements describe a component that holds actions back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7.1.1: "the agent has no network or credential path to its tools that bypasses the gateway."&lt;/li&gt;
&lt;li&gt;7.1.3: "the gateway does not forward the action to the tool until the &lt;em&gt;before&lt;/em&gt; record is durably written."&lt;/li&gt;
&lt;li&gt;7.1.4 (c): "an attested enforcement point that admits only requests carrying matching evidence."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are Level 3. The standard's Requirement Levels table says meeting every Level 1 to 3 requirement in the claimed domains is the minimum for a Proof-of-Control claim. So nothing gets the label without an in-path, fail-closed gateway. That is enforcement, at the service, before the operation runs. Its only policy is that evidence exists, but it withholds the action all the same.&lt;/p&gt;

&lt;p&gt;I think those requirements are right. Evidencing a policy evaluation without controlling the effect channel is how a system ends up with perfect records of decisions it never enforced, and 7.1.5 caps that case below the threshold for exactly that reason. The problem is what happened around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The component with no row
&lt;/h2&gt;

&lt;p&gt;Appendix C is the normative threat model: 32 coverage rows. In the column describing what Proof-of-Control defends against, 12 rows use "gate" or "gates". Word-boundary matches on the raw file for gateway, intercept, interception, mediation, custody and bypass: zero for all six.&lt;/p&gt;

&lt;p&gt;The gateway sees every action at Level 3, holds the signing keys that 7.3.2 puts out of the operator's reach, and at Level 4 refuses actions under 7.6.3 when evidence cannot be written. The CISO review found the failure case, F1, "No fail-closed requirement", and it was applied as 7.6.1 and 7.6.3. The compromise case never got a row.&lt;/p&gt;

&lt;p&gt;The nearest row, audit tampering, is about a host rewriting the record. A compromised gateway rewrites nothing. It signs true records for the actions it lets through and writes nothing for the ones it does not, and every signature verifies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check that does not reach it
&lt;/h2&gt;

&lt;p&gt;Here is the part that is not in the issue, and it is the part I would test first.&lt;/p&gt;

&lt;p&gt;At Level 4, 10.3.2 requires evidence to cover every in-scope action, "demonstrated by reconciling gateway action counts against evidence-record counts over an audit window, with zero unexplained difference." Both numbers come from the gateway. An action that goes around it adds to neither count, so the reconciliation balances.&lt;/p&gt;

&lt;p&gt;That is judgment, not a measurement. I have read the text; I have not run a conformant deployment against it. But the arithmetic does not depend on the implementation.&lt;/p&gt;

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

&lt;p&gt;Issue 78 asks for three things: two Appendix C rows, gateway compromise and gateway bypass, with honest coverage grades; the gateway operator added to the C10.2 trust-assumption disclosure; and a restated boundary. My suggested wording is that Proof-of-Control does not define the policy an agent is held to, and does require an enforcement point whose only policy is that evidence exists. Issue 79 is smaller: the one-pager still tells newcomers Tiers 3 and 4 require trusting "no one", a claim C8 has already withdrawn. Comments in the window get a published disposition.&lt;/p&gt;

&lt;p&gt;For anyone evaluating a system that claims Proof-of-Control, ask who operates the gateway, then reconcile the tool side's own request logs against the evidence store rather than the gateway's counts. A tool call that appears in the first and not the second is the row Appendix C is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/issues/78" rel="noopener noreferrer"&gt;LFDT-ProofOfControl/ov-poc-standard issue 78, filed 24 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/issues/79" rel="noopener noreferrer"&gt;LFDT-ProofOfControl/ov-poc-standard issue 79, one-pager residual trust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/README.md" rel="noopener noreferrer"&gt;README, "What Proof-of-Control is NOT", commit 22c7b62&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/0.1/en/0x10-C07-Evidence-Generation-and-Properties.md" rel="noopener noreferrer"&gt;C7 Evidence Generation and Properties, 7.1.1 to 7.1.5, 7.3.2, 7.6.1 to 7.6.3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/0.1/en/0x03-Using-Proof-of-Control.md" rel="noopener noreferrer"&gt;Using Proof-of-Control, Requirement Levels table&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/0.1/en/0x92-Appendix-C_Threat-Model.md" rel="noopener noreferrer"&gt;Appendix C, Threat Model (normative)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/0.1/en/0x10-C10-Conformance-and-Disclosure.md" rel="noopener noreferrer"&gt;C10 Conformance and Disclosure, 10.3.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/mappings/csa-aarm.md" rel="noopener noreferrer"&gt;CSA AARM crosswalk (draft seed coding)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LFDT-ProofOfControl/ov-poc-standard/blob/22c7b62/docs/reviews/ciso-review-v0.1.4.md" rel="noopener noreferrer"&gt;CISO review of v0.1.4, finding F1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>standards</category>
      <category>enforcement</category>
    </item>
    <item>
      <title>Every control plane writes the log that describes its own behaviour</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 21 Sep 2026 16:41:59 +0000</pubDate>
      <link>https://dev.to/mosiddi/every-control-plane-writes-the-log-that-describes-its-own-behaviour-1jgg</link>
      <guid>https://dev.to/mosiddi/every-control-plane-writes-the-log-that-describes-its-own-behaviour-1jgg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AsHik4MAncctk2ZXQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AsHik4MAncctk2ZXQ" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An agent calls a tool. The policy engine says allow. The call goes through.&lt;/p&gt;

&lt;p&gt;None of that proves the policy engine was not compromised. Software-only governance of MCP tool calls cannot guarantee three things, and they are worth naming separately because they fail in different ways.&lt;/p&gt;

&lt;p&gt;The Cedar policy on disk may not be the policy that ran. A rogue admin can swap the bundle after approval, and the hash check that would catch it runs inside the same operating system that admin controls. The allow decision may have been flipped in memory, because a supply-chain CVE in the evaluator executes in the same address space as everything else. And the audit log may not reflect what happened, because any party holding the software signing key can reconstruct a valid-looking chain afterwards.&lt;/p&gt;

&lt;p&gt;Those are not exotic. They are the ordinary consequences of running the thing that decides in the same place as the thing it decides about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where cMCP puts the decision
&lt;/h3&gt;

&lt;p&gt;cMCP is a gateway in front of your MCP servers, and its one structural commitment is that the policy engine runs inside a hardware Trusted Execution Environment, where the agent being governed cannot reach it. Every tool call is intercepted and evaluated against a Cedar policy bundle before it goes anywhere.&lt;/p&gt;

&lt;p&gt;The mechanism that makes that more than an assertion: &lt;strong&gt;before the gateway serves a single tool call, it measures its installed code, its policy bundle and its config, and binds that digest into the hardware-signed attestation report&lt;/strong&gt;. You are not being asked to believe the gateway loaded the approved bundle. On SEV-SNP and TDX the digest sits in the report’s report_data, on Azure confidential VMs it rides in a vTPM quote that the SNP report vouches for, and on the TPM tier it is extended into a certified NV index. Either way a verifier learns which code and which policy were in force from the same evidence that establishes the enclave, and when the bundle reloads mid-session the gateway re-attests rather than leaving the old report standing.&lt;/p&gt;

&lt;p&gt;The payloads go inside too. In a hardware deployment the tool-call payload is processed inside the enclave, so the policy decision and any redaction happen where the host cannot reach them. What the host and the connectivity provider can still read depends on the egress policy, and the upstream tool server is its own component outside the TEE. The attestation covers the gateway boundary, not what happens on the far side of it.&lt;/p&gt;

&lt;p&gt;Deny is not the only verdict, which matters more than it sounds. A policy can redact a response rather than block the call, and the catalog carries a per-tool schema_validation_mode that selects which. A hard block on a tool an agent genuinely needs does not end in compliance, it ends in somebody routing around the gateway.&lt;/p&gt;

&lt;p&gt;The deliverable is a signed TRACE claim, per session or per call, and a verifier checks it without trusting the operator who produced it. Run the gateway in a TEE and the claim is hardware-attested; run it in software mode and it is signed only, and the claim states which of the two it is rather than leaving a reader to assume the stronger one.&lt;/p&gt;

&lt;p&gt;Software mode is also how you should meet it. pip install cmcp-runtime needs no hardware and gives you the whole shape of the thing before you decide whether to provision for attestation.&lt;/p&gt;

&lt;p&gt;Version 0.5.0 shipped on 6 September. One change in it is a plain correctness win: the policy bundle hash now uses RFC 8785 canonical JSON, which is what docs/spec/cedar-policy.md section 1 always specified. Until then the implementation and its own specification disagreed on how to hash the object the entire enforcement chain anchors to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Aly4W4JvqzVSNL_5B" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Aly4W4JvqzVSNL_5B" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The record it writes about itself
&lt;/h3&gt;

&lt;p&gt;Here is a thing cMCP records that most gateways do not. When a session has read sensitive data and then calls a tool that leaves that sensitivity domain, the boundary crossing goes into the claim as an event. That is the difference between knowing an agent read patient records, and knowing it read patient records and then called an external API.&lt;/p&gt;

&lt;p&gt;The check is a set membership test, and the gateway answers it from a set it holds itself. Which is where a control plane writing its own record gets interesting, because nothing outside it is checking that the set is the right one.&lt;/p&gt;

&lt;p&gt;In 0.4.1 the set was written out by hand in session/call_log.py as {“pii”, “phi”, “pci”, “restricted”}. The vocabulary the rest of the runtime uses lives one file away in session/state.py, ranking public 0, pii 1, confidential 2, and hipaa_phi, mnpi and trade_secret all at 3.&lt;/p&gt;

&lt;p&gt;Read those together. They intersect on pii and nothing else. phi, pci and restricted are not names the runtime uses anywhere, and all three top-tier domains were absent. So the check fired on one domain, the lowest tier above public, and a session that read HIPAA PHI then called an external tool recorded no crossing. The claim was signed, the attestation was valid, the verifier was satisfied, and the event was not in it.&lt;/p&gt;

&lt;p&gt;0.5.0 derives the set from one COMPLIANCE_DOMAINS vocabulary, keeping the legacy spellings so no deployment loses events.&lt;/p&gt;

&lt;p&gt;The codebase had already written this failure mode down. A docstring in state.py warns that a name dropping out of the effective vocabulary will rank 0 through a fail-open get(tag, 0) default, and names it the same class of hole that catalog-load validation was added to close. That comment sits one file away from a hand-written set holding three names the vocabulary did not have. The defence was attached to a mechanism rather than to a rule, so nothing noticed the same shape next door.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AoefzyPUYNV1VBl7Q" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AoefzyPUYNV1VBl7Q" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What to verify
&lt;/h3&gt;

&lt;p&gt;The general form is a question you can put to any governance component, ours first: when this thing records a fact about its own behaviour, where does the vocabulary for that fact come from, and what happens when the value it sees is not in it?&lt;/p&gt;

&lt;p&gt;Find every place your control plane holds a literal set of category names, find where those categories are actually defined, and if the two are not derived from one source, compute the intersection by hand and look at what falls outside.&lt;/p&gt;

&lt;p&gt;Then ask what the failure does. A membership test returning False on an unrecognised value fails open, quietly, and produces a record that is correct in every field it contains. That is worse than a crash, because a signed and complete-looking claim invites more trust than an error does.&lt;/p&gt;

&lt;p&gt;Last, check whether anything goes red when the vocabularies diverge. Not whether today’s values line up. Whether a test fails when somebody adds a seventh domain to one file and not the other.&lt;/p&gt;

&lt;p&gt;What does your evidence say when the thing it describes uses a word your recorder has never heard?&lt;/p&gt;

</description>
      <category>aigovernance</category>
      <category>mcps</category>
      <category>attestation</category>
      <category>agents</category>
    </item>
    <item>
      <title>Five Docker Sandboxes CVEs this year, all in the doors</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Fri, 18 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/five-docker-sandboxes-cves-this-year-all-in-the-doors-2pk7</link>
      <guid>https://dev.to/mosiddi/five-docker-sandboxes-cves-this-year-all-in-the-doors-2pk7</guid>
      <description>&lt;p&gt;&lt;em&gt;Docker published two more CVEs against its agent sandbox on 15 September. Read alongside the three it published in June and August, all five sit in the two controls its README sells, and three of them fail the same way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Five CVEs have been published against Docker Sandboxes this year, all by Docker as its own CNA. Two landed on 15 September, CVE-2026-77179 at CVSS 4.0 9.4 and CVE-2026-79994 at 8.7. Three came earlier: CVE-2026-12039 and CVE-2026-12539 in June, CVE-2026-18171 in August.&lt;/p&gt;

&lt;p&gt;Docker Sandboxes runs each coding agent in its own microVM, which is the right design, and none of the five is in the hypervisor. Every one is in a component that exists to let something through it: the embedded DNS server, the ICMP egress authorizer, the virtio-fs host server (twice), and the guest to host Unix socket relay. Isolation in an agent sandbox is the wall minus every door you cut into it, and the doors are where this product has been breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The README names the doors
&lt;/h2&gt;

&lt;p&gt;The README lists four things under "What you get". Two of them are "File access controls between host and sandbox" and "Network access control". Sort the five records by those two lines and nothing is left over. DNS and ICMP are network access. The two virtio-fs bugs and the socket relay are file access, the relay because it reaches host sockets by filesystem path.&lt;/p&gt;

&lt;p&gt;The first item on the same list is "YOLO mode by default: agents work without asking permission." That is a reasonable default for a product whose pitch is that the agent can run unattended, and it means those two controls are standing in for the human who would otherwise approve each action. That is my reading of the document, not something Docker says.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three of the five made a decision once
&lt;/h2&gt;

&lt;p&gt;This is the part worth taking to any sandbox, not only this one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CVE-2026-12539: ICMP egress is blocked "with an authorizer applied only at network-creation time", and not re-applied to networks rebuilt from disk after the Docker daemon restarts.&lt;/li&gt;
&lt;li&gt;CVE-2026-79994: the socket relay "validates that a socket path is inside an authorized workspace, but later reconnects using the pathname." A guest swaps an intermediate directory for a symlink in between.&lt;/li&gt;
&lt;li&gt;CVE-2026-77179: the virtio-fs server "improperly follows symlinks when reopening an unlinked file from a stored path."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is a missing control. The relay's check ran and was correct when it ran. The ICMP authorizer ran and was correct when it ran. Each failed because its answer was treated as durable past the moment something could change underneath it: a path that can be relinked, a network that can be rebuilt. &lt;strong&gt;An enforcement decision has a lifetime&lt;/strong&gt; , and in all three the lifetime was shorter than the code assumed.&lt;/p&gt;

&lt;p&gt;I have the same shape written down about my own work. The trace-spec LIMITATIONS file says "Signature validity is permanent; trust is not", and requires a verifier to consult revocation status at verification time, with &lt;code&gt;verify_record()&lt;/code&gt; failing closed when the revocation store cannot answer. Different substrate, same rule. The two honest fixes are the same in both places: bind the decision to the object you act on (an open file descriptor, not a name), or re-make it at the moment of use and fail closed when you cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to ask on Monday
&lt;/h2&gt;

&lt;p&gt;For each door a sandbox advertises, ask one question: when is this decision re-made? At creation only, at every connection, at every open, after a restart? If the vendor cannot answer, treat the control as holding until the first event that can invalidate it, and find out what those events are.&lt;/p&gt;

&lt;p&gt;Two narrower things a team running this product can check today.&lt;/p&gt;

&lt;p&gt;Platform scope. CVE-2026-77179 carries a platforms field set to MacOS and opens "On macOS". CVE-2026-79994 has no platforms field, and neither macOS nor Darwin appears anywhere in it. The README installs on macOS, Windows, Ubuntu and Rocky Linux 8. The record does not say the socket bug reaches Linux or Windows; it also does not say it is Mac only. If you run it off a Mac, read that record as applying to you.&lt;/p&gt;

&lt;p&gt;Version. v0.42.0, released 7 September, fixes both new CVEs; the current stable is v0.43.0. The same 0.42.0 notes also say "Fixed a vulnerability where a sandboxed process could get the daemon to open a host D-Bus transport and execute an arbitrary command on the host." An NVD keyword search for Docker Sandboxes returns five records and none of them mentions D-Bus. That may simply not be assigned yet. It is a question for Docker, not a finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not survive checking
&lt;/h2&gt;

&lt;p&gt;"Neither is a VM escape." Both 15 September bugs let a malicious guest act on the host, and 77179 reaches arbitrary host files "potentially achieving host code execution". They are guest to host escapes. What holds is narrower: neither is in the hypervisor.&lt;/p&gt;

&lt;p&gt;"Both helpers checked." Only the socket relay record describes a check. The virtio-fs record describes acting on a stored path, which is the same lifetime failure without a stated validation step.&lt;/p&gt;

&lt;p&gt;"The 9.4 record points at no code, unlike the 8.7." True, and the wrong frame. None of the three earlier records points at code either. 79994 is the exception, citing a pull request in docker/sailor and one in docker/sandboxes, and neither repository resolves for an outside account, so the code is not public in any of the five.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2026/77xxx/CVE-2026-77179.json" rel="noopener noreferrer"&gt;CVE-2026-77179, CVE Program record (virtio-fs, macOS, CVSS 4.0 9.4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2026/79xxx/CVE-2026-79994.json" rel="noopener noreferrer"&gt;CVE-2026-79994, CVE Program record (Unix socket relay, CVSS 4.0 8.7)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-12039" rel="noopener noreferrer"&gt;CVE-2026-12039, NVD (DNS not covered by the egress allowlist)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-12539" rel="noopener noreferrer"&gt;CVE-2026-12539, NVD (ICMP authorizer not re-applied after daemon restart)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-18171" rel="noopener noreferrer"&gt;CVE-2026-18171, NVD (read-only mount writable at the virtio-fs export)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/docker/sbx-releases/releases/tag/v0.42.0" rel="noopener noreferrer"&gt;Docker Sandboxes v0.42.0 release notes, 7 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/docker/sbx-releases/blob/main/README.md" rel="noopener noreferrer"&gt;docker/sbx-releases README, read 18 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/agentrust-io/trace-spec/blob/main/LIMITATIONS.md" rel="noopener noreferrer"&gt;trace-spec LIMITATIONS.md, "Pure offline verification cannot prove non-revocation"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>sandboxing</category>
      <category>enforcement</category>
    </item>
    <item>
      <title>Agent Mesh: what it assumes about your agents</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Thu, 17 Sep 2026 20:35:49 +0000</pubDate>
      <link>https://dev.to/mosiddi/agent-mesh-what-it-assumes-about-your-agents-2j6j</link>
      <guid>https://dev.to/mosiddi/agent-mesh-what-it-assumes-about-your-agents-2j6j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A9xvvCDE9rUUKk5bF" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A9xvvCDE9rUUKk5bF" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most agent platforms have added identity this year. Almost all of it is a name in a config file. The agent calls itself billing-agent, the log records billing-agent, and the dashboard shows billing-agent. Nobody along that path can refuse the name, because nothing about it can be checked.&lt;/p&gt;

&lt;p&gt;Agent Mesh, the trust module in the Agent Governance Toolkit, starts somewhere more awkward. It will not let you create an agent at all unless you name a human who is answerable for it.&lt;/p&gt;

&lt;p&gt;Everything below was run against agentmesh 5.0.0, the current agent-governance-toolkit-core release on PyPI, on my own machine, and the source references are pinned to commit 675a556 so the line numbers stay put. The module ships inside agent-governance-toolkit-core, so install that rather than the bare name: the agentmesh project on PyPI is an unrelated package last touched in 2024.&lt;/p&gt;

&lt;h3&gt;
  
  
  You cannot mint an anonymous agent
&lt;/h3&gt;

&lt;p&gt;The factory method is AgentIdentity.create(), and sponsor is positional and required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;AgentIdentity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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;billing-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sponsor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ops@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&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;read:ledger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass something that is not an email address and it raises IdentityError before any key material is generated. There is no flag to skip it and no default. That single constraint is the design decision the rest of the module hangs off: every agent in the mesh traces to a person, and the registry indexes it, so get_by_sponsor() returns every agent a given human brought into existence.&lt;/p&gt;

&lt;p&gt;If you have ever tried to answer “who owns this service account” in a mature estate, you know that question is usually unanswerable by construction. Here it is answerable by construction instead.&lt;/p&gt;

&lt;p&gt;What you get back is an Ed25519 keypair and a DID derived from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;did:mesh:a4082046d180d169df25e65edd7232b4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thirty-two hex characters, one keypair, one sponsor.&lt;/p&gt;

&lt;h3&gt;
  
  
  The name is only worth what the signature is worth
&lt;/h3&gt;

&lt;p&gt;An identity that cannot be challenged is decoration. Agent Mesh’s handshake is three phases: the caller issues a challenge carrying a 32-byte nonce with a 30 second expiry, the peer signs a payload of challenge_id:nonce:response_nonce:did, and the caller verifies the signature against the public key in the DID.&lt;/p&gt;

&lt;p&gt;Signing and verification behave the way you would want, and it takes four lines to convince yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify correct : True

verify tampered : False

verify wrong key : False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tamper with one byte of the payload and verification fails. Present a signature from a different agent in the same organisation and it fails. That is the part that makes the name refusable, and refusable is the whole point.&lt;/p&gt;

&lt;p&gt;Credentials are separate from identity and short lived. The default TTL is 900 seconds, and capabilities are scoped strings with prefix wildcards, so a credential granting read:* answers True to read:ledger and False to write:ledger. Fifteen minutes is short enough that a leaked bearer token is a nuisance rather than an incident, and long enough that rotation is not in the request path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Av4tj19oxSX2ukppv" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Av4tj19oxSX2ukppv" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The piece I did not expect
&lt;/h3&gt;

&lt;p&gt;Buried in the handshake is an optional parameter called require_freshness. Turn it on and the challenge carries a second nonce, described in the source as an RFC 9334 freshness nonce for Evidence liveness proof. RFC 9334 is the RATS architecture, the attestation document, and this is the same construct attestation uses to prove a claim was produced now rather than replayed from a recording.&lt;/p&gt;

&lt;p&gt;It is not decorative. The freshness nonce is concatenated into the signed payload, it is checked on the other side, and a mismatch returns an explicit Freshness nonce mismatch (RFC 9334). It also bypasses the handshake result cache, so a caller that asks for freshness gets a live exchange rather than a memoised verdict from up to fifteen minutes ago.&lt;/p&gt;

&lt;p&gt;That matters more than it looks. It means an agent handshake and a hardware attestation can be made to answer the same question, at the same moment, under the same nonce. The identity layer and the attestation layer stop being two separate stories about the same agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where a team should point its scepticism
&lt;/h3&gt;

&lt;p&gt;Two things do not hold up, and both are about the gap between what is declared and what is enforced.&lt;/p&gt;

&lt;p&gt;The sponsor is asserted, not verified. Every identity carries sponsor_verified, and it is False on creation. Their own identity specification, section 7.3, says implementations SHOULD verify sponsors, by email confirmation, SSO or a directory, before granting trust above the standard tier. Nothing in the shipped module sets it to True, and no trust decision reads it. So the accountability chain that makes the module interesting is currently a well-formed string that nobody checked.&lt;/p&gt;

&lt;p&gt;Revocation is in-process and quiet about failure. The architecture table lists revocation propagation at five seconds or less as a key parameter, next to values that are genuinely enforced. In the code, REVOCATION_PROPAGATION_TARGET = 5 appears exactly once, at its own definition, and is never read. Revocation fires callbacks registered in the same process, inside a try that logs failures at debug level. I registered a callback that raises, revoked a credential, and revoke() returned True. The local credential was correctly invalidated. Whatever was supposed to hear about it was not told, and nothing said so.&lt;/p&gt;

&lt;p&gt;Neither makes the module unusable. Both change what you can claim while running it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A0HI-FGIyJwUE9e8t" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A0HI-FGIyJwUE9e8t" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do on Monday
&lt;/h3&gt;

&lt;p&gt;Run it in the shape it is honest about. Use it for identity, signature verification and short-lived scoped credentials, where it does exactly what it says. Set require_freshness=True on any handshake that gates a real action, because the cached path is the default and a fifteen minute old verdict is not a liveness proof. Treat sponsor_verified as your integration point rather than a feature you have been given, and wire it to whatever your directory already knows. And do not build a containment story on revocation propagation until you have added a callback that fails loudly, because today one that fails silently looks identical to one that worked.&lt;/p&gt;

&lt;p&gt;The check I would run first is the smallest one. Create an identity, sign a payload, flip a byte, and confirm verification fails. If that passes, the identity layer is real. Then revoke a credential with a deliberately broken listener and see what your own system does with a True that meant less than it looked.&lt;/p&gt;

&lt;p&gt;What would change my mind on the sponsor question is a trust score that refuses to rise while sponsor_verified is False. That is one conditional, it is already specified, and it would turn the most interesting assumption in the module into an enforced one.&lt;/p&gt;

</description>
      <category>aigovernance</category>
      <category>cryptography</category>
      <category>identity</category>
      <category>attestation</category>
    </item>
    <item>
      <title>Verified, with nothing hashed</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Thu, 17 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/verified-with-nothing-hashed-d1</link>
      <guid>https://dev.to/mosiddi/verified-with-nothing-hashed-d1</guid>
      <description>&lt;p&gt;&lt;em&gt;I ran the MCP Inspector's skill verifier against a server I wrote. A skill that advertises no digests gets the same word, the same JSON and the same exit code as one whose every file hashed clean.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three runs against a stdio server I wrote, one skill served three ways, same command each time.&lt;/p&gt;

&lt;p&gt;A manifest with a wrong digest: &lt;code&gt;mismatch&lt;/code&gt;, exit 7. The same manifest with the right digest: &lt;code&gt;verified&lt;/code&gt;, one file, exit 0. The checker works.&lt;/p&gt;

&lt;p&gt;Then the third case. The skill declares &lt;code&gt;"resources": "dynamic"&lt;/code&gt;, which SEP-2640 defines as generated content with no digests to publish. Stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"conformance"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"dynamic-resources"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"resources is &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;dynamic&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: the file set is generated, so no digest
is advertised and integrity cannot be verified."&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"frontmatter"&lt;/span&gt;&lt;span class="p"&gt;:[]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:[]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"verified"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stderr reads &lt;code&gt;Verified 1 skill and 0 files: no conformance errors.&lt;/code&gt; Exit 0.&lt;/p&gt;

&lt;p&gt;Integrity cannot be verified, and the outcome is verified. One JSON line, both statements.&lt;/p&gt;

&lt;p&gt;The flag is &lt;code&gt;--verify&lt;/code&gt; on the Inspector's CLI. Point it at a server and it runs SEP-2640's conformance, digest and frontmatter checks over the whole catalog and turns the answer into an exit code, which is the useful part: a server author can gate CI on it. Exit 7 is a violation, 8 is a catalog it could not finish reading.&lt;/p&gt;

&lt;p&gt;To that CI job, the dynamic case and the fully checked case are one event. Same exit code, same &lt;code&gt;ok&lt;/code&gt;, same &lt;code&gt;outcome&lt;/code&gt;. What carries the difference is a file count in a line on stderr and a warning row a consumer has to go looking for by severity. A pipeline reads the exit code.&lt;/p&gt;

&lt;p&gt;The tool's own README settles what the word is supposed to mean. Its outcome table defines &lt;code&gt;verified&lt;/code&gt; and exit 0 as &lt;em&gt;"Everything was checked and everything passed."&lt;/em&gt; In the dynamic report, &lt;code&gt;files&lt;/code&gt; is the empty array.&lt;/p&gt;

&lt;p&gt;It is worth being exact about what the run does. The Inspector did call &lt;code&gt;resources/read&lt;/code&gt;, and my server logged serving the 129 bytes. Those bytes are parsed for the frontmatter cross check and then dropped without being hashed, because the manifest declined to say what the hash should be. So something happened. Nothing was verified.&lt;/p&gt;

&lt;p&gt;This project has already ruled on this shape once, in its own code. &lt;code&gt;EXIT_CODES.SKILL_INCOMPLETE&lt;/code&gt; exists because a read that stopped at the size bound leaves entries unfetched, and &lt;em&gt;"reporting success for a manifest whose unread entries were never fetched is a false pass."&lt;/em&gt; That is a third outcome and a third exit code, invented so a job that tolerates oversized catalogs can allow 8 and still fail on 7. A catalog advertising no digests at all reaches the same false pass down a different road, and it is still filed under &lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The spec has a word for the state, and it is not that one. SEP-2640 says the &lt;code&gt;"dynamic"&lt;/code&gt; marker exists so a host "can tell a deliberately unverifiable skill from a malformed entry", and calls a &lt;code&gt;SKILL.md&lt;/code&gt; fetched under it "unverifiable" in the same sentence where an array-backed one is "digest-verified".&lt;/p&gt;

&lt;p&gt;Worth saying where the ceiling is even when the digest does check out. A match proves the listing and the body agree with each other. SEP-2640 states the limit in its own text: digests are unsigned and supplied by the same server that supplies the content, a match proves the two are consistent rather than that either is trustworthy, and hosts MUST NOT treat it as a security boundary. I word boundary grepped the whole 673,250 byte CLI bundle: &lt;code&gt;signature&lt;/code&gt; twice, both in comments about TypeScript types. &lt;code&gt;signed&lt;/code&gt;, &lt;code&gt;provenance&lt;/code&gt;, &lt;code&gt;attestation&lt;/code&gt;, &lt;code&gt;publisher&lt;/code&gt;: zero each. Nothing in this path carries authorship.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not survive checking
&lt;/h2&gt;

&lt;p&gt;The brief I started from said this landed in 2.7.0, published on 16 September. It did not. I installed 2.6.0, from 9 September, and its &lt;code&gt;--verify&lt;/code&gt; help text, its &lt;code&gt;dynamic-resources&lt;/code&gt; message and its summary headline are the same strings. The behaviour is eight days old. What 2.7.0 actually changed on this path was the client capability declaration, the configurable catalog budget and a path-less URI fix.&lt;/p&gt;

&lt;p&gt;I also drafted the fix as "add a third outcome value" before reading the review thread on PR 2293. There are already three. Round 15 replaced a two-state verdict with &lt;code&gt;verified&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt; and &lt;code&gt;incomplete&lt;/code&gt; for exactly this class of reason. The ask is a fourth, and the reason it is a small ask is that the precedent is theirs.&lt;/p&gt;

&lt;p&gt;Until something changes, the difference is readable today, and it is one field. If you gate CI on &lt;code&gt;--verify&lt;/code&gt;, exit 0 does not tell you that anything was hashed. This does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;... &lt;span class="nt"&gt;--method&lt;/span&gt; skills/list &lt;span class="nt"&gt;--verify&lt;/span&gt; | jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'select(.files | length == 0)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hit means a skill in that catalog was approved by a verifier that checked no bytes of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;"@modelcontextprotocol/inspector on npm, 2.6.0 and 2.7.0 publish times"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/inspector/blob/main/clients/cli/README.md" rel="noopener noreferrer"&gt;The CLI README's Skill verification section, where the outcome table lives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/inspector/pull/2293" rel="noopener noreferrer"&gt;Inspector PR 2293, the review round that introduced the three outcomes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2640-skills-extension.md" rel="noopener noreferrer"&gt;SEP-2640, the Skills extension, merged 13 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/inspector/issues/2405" rel="noopener noreferrer"&gt;Inspector issue 2405, where this went&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>mcp</category>
      <category>evidence</category>
    </item>
    <item>
      <title>Every Dependency Is a Promise Your System Has to Keep</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Thu, 17 Sep 2026 05:14:19 +0000</pubDate>
      <link>https://dev.to/mosiddi/every-dependency-is-a-promise-your-system-has-to-keep-336e</link>
      <guid>https://dev.to/mosiddi/every-dependency-is-a-promise-your-system-has-to-keep-336e</guid>
      <description>&lt;p&gt;&lt;em&gt;Scaling starts with examining how many things must go right for a customer to succeed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpkc6jl8nqla9f13889sp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpkc6jl8nqla9f13889sp.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Illustration generated with AI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A checkout request arrives. Before the customer sees confirmation, the system checks inventory, authorizes payment, fetches recommendations, updates loyalty points, and sends an email.&lt;/p&gt;

&lt;p&gt;Each integration has a reasonable explanation. Together, they raise an architectural question: &lt;strong&gt;why should buying something depend on all of them working at the same time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question belongs near the beginning of a scaling discussion. It determines how much work we need to scale in the first place.&lt;/p&gt;

&lt;p&gt;I think architecture reviews should give more attention to the conditions we attach to a successful customer outcome. Every synchronous dependency introduces another reason that outcome might be delayed or fail.&lt;/p&gt;

&lt;p&gt;Adding capacity can help a system do more work. We should also examine how much of that work belongs on the critical path.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A useful unit of architectural complexity is the number of things that must go right together.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The critical path is a business decision
&lt;/h3&gt;

&lt;p&gt;Consider the checkout example as a hypothetical design.&lt;/p&gt;

&lt;p&gt;Inventory and payment affect whether the business can accept an order. Recommendations usually do not. An email can arrive after the order is recorded. Loyalty points may tolerate a short delay, depending on the product’s rules.&lt;/p&gt;

&lt;p&gt;If these operations all block confirmation, the architecture has given them equal power to interrupt a purchase. That is a business decision, even if nobody explicitly made it.&lt;/p&gt;

&lt;p&gt;Moving email off the critical path changes that decision. Customers can complete an order while the notification service is unavailable.&lt;/p&gt;

&lt;p&gt;But the obligation to send the email still exists. It needs durable recording, retries, duplicate handling, and a way to detect messages that never arrive. Asynchronous processing introduces its own machinery.&lt;/p&gt;

&lt;p&gt;The trade is worthwhile when separating those failure modes matters enough to justify that machinery. &lt;strong&gt;A queue earns its place through the independence it provides.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why counting services or boxes on a diagram tells us so little. A system with fewer components can still force unrelated operations to succeed together. A system with more components can isolate failures well, or simply distribute the same dependencies across a network.&lt;/p&gt;

&lt;p&gt;The more useful measure is what happens to the customer when one component stops working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subtraction has a correctness floor
&lt;/h3&gt;

&lt;p&gt;There is also a limit to subtraction.&lt;/p&gt;

&lt;p&gt;Removing duplicate work can improve efficiency. Removing the check that prevents a duplicate charge changes the meaning of correctness. Both might reduce latency. Only one preserves the promise made to the customer.&lt;/p&gt;

&lt;p&gt;Before simplifying a workflow, I would write down its non-negotiable properties in business language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No duplicate charges.&lt;/strong&gt; Retrying an order must not create a second charge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable confirmation.&lt;/strong&gt; A confirmed order must survive an application restart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer isolation.&lt;/strong&gt; One customer must never receive another customer’s data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those statements establish a floor beneath the optimization work. The team can change implementation, sequencing, and infrastructure while preserving the properties that make the service trustworthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review the journey, then the components
&lt;/h3&gt;

&lt;p&gt;This gives architecture reviews a more concrete starting point.&lt;/p&gt;

&lt;p&gt;Take one important customer journey. Identify everything it waits for. For each dependency, explain why its result is necessary before success can be reported. Then examine what happens when it is slow, unavailable, or returns an ambiguous result.&lt;/p&gt;

&lt;p&gt;Some dependencies will deserve stronger protection. Some can move later. Some may exist because an old feature or assumption was never retired.&lt;/p&gt;

&lt;p&gt;The resulting work may be less visible than a platform migration. It can still produce a substantial architectural improvement: fewer opportunities for unrelated failures to interrupt the same customer action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make simplification visible
&lt;/h3&gt;

&lt;p&gt;Leadership incentives matter here. New infrastructure has a launch date and an owner. Removing an unnecessary dependency is easier to overlook. If we want teams to simplify, we need to recognize outcomes such as fewer coordinated releases, fewer failure modes reaching customers, and less operational work per completed transaction.&lt;/p&gt;

&lt;p&gt;The same reasoning applies when adding AI to a workflow. If a model becomes another blocking dependency, its latency and availability become part of the customer’s experience. Its place in the architecture should follow from the value of its contribution and the behavior required when that contribution is unavailable.&lt;/p&gt;

&lt;p&gt;Before approving the next component, I would ask the team to show which customer promise requires it, what happens when it fails, and which existing responsibility it replaces. Those answers make the cost of the decision visible.&lt;/p&gt;

&lt;p&gt;This is the thinking behind &lt;em&gt;Scale by Subtraction&lt;/em&gt;, which I explore in my book, &lt;a href="https://www.amazon.com/Architecting-Scale-Practical-Large-Scale-AI-Native/dp/1807420973" rel="noopener noreferrer"&gt;&lt;em&gt;Architecting at Scale&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>machinelearning</category>
      <category>artificialintelligen</category>
    </item>
    <item>
      <title>One repository answered the same question twice on 11 September</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Wed, 16 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/one-repository-answered-the-same-question-twice-on-11-september-3al1</link>
      <guid>https://dev.to/mosiddi/one-repository-answered-the-same-question-twice-on-11-september-3al1</guid>
      <description>&lt;p&gt;&lt;em&gt;Four hours apart, awslabs/mcp merged a parser-based read-only policy for one database server and twenty more regex keywords for another. I installed the published package and found the keyword answer still has a gap the parser answer cannot have.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On 11 September, four hours apart, the awslabs/mcp repository merged two fixes for the same class of problem, and they do not agree with each other.&lt;/p&gt;

&lt;p&gt;At 15:31 UTC, commit f6aec97 landed a parser-based read-only policy for the Postgres server (#4575). It deletes that server's regex mutable-SQL detector and replaces it with a guard built on pglast, which is PostgreSQL's own parser compiled in. The module says why in one line: identifiers, quoting, comments and Unicode escapes can no longer disguise an operation, because &lt;em&gt;the guard and the database read the same bytes the same way.&lt;/em&gt; The check for the statement behind CVE-2026-87911 is now a field on a parse-tree node, not a pattern.&lt;/p&gt;

&lt;p&gt;At 19:22 UTC, commit a55a1a8 answered the same problem for the MySQL server (#4615) by adding roughly twenty more keywords to a regex.&lt;/p&gt;

&lt;p&gt;Both are on main. Both are real work. Only one of them changes what kind of thing the control is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the difference is measurable
&lt;/h2&gt;

&lt;p&gt;The MySQL gate already took a CVE for this, CVE-2026-85788. AWS described it exactly: the check matches SQL text with patterns that expect literal whitespace between keywords, MySQL treats a comment as whitespace, so a comment wedged between two words walked straight through. The fix strips comments before the scan. It works. I checked it.&lt;/p&gt;

&lt;p&gt;The class is not closed, because a comment is not the only thing MySQL treats as whitespace. A second space is too.&lt;/p&gt;

&lt;p&gt;I installed the current published package, version 1.1.2, uploaded 8 September, and called the shipped read-only gate directly. It is two functions, and nothing else stands between a tool call and the database. One space between the two words of a multi-word keyword is rejected. Two spaces, a tab or a newline between the same two words returns an empty list from the keyword scan and an empty list from the injection heuristic, which together are the server's whole rejection path. I have sent the specific statements to the AWS vulnerability reporting page, which is where the repository's own contributing guide says security findings go instead of a public issue, so they are not in this note.&lt;/p&gt;

&lt;p&gt;The cause is one expression. The pattern is built by escaping the whole phrase, which bakes a single literal space into the middle of it. Most entries survive that because their first word is blocked on its own. &lt;code&gt;CREATE FUNCTION&lt;/code&gt; is covered because &lt;code&gt;CREATE&lt;/code&gt; is. A couple have no such cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repository already contains the fix
&lt;/h2&gt;

&lt;p&gt;Two of the four keyword detectors in the same monorepo, for SQL Server and Oracle, build the identical pattern by splitting the phrase on spaces and joining the words with a whitespace class. I ran Oracle's detector against every multi-word keyword it has, under one space, two spaces and a newline. Blocked every time.&lt;/p&gt;

&lt;p&gt;So the regex approach can be made whitespace-correct, and somebody in this repository already did it, in a file next door. The small fix is a two-line copy.&lt;/p&gt;

&lt;p&gt;Worth saying: the Aurora DSQL detector uses the same escape-the-whole-phrase construction as MySQL, and gets away with it only because a second set of anchored regexes catches the same statements a different way. It is the same latent bug, one server over, currently masked. All of this is still a small fix to a text filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the parser commit says about itself
&lt;/h2&gt;

&lt;p&gt;The Postgres guard is careful about its own status. It calls itself defense-in-depth, not a security boundary, and names the authoritative control as the least-privilege database role. AWS says the same in both advisories. That is the correct position, and it is worth sitting with, because it means the read-only switch in a tool server was never the enforcement point. The grant is. A log of what the agent asked for proves what happened; only the role decides what can happen.&lt;/p&gt;

&lt;p&gt;Which leaves a question the repository has now put to itself twice. The Redshift server switched to a parser in June. Postgres switched on 11 September. A parse tree cannot be fooled by whitespace, comments, case or Unicode escapes, because it is not reading text. A keyword list can be fooled by all four, and each one costs an advisory and a release to find out about. Four of these servers are still on the keyword list. Two of them have stopped, and the newer one's own comment explains why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/mcp/commit/f6aec97d0aa3b9caae0cd131dda4a63be5709f64" rel="noopener noreferrer"&gt;awslabs/mcp commit f6aec97, parser-based sql policy (#4575)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/mcp/commit/a55a1a8da1991767e114a7039b7245992b900b4b" rel="noopener noreferrer"&gt;awslabs/mcp commit a55a1a8, statement-leading mutating verbs (#4615)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/mcp/security/advisories/GHSA-x25m-ph3m-3r9q" rel="noopener noreferrer"&gt;CVE-2026-85788, GHSA-x25m-ph3m-3r9q, mysql read-only via inline comments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/mcp/security/advisories/GHSA-fph8-pg5w-78fv" rel="noopener noreferrer"&gt;CVE-2026-87911, GHSA-fph8-pg5w-78fv, postgres COPY TO PROGRAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/security/vulnerability-reporting/" rel="noopener noreferrer"&gt;AWS vulnerability reporting page, where this finding went&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>mcp</category>
      <category>sql</category>
    </item>
    <item>
      <title>The alias check I wired up was bypassed in September 2025</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-alias-check-i-wired-up-was-bypassed-in-september-2025-57kk</link>
      <guid>https://dev.to/mosiddi/the-alias-check-i-wired-up-was-bypassed-in-september-2025-57kk</guid>
      <description>&lt;p&gt;&lt;em&gt;DDRop landed on 14 September and walks past the platform appraisal policy I shipped three weeks ago. Reading it sent me to a paper from last year that had already walked past the same check, by the same four people, for under $50.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two things happened on 14 September. Google merged my documentation fix to go-sev-guest, closing the issue I filed in August about which &lt;code&gt;PlatformInfo&lt;/code&gt; fields are ceilings and which are floors. And DDRop was published.&lt;/p&gt;

&lt;p&gt;DDRop is a DDR5 RDIMM interposer from KU Leuven, ETH Zurich, Durham and Google, going to CCS in November. It injects parity errors on the command and address lines so the register clock driver rejects the command, then disconnects the ALERT line so the CPU is never told to re-issue it. Writes are silently discarded. The paper's first contribution line puts the build at under $200. Table 1 puts the commercial equivalent, Membuster on DDR4, at about $170,000.&lt;/p&gt;

&lt;p&gt;Intel TDX, Intel Scalable SGX and AMD SEV-SNP are all affected, because, in the authors' words, "to maintain memory performance, scalable cloud TEEs omit cryptographic freshness guarantees". The processor can tell that memory is encrypted. It cannot tell that it is current.&lt;/p&gt;

&lt;p&gt;Three weeks ago I shipped &lt;code&gt;appraise_platform_info&lt;/code&gt; into agent-manifest so a verifier could require &lt;code&gt;alias_check_complete&lt;/code&gt;, the bit where AMD firmware reports that its boot-time DRAM alias check finished and found nothing. I wrote in LIMITATIONS.md that this "lets a verifier insist the silicon says it did its own check".&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I got wrong, and it is not DDRop
&lt;/h2&gt;

&lt;p&gt;DDRop has overtaken the check. That framing also lets me off.&lt;/p&gt;

&lt;p&gt;The check was already bypassed when I shipped it. Battering RAM, disclosed on 30 September 2025, is a $47.62 DDR4 interposer that sits in the memory path "behaving transparently during startup and passing all trust checks", then flips to malicious at runtime. Its own summary says it "can circumvent Intel's and AMD's boot-time alias checks" and that it "breaks SEV's attestation feature on fully patched systems". AMD advised on it as SB-3024. Four of its authors are DDRop authors.&lt;/p&gt;

&lt;p&gt;So on 20 August I required a bit whose published bypass was ten months old, and I described it as the one platform assertion bearing on physical access. Across agent-manifest, trace-spec, cmcp and ca2a, and across all twelve notes on this site, the string &lt;code&gt;battering&lt;/code&gt; appears zero times. I had read about BadRAM and cited SB-3015. I had not read the paper that followed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DDRop adds
&lt;/h2&gt;

&lt;p&gt;DDRop is not the same attack. Aliasing by grounding address lines does not work on DDR5, where commands are multi-cycle and grounding two lines corrupts eight logical bits. Write suppression is the way in instead, at native bus speed with no downclocking.&lt;/p&gt;

&lt;p&gt;Then section 6.3 does something the alias check was never in the conversation for. Using injected secure page table entries, an attacker gets plaintext write access to its own TD's control structure, overwrites its own MRTD at offset 0x240 with the victim workload's expected measurement, and clears a validity flag at 0x3F2 to force a recompute. The authors confirm "the quote returned by TDG.MR.REPORT now contains the maliciously injected launch measurement". Every check my offline TDX verifier performs still passes, because the report is genuine and it is the measurement that is a lie. The paper also notes that cryptographic integrity mode would stop its other two case studies and not this one, since the forged structure is written under the correct key, and that its own test BIOS could not confirm that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the vendors actually said
&lt;/h2&gt;

&lt;p&gt;The two responses are not the same and they are being reported as though they were.&lt;/p&gt;

&lt;p&gt;AMD SB-3048 says the technique "falls outside the scope of the published threat model for SEV-SNP" and that AMD "does not plan to assign a CVE or release mitigations in response to this report". Intel's announcement also puts it outside the standard threat model, says nothing about CVEs either way, and then says Intel is evaluating hardening and detection, "including the development of Platform Owner Endorsements which enable remote parties to establish who is in physical possession of the hardware sensitive workloads are running on".&lt;/p&gt;

&lt;p&gt;The out-of-scope position is not news from this week either. Both vendors took it a year ago, for Battering RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I land
&lt;/h2&gt;

&lt;p&gt;The policy stays. It does what it says, and requiring the bit is still better than not requiring it, because it catches the lie BadRAM told, an SPD chip reporting a module larger than it is. What changes is the sentence around it, and LIMITATIONS.md now owes a line naming Battering RAM, which is the honest version of "does its own check".&lt;/p&gt;

&lt;p&gt;The mitigation vectors I declined to appraise in August are the next question rather than a closed one. I said I would be guessing without hardware; I have SEV-SNP silicon and go-sev-guest v0.15.0 ships reference minimums to validate a mask against.&lt;/p&gt;

&lt;p&gt;And I have been writing for months that no confidential computing silicon is custody grade against an adversary who owns the machine, treating custody as the thing outside the report by definition. A vendor has now said in a security advisory that it intends to put custody in the report. I do not know whether Platform Owner Endorsements will work, and a verifier cannot appraise a field that does not exist yet. But I stated that boundary as permanent, twice, and it is the second time in a month that something I filed under unattestable turned out to be somebody's roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked against
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ddropattack.eu/ddrop.pdf" rel="noopener noreferrer"&gt;DDRop paper, read 15 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ddropattack/ddrop" rel="noopener noreferrer"&gt;ddropattack/ddrop, the authors' repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://batteringram.eu/" rel="noopener noreferrer"&gt;Battering RAM, disclosed 30 September 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3048.html" rel="noopener noreferrer"&gt;AMD security bulletin SB-3048, revision 1.0 of 14 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3024.html" rel="noopener noreferrer"&gt;AMD security bulletin SB-3024, the Battering RAM advisory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.intel.com/content/www/us/en/security-center/announcement/intel-security-announcement-2026-08-11-001.html" rel="noopener noreferrer"&gt;Intel security announcement INTEL-2026-08-11-001-DDROP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/agentrust-io/agent-manifest/blob/main/LIMITATIONS.md" rel="noopener noreferrer"&gt;agent-manifest LIMITATIONS.md, the platform appraisal section&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/go-sev-guest/pull/200" rel="noopener noreferrer"&gt;go-sev-guest PR 200, merged 14 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>attestation</category>
      <category>sevsnp</category>
      <category>evidence</category>
    </item>
    <item>
      <title>Deep-dive: how the public TRACE Registry is built to be distrusted</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 14 Sep 2026 16:12:24 +0000</pubDate>
      <link>https://dev.to/mosiddi/deep-dive-how-the-public-trace-registry-is-built-to-be-distrusted-55b8</link>
      <guid>https://dev.to/mosiddi/deep-dive-how-the-public-trace-registry-is-built-to-be-distrusted-55b8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AkSOFu7p34mlDV0tt" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AkSOFu7p34mlDV0tt" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On September 9, the Cloud Security Alliance hosted a briefing on the emerging chain of custody for AI agents: verified identity, attenuated delegation, hardware-signed proof of what an agent actually ran, and a governance framework that appraises agents against that evidence. Alongside it, something concrete shipped. The TRACE trust registry went public, as an open repository plus a verifier on PyPI. There is no hosted service and no query endpoint, deliberately. TRACE is an agentrust-io open specification that OPAQUE implements, not an OPAQUE product. Repository and verifier: &lt;a href="http://github.com/agentrust-io/trace-registry" rel="noopener noreferrer"&gt;github.com/agentrust-io/trace-registry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What the registry is for, in one breath: AI agents increasingly act on behalf of people and companies, and the parties on the other side of those actions need more than a vendor’s word for what actually ran. A TRACE record is a signed evidence envelope describing what ran, under whose authority, and with what supporting attestations. Where hardware attestation is present, a verifier can validate that evidence rather than rely on the operator’s description. The registry is the public, append-only place those records are anchored, so a counterparty, an auditor or a regulator can later check what was claimed and when, without asking anyone’s permission. Think of it as the public notary column for agent attestations: the records stay wherever their owners keep them, and the registry pins their existence and their history.&lt;/p&gt;

&lt;p&gt;Opening the code was the point. In Imran’s words: “you cannot ask anyone to trust a record you control. You publish it and let strangers check the math, including the ones who would enjoy catching you.”&lt;/p&gt;

&lt;p&gt;This post is the engineering deep-dive: how the registry is built so that you do not have to trust the people who run it. Including us.&lt;/p&gt;

&lt;h3&gt;
  
  
  The design problem
&lt;/h3&gt;

&lt;p&gt;A registry’s job is to be believed by strangers. But any registry publishes its own history. Signatures prove records are authentic, not that everyone sees the same set, which is the split-view problem. And “trust our database” fails exactly when it matters: disputes, audits, adversaries.&lt;/p&gt;

&lt;p&gt;The requirements that fall out: append-only history with cheap proofs at any scale, verification that works offline, nothing sensitive leaving anyone’s infrastructure by default, and an integrity anchor that lives outside the operator.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it is built: an append-only log, checkpointed
&lt;/h3&gt;

&lt;p&gt;The registry’s checkpointed portion is an append-only Merkle Mountain Range, with the existing per-batch RFC 6962 inclusion proofs unchanged underneath. MMRs suit evidence logs. Appending never rewrites what came before, so a proof you saved last month still verifies today. Inclusion and consistency proofs stay small at any log size, and the structure suits local-first logs that sync later.&lt;/p&gt;

&lt;p&gt;On a cadence driven by activity rather than a heartbeat, the log emits a checkpoint of a few hundred content-free bytes: the log size, the accumulator root derived from the MMR peaks, the previous checkpoint’s size and commitment, the issuer key id, and a consistency proof back to the previous checkpoint. The registry signs it as deterministic JSON with Ed25519, matching the shipped capsule-emit shape. A COSE_Sign1 wire encoding is a coordinated follow-up across the implementations, not what ships today. No payloads, and no metadata about record contents. The chained reference to the previous checkpoint lets a verifier check, across consecutive checkpoints, that the log is a strict extension of its past, which is a consistency guarantee on top of integrity.&lt;/p&gt;

&lt;p&gt;Three things about this design are worth naming.&lt;/p&gt;

&lt;p&gt;Interop by construction. The registry and our own logs follow the same checkpoint discipline: a log size, a commitment over the accumulator, a chained reference to the previous checkpoint and an issuer key, under different field names and two wire formats. The registry signs deterministic JSON carrying a single peaks-derived root; the capsule ledger signs COSE_Sign1 carrying a peak list. What holds today is the same discipline, a published name mapping and two wires, with convergence on one wire as the coordinated follow-up. The disciplines match because they are the same construction. The registry’s checkpoint layer is the CLL append-only MMR with its signed, consistency-checkable checkpoints, contributed to the registry in the open by Action State Group and reviewed and merged by its maintainers (trace-registry #54), pinned against the same MMRIVER draft test vector as the capsule ledger. The registry’s verifier checks the witness’s receipts end to end; the witness checks the registry’s checkpoint signature before countersigning. The registry’s log carries TRACE records, the capsule ledger carries Agent Action Capsule records, and the checkpoint layer never sees either. That is why evidence from two different record formats ends up comparable instead of parallel.&lt;/p&gt;

&lt;p&gt;Disclose later. Nothing is pushed to a transparency service upfront. You keep evidence local, checkpoint it cheaply and continuously, and if a dispute or an audit ever needs a specific record, you disclose it then, already provably unrewritten since the checkpoint that covered it. Accountability without a firehose of data leaving your infrastructure.&lt;/p&gt;

&lt;p&gt;It is live. Checkpoint 1 is published and checkable: log_id trace-registry/v1, mmr_size 1, root 3af8ddf2c1f429bb4fc670437e48640887f60de809b18f8ccea55fefb0c6639a. The registry holds two entries today, one of them checkpointed, and checkpoint 1 sits at registry/2026/09/01.ndjson in the repository next to the verifier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Alcpj2v20-UJxZXo7" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Alcpj2v20-UJxZXo7" width="1024" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-witnessing: the part no operator can do alone
&lt;/h3&gt;

&lt;p&gt;Checkpoints are submitted to an independent witness, &lt;a href="http://witness.agentactioncapsule.org" rel="noopener noreferrer"&gt;witness.agentactioncapsule.org&lt;/a&gt;, free and public, operated by Action State Group: a different company, with different incentives, keeping its own log. The witness countersigns the checkpoints it is sent and publishes them in its own log.&lt;/p&gt;

&lt;p&gt;What that stamp says, exactly: it proves inclusion of the checkpoint under the witness’s key. Whether a receipt also carries a witness-observed time or a signed grade is a property of that receipt, so the registry’s verifier reports witness_time_established and grade_cryptographically_bound rather than asserting either. Read those two fields rather than this sentence. For the September 7 capture both are false, because that receipt signs the log root rather than a clock and reports its grade outside the signature, and those values are permanent for that receipt. The witness began signing a registration time and a grade on September 12, but it registers a checkpoint by a content-addressed hash, so submitting checkpoint 1 again returns the receipt it already has. The next checkpoint, when the log advances for a real reason, will carry both fields. Its signed grade will be countersigned-observed: this registry is enrolled as a foreign accumulator, which the witness observes and countersigns without verifying the registry’s own consistency proofs. The witness attests the bytes it saw. Continuity is a property a verifier computes across stamps, not one the witness certifies.&lt;/p&gt;

&lt;p&gt;On September 7 the registry submitted checkpoint 1’s existing JSON bytes through the enrolled route and fetched the stamp back by log id and by digest. All three responses carried the same receipt, leaf 936 of a 937-leaf witness tree, and the registry’s standalone verifier checked the registry signature, the checkpoint-to-receipt binding and the witness signature under a pinned key, offline. The captured evidence, the verifier and the negative tests that fail when either signature check is removed are in the registry repository.&lt;/p&gt;

&lt;p&gt;Action State Group is a witness here, not the witness. A log can anchor into several witnesses in parallel or run its own, and bring-your-own-witness stays first class. Witnessing a digest attests inclusion under the witness’s key and never endorses content, which is the same rule the witness applies to everyone. Plurality is an engineering property, not a courtesy. Because both logs are Merkle Mountain Ranges, a witness holds no entry data at all: a few hundred bytes per checkpoint, whatever the size of the log behind it. A witness enrolled to verify a log’s own accumulator can additionally check each checkpoint against its predecessor with a logarithmic consistency proof; this registry is enrolled the other way, by design: observed and countersigned, not verified. Either way the cost of being a witness stays small, which is why neither of us treats the witness seat as something to own. It is also the security argument: sustained equivocation means fooling every witness a verifier consults at once, so the number of independent witnesses is the parameter that matters, and the cost of being one is what sets that number.&lt;/p&gt;

&lt;p&gt;From the moment a checkpoint is countersigned, the covered history is pinned. Rewriting a record means producing a commitment that contradicts a checkpoint already sitting in someone else’s log, and showing different histories to different people means fooling independent operators at once.&lt;/p&gt;

&lt;p&gt;That cuts against the usual vendor script. With the witness in place, you do not have to trust OPAQUE, and you do not have to trust Action State either. Neither of us is asking to be believed.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you want to check: the whole audit, offline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Ar3wdXyNcKiWWZ8mb" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Ar3wdXyNcKiWWZ8mb" width="1024" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Who you must trust: no one’s word, only public keys and open-source math. Every step runs offline with published tooling, and all three checks are one install, pip install “trace-verify[witness]”: trace-verify for inclusion, trace-verify chain for the checkpoint chain, and trace-verify receipt for the witness receipt. None needs a clone of the registry. The capsule side runs the same three checks with its own tooling, against its own log and the same witness: inclusion, chain consistency, countersigned receipt. Same construction, two toolchains.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this proves, and what it does not
&lt;/h3&gt;

&lt;p&gt;Witnessing proves the integrity and consistency of the published history: what was recorded has not been rewritten, and the history the witness observed is pinned outside the registry operator. That does not by itself mean every reader was shown the same view; detecting a split view still requires comparing against the witness, another observer or another witness. It does not prove the log is a complete account of reality either, and more witnesses do not help: a party that never records something has nothing to witness. Closing that gap takes the other side of the exchange: the counterparty’s own record of it, and a way to ask for it where an answer, a refusal, and an absence are three different, attributable things rather than the same silence. That is the next piece of work.&lt;/p&gt;

&lt;p&gt;Two more limits, named outright. The registry’s June 2026 entry predates checkpointing and is deliberately never folded into the log retroactively, so the chain proves consistency of what it covers, not coverage of the registry. And the producer key behind the first anchored record verifies that record and will never sign another, because the demo that produced it generates keys per run. Trust infrastructure that overstates its guarantees is worse than none. The grade is signed under a private-use COSE label agreed bilaterally between the registry and the witness, not a registered one. A third implementer must not read it as standard, and it moves if a registered label is ever assigned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F744%2F0%2ACaiVDT57VtMWW-Ca" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F744%2F0%2ACaiVDT57VtMWW-Ca" width="744" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Check our work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The trace registry: &lt;a href="http://github.com/agentrust-io/trace-registry" rel="noopener noreferrer"&gt;github.com/agentrust-io/trace-registry&lt;/a&gt;, with the verifier and checkpoint 1 at registry/2026/09/01.ndjson.&lt;/li&gt;
&lt;li&gt;TRACE, an open specification series at LF Projects: &lt;a href="http://trace.agentrust-io.com" rel="noopener noreferrer"&gt;trace.agentrust-io.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The checkpoint mechanism: The Checkpointed Local Log, an IETF Internet-Draft in progress.&lt;/li&gt;
&lt;li&gt;The reference stack: capsule-emit, whose seal() produces signed, chained records; checkpointed-local-log, the MMR log and its checkpoints; and capsule-anchor, the witness service, one Apache-2.0 deployment you can run yourself.&lt;/li&gt;
&lt;li&gt;The verifiers and conformance vectors: &lt;a href="http://verify.agentactioncapsule.org" rel="noopener noreferrer"&gt;verify.agentactioncapsule.org&lt;/a&gt;, with vectors in the agent-action-capsule and checkpointed-local-log repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not have to use either of our services. Run the code and check the math.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open artifacts to build on
&lt;/h3&gt;

&lt;p&gt;The pieces are open standards on purpose, because what matters is what gets built on them, on both sides of the evidence. On the producing side, anyone already logging receipts, whether an agent platform, a marketplace or another registry, can get these properties without handing anyone their data. Where you wrote log(), write seal(): the record composes, chains and gets witnessed, and yesterday’s log file becomes evidence. A few hundred content-free bytes is the entire disclosure. On the consuming side, checkpoints, receipts and proofs are stable, specified artifacts rather than vendor APIs, so auditors’ checklists, GRC tooling and assurance frameworks can verify any conforming log with one implementation instead of one integration per vendor. Interoperable evidence is what makes an assurance ecosystem possible, and it is the layer we are both betting on.&lt;/p&gt;

&lt;p&gt;The specifications live where open work belongs, at the IETF (SCITT and COSE) and the Linux Foundation (TRACE), and the work continues there. More logs and more witnesses are welcome. Becoming a witness takes an honest clock and a public key: capsule-anchor is Apache-2.0, and enrolling a log’s declared wire form is one configuration entry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Agent actions are becoming the records that matter, and records only count when a disinterested party can vouch for their history. The witness attests inclusion, never content, on identical terms for this registry, for our own logs, and for anyone who shows up with a checkpoint.” Steven Mih, Action State Group&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>compliance</category>
      <category>traceability</category>
      <category>euaiact</category>
      <category>nistframework</category>
    </item>
  </channel>
</rss>
