<?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>The fix shipped three weeks before the bug had a name</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-fix-shipped-three-weeks-before-the-bug-had-a-name-3ggb</link>
      <guid>https://dev.to/mosiddi/the-fix-shipped-three-weeks-before-the-bug-had-a-name-3ggb</guid>
      <description>&lt;p&gt;&lt;em&gt;One coding agent answered repo-controlled git config with a confirmation prompt in August. Another answered the neighbouring case with a refusal. The gap between those two choices is the whole argument, and neither public record tells you which one you are running.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On 10 August, Qwen Code shipped v0.21.9. One line in its changelog, filed under Bug Fixes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Requires explicit confirmation for read-only Git commands when repository configuration executes external programs via &lt;code&gt;diff.external&lt;/code&gt; or &lt;code&gt;core.fsmonitor&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three weeks later that shape of bug got a name and a list of affected coding agents.&lt;/p&gt;

&lt;p&gt;The mechanism is not in dispute. Two CVE records describe it in the same terms. In goose, a repository whose &lt;code&gt;.git/config&lt;/code&gt; sets &lt;code&gt;core.fsmonitor&lt;/code&gt; to a command causes git to run that command during the index refresh that &lt;code&gt;git diff HEAD&lt;/code&gt; performs, before the agent contacts a model at all. In Hermes Agent, the same key fires on the &lt;code&gt;git status&lt;/code&gt; refresh triggered by sending any message. Both were fixed. Neither fix required the researchers to be clever; the repository simply supplies part of the command line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two vendors, one bug class, two different controls
&lt;/h2&gt;

&lt;p&gt;Qwen's answer is a confirmation prompt. Claude Code's changelog gives the other answer, from a neighbouring case. In v2.1.196 there is exactly one line marked Security:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;claude mcp list&lt;/code&gt;/&lt;code&gt;get&lt;/code&gt; no longer spawn &lt;code&gt;.mcp.json&lt;/code&gt; servers that a repo self-approved via a committed &lt;code&gt;.claude/settings.json&lt;/code&gt;; untrusted workspaces show Pending approval&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same class. A file committed inside a repository causes the agent to spawn a process during a command whose name suggests it only reads. &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;get&lt;/code&gt; sound as passive as &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;diff&lt;/code&gt;. The control chosen is different. It does not ask the developer whether this repository should be allowed to configure itself. It refuses, and marks the workspace pending.&lt;/p&gt;

&lt;p&gt;That gap is the argument. A confirmation prompt asks a developer to adjudicate &lt;code&gt;core.fsmonitor&lt;/code&gt; in a repository they opened ninety seconds ago, before they have read a line of it, while the thing they wanted was to start work. Everything needed to answer the question sits inside the artifact the question is about. Prompts of that shape get approved, and the people most likely to approve them are the ones who have seen the prompt before and had it be fine every previous time. A prompt is a control that degrades with familiarity. The control that holds is the one that never asks: run git with the repository's values for those keys unset, and the question does not arise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the public record cannot tell you
&lt;/h2&gt;

&lt;p&gt;Coverage credits Claude Code v2.1.196 with fixing the &lt;code&gt;core.fsmonitor&lt;/code&gt; issue. Two records a careful user would check disagree by saying nothing at all.&lt;/p&gt;

&lt;p&gt;The changelog is silent. I grepped the published file, all 6,362 lines. &lt;code&gt;fsmonitor&lt;/code&gt; does not appear. Neither does &lt;code&gt;diff.external&lt;/code&gt;, nor the string &lt;code&gt;core.&lt;/code&gt; anywhere in the file.&lt;/p&gt;

&lt;p&gt;The advisory database is also silent. goose and Hermes both have CVE records naming this exact mechanism. For Claude Code there is no advisory published in August or September 2026, and none listing a fixed version anywhere in the 2.1.19x range. The most recent is from 24 July.&lt;/p&gt;

&lt;p&gt;That is not evidence that no fix shipped. Silent fixes are ordinary and often correct. It is evidence of something narrower: from the changelog and the advisory database together, a person running v2.1.196 cannot establish whether they have this fix, while a person running goose or Hermes can establish it in one query. Anyone diffing changelogs to decide whether to upgrade gets no answer, and diffing changelogs is what most teams actually do.&lt;/p&gt;

&lt;p&gt;Worth adding that &lt;code&gt;.claude/settings.json&lt;/code&gt; has been an attacker-controlled trust input before. In March, CVE-2026-33068 described a malicious repository setting &lt;code&gt;permissions.defaultMode&lt;/code&gt; to &lt;code&gt;bypassPermissions&lt;/code&gt; in that committed file, silently skipping the trust dialog. Fixed in 2.1.53. The September line is the second time the same repo-controlled file has had to be defended against the same idea.&lt;/p&gt;

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

&lt;p&gt;The researchers' report is not in here. Their domain, and three write-ups of it, were unreachable, so every claim that originates with them stayed out: the name given to the disclosure, the count of affected agents, the tally of unpatched issues, and the specific claim that Qwen Code remains affected at v0.22.3. That last one is why this note does not tell you whether the August prompt held. Nine stable releases separate v0.21.9 from v0.22.3, and I can see the mitigation shipped and cannot see whether it was enough.&lt;/p&gt;

&lt;p&gt;One figure was wrong on the way in. The single Security line in v2.1.196 is one of &lt;strong&gt;nine&lt;/strong&gt; such lines in that changelog, not seven; the first count matched only the unbolded form and missed two written as &lt;code&gt;Security:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pull request 8645 landed on 10 August. Whoever wrote it was not responding to a disclosure, because there was not one yet. They found it, wrote one sentence, and shipped. That sentence then sat in a public changelog, naming both configuration keys, for three weeks, while the same key was still live in other agents. It was never hidden. It was filed under Bug Fixes.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/QwenLM/qwen-code/main/CHANGELOG.md" rel="noopener noreferrer"&gt;Qwen Code CHANGELOG, v0.21.9 (2026-08-10), the diff.external and core.fsmonitor line, PR 8645&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md" rel="noopener noreferrer"&gt;Claude Code CHANGELOG, v2.1.196, the single line marked Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.osv.dev/v1/vulns/CVE-2026-72718" rel="noopener noreferrer"&gt;CVE-2026-72718, goose CLI arbitrary command execution via git core.fsmonitor, fixed 1.44.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.osv.dev/v1/vulns/CVE-2026-71963" rel="noopener noreferrer"&gt;CVE-2026-71963, Hermes Agent RCE via git core.fsmonitor config injection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.osv.dev/v1/vulns/GHSA-mmgp-wc2j-qcv7" rel="noopener noreferrer"&gt;CVE-2026-33068, Claude Code workspace trust dialog bypass via repo-controlled .claude/settings.json, fixed 2.1.53&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>supplychain</category>
      <category>codingagents</category>
    </item>
    <item>
      <title>The finding was correct and eight weeks late</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Wed, 02 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-finding-was-correct-and-eight-weeks-late-15jo</link>
      <guid>https://dev.to/mosiddi/the-finding-was-correct-and-eight-weeks-late-15jo</guid>
      <description>&lt;p&gt;&lt;em&gt;A gap in the SEAT post-handshake attestation draft turned out to have been argued on the working group list in July, and specified in a closed issue a year before that. What I filed instead came from reading the RFCs the draft already cites.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The gap I had found was real, and somebody had already said so. Nathanael Ritz put it to the SEAT mailing list on 10 July, quoting the same sentence I had marked, making the argument in more detail than I had, and grounding it on the working group charter rather than on the document's abstract. Two people replied substantively the same day. My contribution would have been to say it again, later, and worse, to the people who had already had the conversation.&lt;/p&gt;

&lt;p&gt;That is the case for checking prior art before you verify anything, rather than after. Verification is the expensive step and it answers the second question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The document
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;draft-fossati-seat-expat&lt;/code&gt; moves remote attestation out of the TLS handshake and into RFC 9261 exported authenticators, after intra-handshake attestation was shown to permit relay attacks. Its own appendix gives the motive: the older design "does not bind the Evidence to the application traffic secrets, resulting in relay attacks".&lt;/p&gt;

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

&lt;p&gt;The abstract promises the approach "supports both the passport and background check models" while attestation "remains bound to the underlying communication channel". The Terminology section defines "attestation credentials" as covering both Evidence and attestation results. Every binding requirement in the document then names Evidence and only Evidence. A word-boundary grep for &lt;code&gt;result&lt;/code&gt; across the binding section returns zero. The passport branch's sole requirement is that the Attestation Result is correctly signed and meets policy.&lt;/p&gt;

&lt;p&gt;All true, and all previously observed. Ritz quoted that same passport bullet in July. Worse for my version, a comment on issue 21, closed, filed under the title "Misleading Finished Message" and ostensibly about a diagram, had already asked "What binds the attestation results to the handshake?" in August 2025 and specified the fix: carry &lt;code&gt;certificate_request_context&lt;/code&gt; to the Verifier as &lt;code&gt;rdata&lt;/code&gt; and have it reflected into the Attestation Result. Running &lt;code&gt;git log -S rdata&lt;/code&gt; over the draft source returns nothing. The mechanism was designed, recorded in a thread about a figure, and lost.&lt;/p&gt;

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

&lt;p&gt;The draft specifies the TLS exporter label twice, and spells it differently each time. Section 4 says the verifier recomputes the exporter value "using the label Attestation Binding". Section 5.1 says the invocation uses "the label Attestation", and prints &lt;code&gt;TLS-Exporter("Attestation", certificate_request_context, 32)&lt;/code&gt;. The label is an input to &lt;code&gt;Derive-Secret&lt;/code&gt;, so two implementations that each follow one section derive different values and every binding check between them fails. Both spellings are in the published -03.&lt;/p&gt;

&lt;p&gt;The part that makes it more than an editorial nit came from the draft's own citations. RFC 8446 section 7.5 says requirements for exporter label format are defined in section 4 of RFC 5705. That section says all label values "MUST be registered via Specification Required". Section 6 adds that IANA "MUST also verify that one label is not a prefix of any other label". &lt;code&gt;Attestation&lt;/code&gt; is a prefix of &lt;code&gt;Attestation Binding&lt;/code&gt;, so the two can never both be registered, whichever section the working group prefers. Neither is registered today; the draft's IANA section registers only the extension type, and the string &lt;code&gt;attestation&lt;/code&gt; does not appear anywhere in the TLS parameters registry.&lt;/p&gt;

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

&lt;p&gt;The novelty of the lead, entirely, for the reasons above. The claim was verified and the finding was spent.&lt;/p&gt;

&lt;p&gt;Also the assumption underneath it. I had been treating the requirement that Evidence carry a hash of the authenticator identity key as settled ground, the fixed half against which the passport half looked unfinished. Issue 58 is an open challenge to exactly that requirement, asking why the public key needs hashing into the binder at all when the exported key material is already bound to the session, with a co-author answering that he cannot say more until the formal analysis is done. Building on it as a solved baseline would have walked into a live dispute.&lt;/p&gt;

&lt;p&gt;Two counts were wrong in the version I started from: the file is 35,398 bytes rather than 34,900, and "Attestation Result" occurs 22 times rather than 20. Neither touches the load-bearing claim, which is that none of those occurrences falls in the binding or freshness sections.&lt;/p&gt;

&lt;p&gt;If you are reading an unfamiliar draft, the two greps that pay are these. Take every value it defines and check it against the registry that owns that namespace. Then take every specification it cites normatively and read what that document actually requires. Both are cheap, both produce findings its authors have already agreed to be bound by, and neither asks you to know anything they do not.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tls-attestation/exported-attestation/blob/main/draft-fossati-seat-expat.md" rel="noopener noreferrer"&gt;draft-fossati-seat-expat, source on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mailarchive.ietf.org/arch/msg/seat/Xwf2c1jfUWeGgHjWGFliKqtqANM/" rel="noopener noreferrer"&gt;SEAT list, The Passport Model-sized elephant in the room, 10 July 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tls-attestation/exported-attestation/issues/21" rel="noopener noreferrer"&gt;exported-attestation issue 21, comment of 4 August 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tls-attestation/exported-attestation/issues/58" rel="noopener noreferrer"&gt;exported-attestation issue 58, why the public key is hashed into the binder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8446.html#section-7.5" rel="noopener noreferrer"&gt;RFC 8446 section 7.5, Exporters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc5705.html#section-4" rel="noopener noreferrer"&gt;RFC 5705 sections 4 and 6, exporter label format and registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml" rel="noopener noreferrer"&gt;IANA TLS Parameters, Exporter Labels registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tls-attestation/exported-attestation/issues/63" rel="noopener noreferrer"&gt;exported-attestation issue 63, filed 2 September 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>attestation</category>
      <category>tls</category>
      <category>specreview</category>
    </item>
    <item>
      <title>What your agent can prove after the second hop</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 31 Aug 2026 18:42:16 +0000</pubDate>
      <link>https://dev.to/mosiddi/what-your-agent-can-prove-after-the-second-hop-3gb9</link>
      <guid>https://dev.to/mosiddi/what-your-agent-can-prove-after-the-second-hop-3gb9</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%2Adt5OrnT53IBqlP1w" 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%2Adt5OrnT53IBqlP1w" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A2A solved the part everyone hits first. Agents can find each other, describe what they do, and hand each other work without a bespoke integration per pair. The extension mechanism is the piece I keep coming back to, because it made the protocol extensible without making it heavy, and the team deliberately left trust decisions to the deployment instead of baking one model into the wire format. That restraint is why anyone can profile it, including us.&lt;/p&gt;

&lt;p&gt;So agent A delegates to agent B, and that mostly works. Then B delegates to C, and the question changes. Not “is this call authorized” but what can C check on its own, without asking anybody, including us.&lt;/p&gt;

&lt;p&gt;cA2A is our profile on A2A for calls that cross a trust boundary. Here is what it can answer today, and how I know.&lt;/p&gt;

&lt;h3&gt;
  
  
  The chain is a portable object
&lt;/h3&gt;

&lt;p&gt;A delegation chain is a root-to-leaf list of signed credentials. Each hop carries an issuer, a subject, a scope, a depth, a pointer to its parent, and an Ed25519 signature over the canonical body. Canonical means RFC 8785, so the bytes one implementation signs are the bytes another verifies. That is what makes the chain checkable by someone who did not build our runtime.&lt;/p&gt;

&lt;p&gt;When C receives a task that came through B, it checks nine things and raises on the first that fails, each with its own error code. The module docstring groups them as five invariants; nine is the count of distinct checks, and it is the more useful number if you are the one reading the failure. Signatures against their issuers. The root having no parent and depth zero. Each parent_id matching the previous credential_id, and each issuer matching the previous subject. Depth incrementing and staying under the limit. No credential_id repeating. Each validity window containing the evaluation time.&lt;/p&gt;

&lt;p&gt;Two of those nine carry most of the weight.&lt;/p&gt;

&lt;p&gt;Scope attenuation. Every hop’s scope must be a provable subset of its parent’s. That is the confused-deputy defense: without it, B accepts a narrow task from A and then acts with authority A never granted.&lt;/p&gt;

&lt;p&gt;Root pinning. A valid signature tells you who issued a chain, not whether you should care. A chain minted by an attacker is perfectly self-consistent: every signature verifies, every scope narrows correctly. C supplies its own set of trusted root issuers and fails closed when the root is not in it. Structural verification is spell-checking; the pinned anchor is what makes it authorization.&lt;/p&gt;

&lt;p&gt;Offline audit gets the same verifier with — at-time, so recorded evidence is checked at the moment the action was decided rather than the moment somebody is looking. A window that lapsed since then says nothing about validity when it mattered.&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%2Au4vBHt53W2NH7YrO" 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%2Au4vBHt53W2NH7YrO" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What that looks like end to end
&lt;/h3&gt;

&lt;p&gt;There is a runnable example in the repo, examples/cross-operator-delegation. A parent agent in domain A delegates a scoped task to a child in domain B, with independent keys on each side. It runs offline in a few seconds and checks twelve things:&lt;/p&gt;

&lt;p&gt;The child is delegated {task:read, task:write}. Its local Cedar policy permits {task:read, task:audit}. The effective scope is the intersection, {task:read}, so the child allows task:read and denies task:write even though task:write was legitimately delegated. Delegated authority is a ceiling, not a grant.&lt;/p&gt;

&lt;p&gt;The task is sealed to the child’s attested channel key. A silently swapped binary on the child is rejected, because its measurement no longer matches. Every hop emits a provenance record, the records form a hash-linked DAG, and reparenting a record breaks it. Then the CLI re-verifies the committed artifacts from scratch: ca2a verify-chain and ca2a verify-dag, both against an explicitly pinned root.&lt;/p&gt;

&lt;p&gt;I fixed that example this week, which is worth admitting because of what it says about the invariant. When we made root pinning mandatory, the change updated the two CLI calls in that demo and missed the three in-process ones. The demo failed with UntrustedDelegationRoot, which reads like the example is wrong about delegation when it was simply not passing the root it already had in hand. It is 12/12 now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attestation, on real silicon, this week
&lt;/h3&gt;

&lt;p&gt;The part I most wanted to stop hedging about. Verifying somebody else’s captured quote proves your verifier parses. It says nothing about whether your code can produce that evidence on a real machine.&lt;/p&gt;

&lt;p&gt;So this week both collectors ran on genuine confidential VMs.&lt;/p&gt;

&lt;p&gt;On a GCP C3 instance, Intel TDX, the guest identified itself and our provider produced an 8,000-byte DCAP v4 quote whose report data matched the binding we asked for. Our verifier then appraised it to the Intel SGX Root CA, giving a non-zero 48-byte MRTD.&lt;/p&gt;

&lt;p&gt;On a GCP N2D pinned to AMD Milan, our provider produced a 1,184-byte SEV-SNP report, with the certificate table arriving alongside it: VCEK, SEV-Milan, and the self-signed ARK-Milan. Our verifier appraised it to that root and failed closed on a single flipped bit in the signed body.&lt;/p&gt;

&lt;p&gt;Both quotes produced by our own code, both appraised by our own code, to the vendor roots. The captures are not committed, because a SEV-SNP report’s 64-byte CHIP_ID is a per-CPU hardware identifier.&lt;/p&gt;

&lt;h3&gt;
  
  
  The thing nobody was checking
&lt;/h3&gt;

&lt;p&gt;A SEV-SNP report says four things well: it is authentic, it chains to AMD, its signature verifies, and it describes this measurement. Those are the right four checks and they are not in dispute.&lt;/p&gt;

&lt;p&gt;None of them ask what kind of machine it came from.&lt;/p&gt;

&lt;p&gt;The report carries that separately, in a field at offset 0x40. Whether SMT is enabled. Whether ECC is on. Whether the firmware completed its boot-time DRAM alias check, which is AMD’s mitigation for BadRAM. Our verifier read past that field and did not look.&lt;/p&gt;

&lt;p&gt;It looks now. You pass a policy: which fields must be true, which must be false. The direction lives in the argument name rather than the field name, which sounds fussy until you see the alternative. The reference verifier keeps one struct of booleans documented as “the maximum of acceptable PLATFORM_INFO data” and then enforces four of its seven fields as minimums, so setting a field to true does not permit that condition, it demands it. With require and forbid as separate arguments you cannot make that mistake.&lt;/p&gt;

&lt;p&gt;Then we pointed it at the real GCP host, and it reported 0x25:&lt;/p&gt;

&lt;p&gt;The DRAM alias check is complete, so BadRAM is mitigated and require_platform={“alias_check_complete”} passes on GCP. ECC is on. And SMT is enabled, so forbid_platform={“smt_enabled”} rejects that host.&lt;/p&gt;

&lt;p&gt;That is not a defect in GCP and it is not a defect in us. It is the point. Before this week, that report verified exactly as cleanly as one from a machine with SMT off and the alias check never run. If SMT matters to your threat model, you now have a way to say so and a way to find out. If it does not, nothing changes and the appraisal stays off, because it is opt in and appraises nothing by default.&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%2AGZoX9AEtTqe7sBV-" 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%2AGZoX9AEtTqe7sBV-" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is still open, honestly
&lt;/h3&gt;

&lt;p&gt;Software mode is still the default, where a peer key is accepted at assurance=”none”. The hardware path is validated, not automatic. Mutual attestation is implemented but off by default and not yet simultaneous: the caller commits a sealed payload before the callee has appraised it. And the sealed channel is per hop by construction, so a payload is plaintext inside every intermediate that acts on it. Authority composes across hops. Confidentiality terminates at each one.&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%2A2GDBY_O-nU9KOmar" 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%2A2GDBY_O-nU9KOmar" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What I would do on Monday
&lt;/h3&gt;

&lt;p&gt;Pin your root issuers and fail closed, because signature validity is not authorization and the gap between them is where confused-deputy bugs live. Evaluate credential windows at decision time when you audit. And if you run confidential workloads anywhere, go and read PLATFORM_INFO on one of your own hosts.&lt;/p&gt;

&lt;p&gt;I would genuinely like to know what your cloud says. Ours said SMT was on, and we had been verifying those reports for weeks without noticing.&lt;/p&gt;

</description>
      <category>confidentialcomputin</category>
      <category>agents</category>
      <category>cryptography</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Two normative sentences went missing when one tasks spec superseded another</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 31 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/two-normative-sentences-went-missing-when-one-tasks-spec-superseded-another-3b36</link>
      <guid>https://dev.to/mosiddi/two-normative-sentences-went-missing-when-one-tasks-spec-superseded-another-3b36</guid>
      <description>&lt;p&gt;&lt;em&gt;The MCP tasks extension requires an authorization check on every task request. Its own rationale says that check is often impossible, the sentence that used to make servers disclose that is gone, and no error code represents a denial. One of the two losses was already found and fixed by somebody else three weeks ago.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The MCP tasks extension defines three protocol error codes. &lt;code&gt;-32602&lt;/code&gt; for an invalid or nonexistent task ID, &lt;code&gt;-32603&lt;/code&gt; for internal errors, &lt;code&gt;-32021&lt;/code&gt; for a missing client capability. Its Security Considerations require servers to perform an authorization check on every task-related request. None of the three codes represents that check failing.&lt;/p&gt;

&lt;p&gt;So a server that runs the mandated check and denies has nothing to return that says it denied. It returns &lt;code&gt;-32602&lt;/code&gt;, which is also the answer for a task that never existed. A denial and a typo are the same response.&lt;/p&gt;

&lt;p&gt;That may be deliberate. There is a decent anti-enumeration argument for it, and the extension has no &lt;code&gt;tasks/list&lt;/code&gt; for related reasons. But it is written down nowhere, so nobody implementing to the spec can tell a design decision from a hole.&lt;/p&gt;

&lt;p&gt;I filed that and one other point as issue 20 this afternoon. Checking whether they were already raised is most of what follows, and it changed what I filed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that took reading two repositories
&lt;/h2&gt;

&lt;p&gt;The requirement and its rationale do not live in the same document.&lt;/p&gt;

&lt;p&gt;The Security Considerations bullet is in &lt;code&gt;ext-tasks&lt;/code&gt;, the extension's own repo. The paragraph explaining when it can actually be satisfied is in SEP-2663, in the main specification repo, and says this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;all tasks should be bound to some sort of "authorization context," the implementation of which is left to individual servers according to their existing bespoke permission models. However, in many cases, it is not possible to perform this binding, in which case the task ID becomes the only line of defense against contamination.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With sessions removed from the protocol by SEP-2567, there is, in the SEP's words, "no other natural scope a server can define unilaterally". &lt;code&gt;grep -ci motivation&lt;/code&gt; on the ext-tasks specification returns zero. An implementer working from the extension repo sees a MUST with none of the context that bounds it.&lt;/p&gt;

&lt;p&gt;In those deployments the mandated check reduces to the bearer check the neighbouring bullet permits, where a server MAY use task IDs as bearer tokens. Possession of the handle becomes the authorization. That is a different property from the one the MUST describes, and the document uses the vocabulary of the stronger one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Somebody already found half of this
&lt;/h2&gt;

&lt;p&gt;The auth binding bullet was not in the extension at all until eleven days ago.&lt;/p&gt;

&lt;p&gt;Rich Smith opened PR 9 on 31 July, titled "restore auth binding requirement dropped during SEP-2663 port". It was merged on 20 August. His argument: the port carried three of the four Security Implications bullets and dropped the second, leaving &lt;code&gt;tasks/get&lt;/code&gt;, &lt;code&gt;tasks/update&lt;/code&gt; and &lt;code&gt;tasks/cancel&lt;/code&gt; with no specified authorization requirement. In his words, an unguessable task ID was the only control the specification mandated.&lt;/p&gt;

&lt;p&gt;He was right and it is fixed. I did not expect the second loss underneath it.&lt;/p&gt;

&lt;p&gt;SEP-1686, the tasks proposal this extension supersedes, had a rule for exactly the case the Motivation paragraph describes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Receivers that do not implement session or authentication binding SHOULD document this limitation clearly, as task results may be accessible to any requestor that can guess the task ID.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is not in SEP-2663 and not in the extension. The requirement to bind survived the transition, after being restored by hand. The requirement to disclose when you cannot bind did not survive, and nobody has restored it.&lt;/p&gt;

&lt;p&gt;Two normative sentences from the same section went missing in the same supersession. One was caught by a contributor doing a bullet-by-bullet diff. The other is still gone.&lt;/p&gt;

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

&lt;p&gt;The framing I started with was that the document contradicts itself. It does not, because the two halves are in different repositories, and on the repo where the requirement lives the rationale is simply absent. That is a weaker claim and a more useful one: this is a porting loss, not a drafting error, and it has the same signature as the one already fixed.&lt;/p&gt;

&lt;p&gt;I also could not claim the error taxonomy point was novel. The same shape was raised during SEP-2663 review for &lt;code&gt;requestState&lt;/code&gt; integrity failures, and the maintainers resolved it on 4 May by allowing a synchronous error path as SHOULD statements. That is why two lines in Protocol Errors read SHOULD today. The authorization case is the one that conversation did not cover.&lt;/p&gt;

&lt;p&gt;And the Agents WG settled the governing principle three days ago, on 28 August, for a different proposal: operation-specific error codes should be defined, reusing an existing protocol code where an appropriate one exists. Pointing at a decision a group has already made is worth more than arriving with an argument they have not asked for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check worth stealing
&lt;/h2&gt;

&lt;p&gt;When a specification says it supersedes another, diff the security sections rather than reading the new one. Two separate losses came out of one transition here, and the one that was caught was caught by somebody comparing bullets by hand.&lt;/p&gt;

&lt;p&gt;The roadmap published on 22 August wants servers recognising agent identities built on existing standards "rather than pasted API keys and long-lived tokens", and names maturing this extension so it can move into the core specification. In the draft schema &lt;code&gt;ttlMs&lt;/code&gt; is &lt;code&gt;number | null&lt;/code&gt;, null documented as unlimited. A handle that may be a bearer token and need never expire is worth reconciling with that sentence while the document is cheap to change.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md" rel="noopener noreferrer"&gt;SEP-2663 Tasks Extension, merged 15 May 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md" rel="noopener noreferrer"&gt;ext-tasks specification/draft/tasks.md, Security Considerations and Protocol Errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-tasks/pull/9" rel="noopener noreferrer"&gt;ext-tasks PR 9, restore auth binding requirement dropped during SEP-2663 port&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/agents-wg/blob/main/proposals/1686-tasks.md" rel="noopener noreferrer"&gt;SEP-1686 tasks proposal, section 8.1 Task Isolation and Access Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/agents-wg/blob/main/meetings/2026-08-28.md" rel="noopener noreferrer"&gt;Agents WG meeting notes, 28 August 2026, error contracts for agent operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-tasks/blob/main/schema/draft/schema.ts" rel="noopener noreferrer"&gt;ext-tasks schema/draft/schema.ts, Task interface and ttlMs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/mcp-roadmap/" rel="noopener noreferrer"&gt;MCP roadmap, 22 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-tasks/issues/20" rel="noopener noreferrer"&gt;ext-tasks issue 20, filed 31 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>agentsecurity</category>
      <category>specreview</category>
    </item>
    <item>
      <title>Your telemetry and your evidence are not the same record</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 24 Aug 2026 22:50:17 +0000</pubDate>
      <link>https://dev.to/mosiddi/your-telemetry-and-your-evidence-are-not-the-same-record-3g2o</link>
      <guid>https://dev.to/mosiddi/your-telemetry-and-your-evidence-are-not-the-same-record-3g2o</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%2ACJJRotjFvJUFx-ki" 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%2ACJJRotjFvJUFx-ki" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last month we wired cMCP’s audit chain into OpenTelemetry. It took about a day, it is opt in behind a single environment variable, and it exports digests rather than payloads. A gateway operator now watches policy decisions in the same dashboard they already watch everything else in, with no new tooling and no new place to look.&lt;/p&gt;

&lt;p&gt;That is the boring half. The interesting half is what the gateway is recording underneath, because one blocked tool call produces three different records, deliberately, and each one exists for a different reader.&lt;/p&gt;

&lt;h3&gt;
  
  
  What one blocked call actually produces
&lt;/h3&gt;

&lt;p&gt;An agent calls a tool through the gateway. Cedar evaluates the policy bundle, and the call is refused with an authority named who could approve it. Three things happen.&lt;/p&gt;

&lt;p&gt;The audit chain gets the authoritative entry. It is hash linked, so each entry carries the digest of the one before it. Insert, delete or alter an entry and the chain stops verifying from that point forward. It records the decision, the rule that matched, the tool, the server identity, the latency, and digests of the request and response. Not the payloads. Digests.&lt;/p&gt;

&lt;p&gt;Telemetry gets a mirror of that entry. The span carries a nineteen field allowlist, chosen field by field, so that adding a new field to an audit entry does not silently start exporting it. request_payload_hash and response_payload_hash are on that list. The payloads themselves are not, and cannot be, because the allowlist is the whole mechanism. Blocked decisions mark the span as an error, so a dashboard can alert on refusals without parsing attributes.&lt;/p&gt;

&lt;p&gt;The TRACE Claim gets the portable version. This is the artifact you hand somebody who does not trust you. It is signed, it carries the policy bundle hash that was in force, and it can be checked by a verifier the counterparty already has, without calling us and without trusting the operator who ran the workload.&lt;/p&gt;

&lt;p&gt;So: your on call engineer reads the span, your incident review reads the chain, and your auditor reads the claim. One event, three readers, three formats built for them.&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%2AyfTLk2dQsTmvSNil" 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%2AyfTLk2dQsTmvSNil" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why that is the design rather than an accident
&lt;/h3&gt;

&lt;p&gt;The instinct is to say this is duplication and should be collapsed into one record. It should not, and the reason is worth being precise about.&lt;/p&gt;

&lt;p&gt;The audit chain is authoritative because it is tamper evident and local. The span is convenient because it lands in infrastructure you already run. The claim is portable because it is signed and versioned, which means somebody can verify it in two years with a tool built today.&lt;/p&gt;

&lt;p&gt;Those three properties pull in different directions. A record optimised for tamper evidence is not optimised for a dashboard. A record optimised for a dashboard has no reason to be stable across versions. A record optimised for a stranger to verify has to hold its shape for years, which is exactly the constraint that makes it a bad place to put whatever field you thought of this morning.&lt;/p&gt;

&lt;p&gt;The useful principle: the record that is easiest to enrich is the one with the least at stake. The span vocabulary can get richer whenever we like, because nothing outside the process depends on its shape. The signed claim cannot, because the entire point of evidence is that somebody else verifies it later with a tool they already have.&lt;/p&gt;

&lt;p&gt;Once you see that, the three records stop looking like duplication and start looking like three different contracts with three different parties.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this gives you today
&lt;/h3&gt;

&lt;p&gt;If you run a gateway with this on, a blocked call is visible in your existing observability stack within a second, correlated with the trace your application already emits, without shipping a payload anywhere. That is the operational win and it costs one environment variable.&lt;/p&gt;

&lt;p&gt;The evidence win is larger and slower. Because the claim binds the policy bundle hash, “the agent was refused under this exact policy” becomes something a counterparty can check rather than something you assert in a meeting. AARM v1.0 asks a policy engine for five decision types, and cMCP produces all five through an explicit crosswalk rather than string literals scattered through the evaluator, so an assessor can read the mapping in one file and check it against the bundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  The honest limit
&lt;/h3&gt;

&lt;p&gt;Two of the five decision types are classified rather than enforced. defer records a blocked call with its advice payload attached, but the gateway does not hold the request open pending an out of band decision, because it has no callback registry. step_up blocks and names an authority, with no in band re submission path yet. Both are in &lt;a href="http://LIMITATIONS.md" rel="noopener noreferrer"&gt;LIMITATIONS.md&lt;/a&gt; rather than a footnote. And a claim narrows step_up and defer to deny, because the v1.0 decision vocabulary predates AARM and widening it would make older verifiers reject claims they should accept. The audit chain and the span both keep the finer value, so the distinction survives where it matters operationally. Separately, pip install cmcp currently resolves a cryptography carrying two advisories reachable through certificate chain verification, pending an upstream release, and that is tracked in the open at &lt;a href="https://github.com/agentrust-io/cmcp/issues/471" rel="noopener noreferrer"&gt;https://github.com/agentrust-io/cmcp/issues/471&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where this is going
&lt;/h3&gt;

&lt;p&gt;The wider problem is that governance facts live in the wrong places. Policy decisions in a policy engine’s logs, approvals in a database, cost in a billing module, none of it correlated with the trace your application already emits.&lt;/p&gt;

&lt;p&gt;So we put the contract in the open this week, at &lt;a href="https://github.com/agentrust-io/agentrust-telemetry" rel="noopener noreferrer"&gt;https://github.com/agentrust-io/agentrust-telemetry&lt;/a&gt;. It is backend neutral and deliberately not a tracing backend, a policy engine, an agent framework or a dashboard. You keep all of those. It defines six event families, including an evidence lifecycle that carries run completeness and TRACE finalization status, as JSON Schema with conformance fixtures and an independent runner, so you can check an implementation rather than take its word. It is alpha, and I am putting it out at that maturity on purpose, because the argument worth having is which facts belong in the contract at all, and that is much cheaper before there is an API to keep working.&lt;/p&gt;

&lt;p&gt;Here is the question I do not have a settled answer to. If your telemetry and your evidence disagree about the same event, which one is authoritative? We say the audit chain is, and telemetry is a mirror. But the mirror is the more detailed object, and I can imagine an incident where the span is the only place a distinction was preserved.&lt;/p&gt;

&lt;p&gt;If you run policy enforcement anywhere in your stack, go and diff the two records for one blocked call. I would genuinely like to know whether anyone else’s line up.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>confidentialcomputin</category>
      <category>aigovernance</category>
      <category>agents</category>
    </item>
    <item>
      <title>The tool list was advertised, the resolver decided</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 24 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-tool-list-was-advertised-the-resolver-decided-413d</link>
      <guid>https://dev.to/mosiddi/the-tool-list-was-advertised-the-resolver-decided-413d</guid>
      <description>&lt;p&gt;&lt;em&gt;Spring AI released a fix on 21 August for a bug where a tool absent from the request could still be called. The vulnerable dispatch is one line. The public fix exists on one of the three affected release lines, and the fallback left almost nothing useful in the log.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is the vulnerable path in &lt;code&gt;DefaultToolCallingManager&lt;/code&gt; at tag &lt;code&gt;v2.0.0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ToolCallback&lt;/span&gt; &lt;span class="n"&gt;toolCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolDefinition&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findFirst&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseGet&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbackResolver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolName&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;toolCallbacks&lt;/code&gt; is the list attached to this request. It is also the list advertised to the model.&lt;/p&gt;

&lt;p&gt;If the model returns a name that is absent from that list, execution does not stop. The manager falls through to a resolver that has no knowledge of the request.&lt;/p&gt;

&lt;p&gt;Spring's advisory describes the distinction precisely: the per-request tool list was advertised as a boundary but was not fully enforced during dispatch.&lt;/p&gt;

&lt;p&gt;Sit with the word &lt;em&gt;advertised&lt;/em&gt;. The boundary was something said to the model. The thing that actually decided was a lookup in a map.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the map holds
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;StaticToolCallbackResolver&lt;/code&gt; holds a map keyed by tool name. Its &lt;code&gt;resolve&lt;/code&gt; operation is a lookup in that map. &lt;code&gt;DelegatingToolCallbackResolver&lt;/code&gt; walks its configured resolvers and returns the first non-null result. Neither carries the request's advertised tool set.&lt;/p&gt;

&lt;p&gt;For the built-in resolvers, the per-request list governed what the model could see. The resolver governed what the process could execute.&lt;/p&gt;

&lt;p&gt;The evidence gap follows the enforcement gap. A successful fallback produced no fallback-specific log in the manager. The static resolver emits a debug message, but the message does not include the tool name.&lt;/p&gt;

&lt;p&gt;Enforcement did not run. Separately, the evidence needed to reconstruct the bypass was not written either. An audit log cannot tell you which unadvertised tool was resolved if the resolution event never names it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the fix
&lt;/h2&gt;

&lt;p&gt;At &lt;code&gt;v2.0.1&lt;/code&gt;, the dispatch line became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseGet&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resolutionFallbackEnabled&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbackResolver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is now &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the right default for a library. It is still worth naming what kind of control it is: an in-process default that an application can reverse.&lt;/p&gt;

&lt;p&gt;Spring documents two ways to restore the old behaviour. Direct users can set &lt;code&gt;.resolutionFallbackEnabled(true)&lt;/code&gt; on the builder. Spring Boot users can set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.tools.resolution.fallback.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamic tool resolution is a legitimate requirement. But once fallback is restored, the request's advertised list is no longer the complete execution boundary. That choice needs its own policy and evidence, not merely a configuration value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which line got the public fix
&lt;/h2&gt;

&lt;p&gt;The advisory identifies three affected release lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1.0.0&lt;/code&gt; through &lt;code&gt;1.0.9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1.1.0&lt;/code&gt; through &lt;code&gt;1.1.8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2.0.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its fix table contains four versions. &lt;code&gt;2.0.1&lt;/code&gt; is marked OSS. &lt;code&gt;2.0.0.1&lt;/code&gt;, &lt;code&gt;1.1.9&lt;/code&gt;, and &lt;code&gt;1.0.10&lt;/code&gt; are marked Enterprise Support Only.&lt;/p&gt;

&lt;p&gt;That is a published commercial support model doing what it says it does. It is not a scandal. It does change the usual "upgrade to 2.0.1" summary.&lt;/p&gt;

&lt;p&gt;For someone already on &lt;code&gt;2.0.0&lt;/code&gt;, the public fix is a patch bump. For someone on either affected 1.x line, the publicly available route is a major-version migration. The advisory's table says that plainly.&lt;/p&gt;

&lt;p&gt;I also dropped the CVSS number. The vector published by Spring did not reconcile with one score presented during checking, so this note uses Spring's &lt;strong&gt;Medium&lt;/strong&gt; severity label and makes no numerical claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I sit in it
&lt;/h2&gt;

&lt;p&gt;cMCP enforces policy where tool calls cross its gateway. Its limitations already say that the gateway controls the tool boundary, not the model boundary, and that protection does not apply when a call bypasses the gateway.&lt;/p&gt;

&lt;p&gt;A callback resolved from a local Java registry is an in-process method call. It never crosses an MCP boundary. My gateway would not see it, deny it, or place it in its audit chain.&lt;/p&gt;

&lt;p&gt;That does not make the gateway control wrong. It identifies its enforcement domain.&lt;/p&gt;

&lt;p&gt;A control plane that observes calls leaving a process governs one class of tool execution. CVE-2026-59318 lived in the other class. I do not know how many deployed applications re-enable the fallback or otherwise resolve tools inside the process.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spring.io/security/cve-2026-59318/" rel="noopener noreferrer"&gt;Spring advisory CVE-2026-59318, published 20 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/blog/2026/08/21/spring-ai-2-0-1-available-now/" rel="noopener noreferrer"&gt;Spring AI 2.0.1 release announcement, 21 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-ai/reference/upgrade-notes.html#_tool_resolution_fallback_disabled_by_default" rel="noopener noreferrer"&gt;Spring AI 2.0.1 upgrade notes, tool resolution fallback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java" rel="noopener noreferrer"&gt;DefaultToolCallingManager at v2.0.0, the vulnerable fallback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-ai/blob/v2.0.1/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java" rel="noopener noreferrer"&gt;DefaultToolCallingManager at v2.0.1, the disabled-by-default fallback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-ai/blob/v2.0.1/spring-ai-model/src/main/java/org/springframework/ai/tool/resolution/StaticToolCallbackResolver.java" rel="noopener noreferrer"&gt;StaticToolCallbackResolver at v2.0.1, registry lookup and debug log&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://repo1.maven.org/maven2/org/springframework/ai/spring-ai-model/" rel="noopener noreferrer"&gt;Maven Central, public spring-ai-model versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/agentrust-io/cmcp/blob/main/LIMITATIONS.md" rel="noopener noreferrer"&gt;cMCP LIMITATIONS.md, gateway enforcement boundary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agentsecurity</category>
      <category>toolcalling</category>
      <category>enforcement</category>
    </item>
    <item>
      <title>The MCP registry checks that the repository URL looks like GitHub</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:00:19 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-mcp-registry-checks-that-the-repository-url-looks-like-github-2bpp</link>
      <guid>https://dev.to/mosiddi/the-mcp-registry-checks-that-the-repository-url-looks-like-github-2bpp</guid>
      <description>&lt;p&gt;Publishing proves you own the namespace and you own the package. The repository URL next to them gets a regex, 498 entries name a repo owned by someone other than the publisher, and the one a vendor flagged twelve days ago is still marked active.&lt;/p&gt;

&lt;p&gt;The official MCP registry holds 24,615 server records. Of the 16,850 published under an io.github.* namespace, 15,235 name a GitHub repository, and 498 of those name a repository owned by a different GitHub account than the namespace the publisher authenticated as.&lt;/p&gt;

&lt;p&gt;Most of those 498 are almost certainly innocent. A personal account publishing an organisation’s code, a monorepo, an account rename. That is the point. Nothing checked, so nothing distinguishes them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What publishing actually proves
&lt;/h3&gt;

&lt;p&gt;The requirements document is clear and short. Four validations: namespace authentication, package ownership verification, restricted registry base URLs, and _meta namespace restrictions. Grep it for "repositor" and you get nothing.&lt;/p&gt;

&lt;p&gt;The code is not silent, though, and the difference matters. validateRepository calls IsValidRepositoryURL, which is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^https?://(www\.)?github\.com/[\w.-]+/[\w.-]+/?$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A shape check. Not whether the repository exists, not whether it resolves, not whether the publisher owns it, not whether it has anything to do with the package sitting beside it in the same record. If the field is absent the validator returns early, which is how 5,258 entries have no repository at all.&lt;/p&gt;

&lt;p&gt;So the record proves two things and asserts a third. The namespace is proven. The package is proven. The repository is typed in.&lt;/p&gt;

&lt;p&gt;None of which is news to the maintainers. Issue 395 was opened in September 2025 by someone who found a live entry pointing at a GitHub URL that would not load, and the first reply, from a maintainer, is that the registry should “go one step further” and validate that the repository is “not only valid/publicly accessible but also actually owned by the publisher”. Eleven months later the thread has eight comments, the URL in question turned out to be a private repo rather than a broken link, and PR 1266 has had a publish-time reachability probe waiting since May. The check the maintainer asked for on day one has not been built.&lt;/p&gt;

&lt;h3&gt;
  
  
  What used the gap
&lt;/h3&gt;

&lt;p&gt;On 8 August at 20:58 UTC, io.github.jUXTAPOSITION1/vape was published. On 9 August, one day later, OX Security reported it. Their account: the PyPI package is clean, deliberately, to get past automated scanners, and the payload sits in the linked repository in .vscode/settings.json and .claude/settings.json, which run when a developer opens or clones the project in a coding client. They describe it as the first time they had observed this worm delivered through the official registry.&lt;/p&gt;

&lt;p&gt;Every prior version of this story ran the other way. Honest repository, poisoned package. Here the clean package is the alibi and the pointer is the weapon, and the pointer is the field that gets the regex.&lt;/p&gt;

&lt;p&gt;GitHub blocked the repository for terms-of-service violation on 15 August. The PyPI package is still installable, both releases, neither yanked. The registry entry is still active, and its statusChangedAt is identical to the microsecond to its publishedAt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why nobody attached a warning
&lt;/h3&gt;

&lt;p&gt;There are three statuses. active, visible by default. deprecated, visible with a message. deleted, hidden by default. statusMessage is capped at 500 characters and rejected with a 400 when the status is active.&lt;/p&gt;

&lt;p&gt;I read all 735 status messages in the registry. Not one describes a moderation action. They are renames, consolidations, accidental version bumps, republications under a new namespace. That is not a failure, it is the feature working as designed: statusMessage was introduced to carry a publisher's deprecation reason, and issue 623 was closed on exactly that ground.&lt;/p&gt;

&lt;p&gt;Which leaves the vocabulary with no way to say the thing this entry needs said. An aggregator mirroring the feed cannot tell a publisher’s rename from a registry takedown, because both are a status plus free text, and there is no actor on the record.&lt;/p&gt;

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

&lt;p&gt;“The registry does not validate the repository field.” Cut. It validates the shape. One grep would have refuted the stronger claim in public.&lt;/p&gt;

&lt;p&gt;“The choice is silence or removal.” Cut. deprecated is documented as visible with a warning. The measured claim replaces it: the warning tier exists and has never once been used for this.&lt;/p&gt;

&lt;p&gt;“The repository was deleted.” Corrected. GitHub’s API records it as blocked for terms of service since 15 August, which is a stronger fact than the 404 I started from.&lt;/p&gt;

&lt;p&gt;“The maintainers deferred ownership binding.” Cut, and it was nearly the worst error here. That is PR 1266’s characterisation of the thread. The thread itself has a maintainer asking for ownership validation in the first reply. Nobody decided against it.&lt;/p&gt;

&lt;p&gt;I have not run the payload or read the repository, which is disabled. The attribution is OX Security’s and I am reporting it as theirs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where I sit in this
&lt;/h3&gt;

&lt;p&gt;cMCP’s own limitations file says the gateway cannot detect a look-alike package added to the catalog in the first place, because catalog approval is human-gated. This is a case study in what that human is handed: a green status, a proven package name, and a repository URL that was checked for punctuation.&lt;/p&gt;

&lt;p&gt;Filed today as issue 1563, a takedown request against the entry, citing the moderation policy’s malware clause. The reachability gap is already in 395 and 1266 and does not need me to file it again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checked against
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://registry.modelcontextprotocol.io/v0/servers?search=vape" rel="noopener noreferrer"&gt;MCP registry, the io.github.jUXTAPOSITION1/vape entry, read 21 August 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/official-registry-requirements.md" rel="noopener noreferrer"&gt;Official registry requirements, the four validations enforced on publish&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/blob/main/internal/validators/validators.go" rel="noopener noreferrer"&gt;internal/validators/validators.go, validateRepository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/blob/main/internal/validators/utils.go" rel="noopener noreferrer"&gt;internal/validators/utils.go, IsValidRepositoryURL and the two regexes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/official-registry-api.md" rel="noopener noreferrer"&gt;Official registry API reference, status values and the statusMessage restriction&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/moderation-policy.mdx" rel="noopener noreferrer"&gt;The MCP Registry moderation policy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/issues/395" rel="noopener noreferrer"&gt;Issue 395, validate provided repositories are publicly reachable, open since September 2025&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/pull/1266" rel="noopener noreferrer"&gt;PR 1266, probe repository URL reachability at publish&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/registry/issues/1563" rel="noopener noreferrer"&gt;Issue 1563, the takedown request I filed against the entry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ox.security/blog/shai-hulud-outbreak-debrief-the-worm-evolves-into-mcp/" rel="noopener noreferrer"&gt;OX Security, Shai-Hulud outbreak debrief, 9 August 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/pypi/vape-mcp-server/json" rel="noopener noreferrer"&gt;PyPI JSON API, vape-mcp-server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/agentrust-io/cmcp/blob/main/LIMITATIONS.md" rel="noopener noreferrer"&gt;cMCP LIMITATIONS.md, tool name collision via malicious catalog entries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://imransiddique.com/notes/2026-08-21-repository-url-looks-like-github.html" rel="noopener noreferrer"&gt;imransiddique.com&lt;/a&gt; on August 21, 2026.&lt;/p&gt;

</description>
      <category>agt</category>
      <category>mcpserver</category>
      <category>mcpregistry</category>
    </item>
    <item>
      <title>The platform bit that means the opposite of what the doc says</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Thu, 20 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-platform-bit-that-means-the-opposite-of-what-the-doc-says-5bf2</link>
      <guid>https://dev.to/mosiddi/the-platform-bit-that-means-the-opposite-of-what-the-doc-says-5bf2</guid>
      <description>&lt;p&gt;&lt;em&gt;Google Cloud tells you to update go-sev-guest so your parser stops breaking on v4 attestation reports. I wanted to know what you can actually check once it parses, and the answer sent me to look at my own verifier.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I set out to answer a small question this morning, and it cost me more than I expected.&lt;/p&gt;

&lt;p&gt;Google Cloud's Confidential VM release notes, in an entry dated 27 October 2025, say that after a firmware update SEV-SNP instances generate v4 attestation reports and that parsers written for v3 might break. The fix offered is to update go-sev-guest to v0.14.0 or above. That advice is correct. I wanted to know what you can check once the thing parses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The version the advice names cannot check what it reads
&lt;/h2&gt;

&lt;p&gt;The v4 report added two mitigation vectors, &lt;code&gt;LAUNCH_MIT_VECTOR&lt;/code&gt; and &lt;code&gt;CURRENT_MIT_VECTOR&lt;/code&gt;, at offsets 0x1F8 and 0x200. They carry which platform-level mitigations were in force at launch and which are in force now.&lt;/p&gt;

&lt;p&gt;At v0.14.0, tagged 9 October 2025, the &lt;code&gt;abi&lt;/code&gt; package reads both. The &lt;code&gt;validate&lt;/code&gt; package does not contain the string "mitigation" anywhere. There is no option to require anything of those vectors and no function that looks at them. Same at v0.14.1.&lt;/p&gt;

&lt;p&gt;The policy hooks arrive in v0.15.0, tagged 9 June 2026 and still the latest release: &lt;code&gt;MinimumLaunchMitigationVector&lt;/code&gt;, &lt;code&gt;MinimumCurrentMitigationVector&lt;/code&gt;, and a check wired into the main validation path.&lt;/p&gt;

&lt;p&gt;That is not a scandal. Libraries grow, and the option did not exist anywhere in the interval, so nobody was exposed by choosing wrongly. But "update to v0.14.0 or above" answers the question people asked, which was why their parser crashed, and not the question worth asking, which is what the new report says and whether anyone is reading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four of seven fields mean the opposite of the documentation
&lt;/h2&gt;

&lt;p&gt;The second half is a documentation problem rather than a code one.&lt;/p&gt;

&lt;p&gt;The library's platform-info policy field is documented as a ceiling. The doc comment says &lt;code&gt;PlatformInfo&lt;/code&gt; is the maximum of acceptable data. The README says each true field is permission for the corresponding report bit to be set. Read that and you build a policy by deciding what you will tolerate.&lt;/p&gt;

&lt;p&gt;Then read the function. The struct has seven fields. Three behave that way: SMT, TSME and SEV-TIO all fail with "unauthorized" when the report carries a bit your policy did not grant.&lt;/p&gt;

&lt;p&gt;The other four are reversed. ECC, RAPL disabled, ciphertext hiding and alias-check-complete all fail when the report is &lt;em&gt;missing&lt;/em&gt; something your policy asked for. For those, setting the bit true is not permission. It is a demand.&lt;/p&gt;

&lt;p&gt;The one I care about is alias-check-complete. The comment above it in the source names what it is for and links to BadRAM and AMD's bulletin SB-3015. The firmware's alias check is the thing that prevents. The bit in the report only proves the firmware ran it. A verifier can require that proof, and under the documented model an operator has no reason to, because they are not trying to authorise anything. They leave it false and the requirement never runs.&lt;/p&gt;

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

&lt;p&gt;The morning brief that pointed me here claimed nobody had made this argument before. Wrong. Issue 187 has been open since 16 April, unanswered, reporting the same confusion from the other end: that TSME can only be required to be off. I read that as evidence for the doc being the cause, and cited it.&lt;/p&gt;

&lt;p&gt;It also had three of the four error strings wrong, and counted thirteen references where there are fifteen. I could not treat any of it as read.&lt;/p&gt;

&lt;p&gt;I could not date the release note from search results either. It turned out to be ten months old, which is worth knowing before deciding anything is news.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this cost me
&lt;/h2&gt;

&lt;p&gt;I went to check my own verifier before writing a word of this, and I did not enjoy the result.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_snp_verify.py&lt;/code&gt; in agent-manifest does not parse &lt;code&gt;PLATFORM_INFO&lt;/code&gt; at all. The field sits at offset 0x40. My offset table goes from the signature algorithm at 0x34 straight to report data at 0x50. It is not in the table, not in the parsed struct, and across every first-party file in agent-manifest, cmcp, ca2a and trace-spec the terms &lt;code&gt;platform_info&lt;/code&gt;, &lt;code&gt;alias_check&lt;/code&gt;, &lt;code&gt;mit_vector&lt;/code&gt; and &lt;code&gt;badram&lt;/code&gt; appear zero times each.&lt;/p&gt;

&lt;p&gt;So this is not a case of my documents failing to mention a check that the code performs. The bytes are never read. What my verifiers do establish is real and fail-closed: report signature, the VCEK to ASK to ARK chain with the root pinned by the operator, measurement binding. That is authenticity and identity. None of those four asks the report what kind of machine it came from.&lt;/p&gt;

&lt;p&gt;I have written the honest limit the same way for months, that no confidential computing silicon is custody grade against an adversary who owns the machine, with BadRAM as the reason. I treated it as a boundary to state and live with. Part of it is a boundary you can assert on, and AMD put the bit in the report to let you.&lt;/p&gt;

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

&lt;p&gt;I filed issue 195 against go-sev-guest, proposing the doc comment and the README bullet be split into the ceiling set and the floor set, with the fields named in each.&lt;/p&gt;

&lt;p&gt;Parsing &lt;code&gt;PLATFORM_INFO&lt;/code&gt; and exposing an appraisal policy over it goes into agent-manifest next, and the limitations files across all four repos get a line naming what is not checked. Not everything unenforced is unenforceable. Sometimes the check is sitting in the struct and nobody wired it up.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google/go-sev-guest/blob/c930ed67bebfe7245c0309888ec185bd9ad35899/validate/validate.go" rel="noopener noreferrer"&gt;google/go-sev-guest validate.go at c930ed67, read 20 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/go-sev-guest/blob/c930ed67bebfe7245c0309888ec185bd9ad35899/abi/abi.go" rel="noopener noreferrer"&gt;google/go-sev-guest abi.go at c930ed67, the SnpPlatformInfo struct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/go-sev-guest/issues/195" rel="noopener noreferrer"&gt;Issue 195, the doc bug I filed against go-sev-guest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/go-sev-guest/issues/187" rel="noopener noreferrer"&gt;Issue 187, milesdai on TSME polarity, open since April 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/confidential-computing/confidential-vm/docs/release-notes" rel="noopener noreferrer"&gt;Google Cloud Confidential VM release notes, entry of 27 October 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://badram.eu/" rel="noopener noreferrer"&gt;BadRAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3015.html" rel="noopener noreferrer"&gt;AMD security bulletin SB-3015&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>attestation</category>
      <category>sevsnp</category>
      <category>evidence</category>
    </item>
    <item>
      <title>The attestation everyone cites cannot be looked up any more</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Wed, 19 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-attestation-everyone-cites-cannot-be-looked-up-any-more-2nlc</link>
      <guid>https://dev.to/mosiddi/the-attestation-everyone-cites-cannot-be-looked-up-any-more-2nlc</guid>
      <description>&lt;p&gt;&lt;em&gt;Thirty poisoned npm packages are gone along with their attestations, and the Sigstore copy that survives can only be fetched with a digest that no longer exists in any public place I could find.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every account of the keyv compromise turns on one sentence. Aikido's is representative: "the poisoned versions were published to npm with valid provenance signed by GitHub Actions." That is the detail that made this story worth writing about rather than another package takeover.&lt;/p&gt;

&lt;p&gt;Fifteen days later I went to check it, and at npm you cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is left
&lt;/h2&gt;

&lt;p&gt;I fetched thirty packages this morning: the twenty that one release run published, and ten more across the maintainer's other projects. Every one behaves the same way. The poisoned version is absent from its packument's versions map, its tarball returns 404, and its attestation endpoint returns 404. A control fetch of a clean version returns 200 on all three.&lt;/p&gt;

&lt;p&gt;One thing does survive. npm keeps the publish timestamp in the packument's time map for a version that exists nowhere else in the registry. Thirty orphan timestamps, running from 09:30:01.291Z to 10:28:01.451Z on 4 August, and nothing else.&lt;/p&gt;

&lt;p&gt;Worth noting in passing, since it bears on every count you have read: the public indicator lists name eleven packages. The release run's own log ends with the line "Published 20 package(s)", from that one repository, before the campaign spread anywhere else. Thirty is a count of what I fetched, not a total.&lt;/p&gt;

&lt;h2&gt;
  
  
  The copy that is still there
&lt;/h2&gt;

&lt;p&gt;Deleting from npm does not delete the attestation. npm's provenance is written to Rekor, Sigstore's public append-only transparency log, and append-only means npm cannot take an entry back out.&lt;/p&gt;

&lt;p&gt;The retrieval path works, and I checked that it works before concluding anything about the missing ones. keyv 6.0.0-rc.1, published the evening before the compromise and still live, has a tarball digest in its packument. Handing that sha512 to Rekor's search index returns two entries, which are the two Sigstore bundles npm attached to that release. Fetching one by its log index returns the record.&lt;/p&gt;

&lt;p&gt;Now do the same for keyv 6.0.0. The digest lives in the packument's entry for that version, and the packument's entry for that version is exactly what unpublishing removes. The tarball is gone, so I cannot recompute it. npm's attestation endpoint, which would hand it over, is one of the ones returning 404. A public mirror I checked never carried the version at all. Rekor's index also accepts a certificate subject, so I tried the workflow identity instead, and it returned nothing for the poisoned build and nothing for the live control either, which tells me the method does not work rather than that the entry is missing.&lt;/p&gt;

&lt;p&gt;So the entry is almost certainly sitting in the log, and I have no way to ask for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think this matters
&lt;/h2&gt;

&lt;p&gt;Unpublishing was the right call and it did the job it was for. Enforcement runs at the registry, in front of the fetch, and it is what actually stops anyone installing keyv 6.0.0 today. Nothing here argues against it. The point sits next to it, not against it.&lt;/p&gt;

&lt;p&gt;Evidence is a different job. It prevents nothing. Its whole value is that somebody who trusts neither the attacker nor the registry can come back later and check a claim. An append-only log answers the question "can this record be removed", which is the question everybody designs for. It does not answer "can this record be found", and that turns out to be the one that bites, because the address was carried by the artifact and the artifact is what you delete. The record outlived its index.&lt;/p&gt;

&lt;p&gt;The one other place the build is documented is the workflow run at GitHub. Per GitHub's documented default those logs are deleted 90 days after the run, which for this incident falls in early November, and I could not read the repository's actual setting.&lt;/p&gt;

&lt;p&gt;I have not answered this in my own work either. TRACE's limitations document says "Signature validity is permanent; trust is not. Nothing inside a record can retract the key that signed it", and the answer we specified is a revocation store the verifier consults at verification time. That handles a key being withdrawn. It says nothing about a record that stays perfectly valid and becomes unreachable because the thing it describes was deleted, and a content-addressed trust record has the same shape of problem as npm's.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Whether the poisoned attestations are in Rekor.&lt;/strong&gt; I believe they are and I did not confirm it. Both routes failed for the reasons above, and the subject-index route failed its own control, so treat this as unproven rather than as a negative result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The size of the campaign.&lt;/strong&gt; Aikido's page currently reads 444 packages across 1,381 versions; VentureBeat cites Aikido at 868; JFrog traced more than 400 packages and 1,700 versions. I did not resolve them, so no total appears above and none of the argument rests on one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether the eleven named packages are the malicious set.&lt;/strong&gt; I can show that thirty were published in the window from the same runs and later removed. I cannot show what was in any of them, because the tarballs are gone. That is the same wall as everything else here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 90-day expiry.&lt;/strong&gt; That is GitHub's documented default. The repository's configured retention is not readable without access I do not have.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.aikido.dev/blog/keyv-and-friends-compromised-in-npm-supply-chain-attack" rel="noopener noreferrer"&gt;Aikido, "Keyv and friends compromised in npm supply chain attack", 4 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snyk.io/blog/inside-keyv-npm-compromise-preinstall-malware-trusted-provenance-ide-hooks/" rel="noopener noreferrer"&gt;Snyk, "Inside the keyv npm compromise", read 19 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://venturebeat.com/security/the-shai-hulud-npm-worm-didnt-fake-its-security-check-it-earned-a-legitimate-one" rel="noopener noreferrer"&gt;VentureBeat, "The Shai-Hulud npm worm didn't fake its security check, it earned a legitimate one"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nulltap.sh/p/keyv-shai-hulud-provenance/" rel="noopener noreferrer"&gt;nulltap, "Keyv's signed npm releases carried a credential-stealing worm"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.npmjs.org/keyv" rel="noopener noreferrer"&gt;npm registry packuments and attestation endpoints, read 19 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rekor.sigstore.dev/api/v1/log/entries?logIndex=2336496546" rel="noopener noreferrer"&gt;Sigstore Rekor transparency log, search index API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/organizations/managing-organization-settings/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization" rel="noopener noreferrer"&gt;GitHub docs, retention period for Actions artifacts and logs&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 specification, LIMITATIONS.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>supplychain</category>
      <category>evidence</category>
      <category>provenance</category>
    </item>
    <item>
      <title>My quickstart ends in FAIL, and that is the correct answer</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 17 Aug 2026 22:30:20 +0000</pubDate>
      <link>https://dev.to/mosiddi/my-quickstart-ends-in-fail-and-that-is-the-correct-answer-c6d</link>
      <guid>https://dev.to/mosiddi/my-quickstart-ends-in-fail-and-that-is-the-correct-answer-c6d</guid>
      <description>&lt;p&gt;If you run the cMCP quickstart on your laptop, the last command prints &lt;code&gt;FAIL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I want to explain that before you hit it, because the reflex is to assume the tutorial is broken. It is not. That output is the whole argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the ten minutes actually do
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;cmcp-runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MIT, on PyPI, no account, no sales call, no special hardware. You write a Cedar policy. You point an agent at a tool. You then fire an action your own policy forbids, something like a record with PII leaving on a call to Salesforce.&lt;/p&gt;

&lt;p&gt;It returns 403 before the call is forwarded. Not after. Not logged and alerted on. The request does not reach the tool, and the decision is made at the boundary rather than inside the model that wanted to make the call. That distinction is the entire reason the project exists: a model may propose an action, but it cannot be the authority that permits it.&lt;/p&gt;

&lt;p&gt;Then you finalise the session and get a TRACE claim, and you verify it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmcp verify claim.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The part I want you to look at
&lt;/h2&gt;

&lt;p&gt;In dev mode the verifier prints something close to this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema: pass&lt;/li&gt;
&lt;li&gt;signature: pass&lt;/li&gt;
&lt;li&gt;policy bundle hash: pass&lt;/li&gt;
&lt;li&gt;tool catalog hash: pass&lt;/li&gt;
&lt;li&gt;attestation freshness: pass&lt;/li&gt;
&lt;li&gt;audit chain integrity: pass&lt;/li&gt;
&lt;li&gt;hardware attestation: fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall result: &lt;code&gt;FAIL (partially_verified)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Six checks pass. One cannot run, because your laptop has no TEE, so there is no hardware measurement to compare against. And the tool refuses to round that up.&lt;/p&gt;

&lt;p&gt;It would be trivial to make this print &lt;code&gt;VERIFIED&lt;/code&gt; with a footnote. Most things in this space do exactly that. I think it is the single most damaging habit in the category, because it teaches operators that a green result is a green result, and then a green result arrives one day from a system that measured nothing at all.&lt;/p&gt;

&lt;p&gt;Unmeasured is not empty. A check that could not be performed and a check that passed are different facts, and any evidence format that collapses them is worse than no evidence, because it launders an absence into an assurance. If you take one thing from the quickstart, take that, and go look at whether your own pipeline can tell the difference.&lt;/p&gt;

&lt;p&gt;Run the same claim on real TEE hardware and the last line passes and the overall result changes. Nothing else about the record changes. That is the point: the record is honest about its own coverage in both cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually want from you
&lt;/h2&gt;

&lt;p&gt;The happy path working is not interesting. I already know the happy path works.&lt;/p&gt;

&lt;p&gt;What I want is the edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a policy that should have fired and did not&lt;/li&gt;
&lt;li&gt;a record that does not reconcile against the policy bundle it claims&lt;/li&gt;
&lt;li&gt;anything that behaves differently from what the spec says it does&lt;/li&gt;
&lt;li&gt;a verification result you can argue is wrong in either direction, false pass or false fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find one and I will publish it with the fix, or with the open issue if there is no fix yet, and I will name you if you want the credit. I have written up limits in my own spec before and I would rather do it again than find out from a customer.&lt;/p&gt;

&lt;p&gt;There is a real one already on the table, so I am not pretending the surface is clean: TRACE verifies against the issuer's own key, which means it detects later tampering well and says much less than it sounds about whether the issuer was honest at write time. Independent verification needs an anchor outside the issuer. That is unsolved, and it is not the only thing here that is.&lt;/p&gt;

&lt;p&gt;Ten minutes. Then tell me what broke.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agentrust-io.com/quickstart/?source=devto-imran" rel="noopener noreferrer"&gt;https://agentrust-io.com/quickstart/?source=devto-imran&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One question if you want to answer only one: did it unblock something you cannot ship today?&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Sovereign verifiability takes three layers. A2A just secured the foundation.</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Mon, 17 Aug 2026 16:59:07 +0000</pubDate>
      <link>https://dev.to/mosiddi/sovereign-verifiability-takes-three-layers-a2a-just-secured-the-foundation-eb8</link>
      <guid>https://dev.to/mosiddi/sovereign-verifiability-takes-three-layers-a2a-just-secured-the-foundation-eb8</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%2AAwfbahEO5jhR33L8" 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%2AAwfbahEO5jhR33L8" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A2A is joining the Agentic AI Foundation. Mazin Gilbert announced it this morning and Ina Fried has the story at Axios. MCP defines how an agent reaches a tool. A2A defines how one agent hands a task to another and gets a result back. As of today both sit under the same open governance, with a steering committee that includes AWS, Cisco, Google, IBM Research, Microsoft, Salesforce, SAP and ServiceNow.&lt;/p&gt;

&lt;p&gt;This is a genuinely good day, and I do not want to undersell it. An interoperable agent-to-agent transport under neutral governance is the precondition for everything I am going to describe below. Google could have kept it, and chose open governance instead. The teams who built A2A to v1 and the TSC now stewarding it have done the hard, unglamorous work that makes a layer everyone can build on.&lt;/p&gt;

&lt;p&gt;We build on that layer. Agent Mesh and cA2A both assume A2A rather than replace it, in the same way our confidential MCP work assumed MCP. So this edition is about what we have been putting on top, why it is needed for sovereignty specifically, and what it still cannot do.&lt;/p&gt;

&lt;p&gt;The short version: a sovereignty claim has to survive three layers, and the transport is only the ground it stands on.&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%2F731%2F0%2ANnsvsUyuS2Pgiv5M" 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%2F731%2F0%2ANnsvsUyuS2Pgiv5M" width="731" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the test that shows why the layers above the transport have to exist.&lt;/p&gt;

&lt;p&gt;A resource owner permits processing in France only. A lead agent delegates summarisation to a sub-agent. The sub-agent calls a model endpoint that is EU-branded but served from a region outside France.&lt;/p&gt;

&lt;p&gt;Every component behaved as configured. The deployment was compliant. The data flow was not.&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%2F731%2F0%2As3QG52_lJptlIAJm" 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%2F731%2F0%2As3QG52_lJptlIAJm" width="731" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wrote that as the acceptance test in an RFC last week, and I want to spend this edition on why it is hard, because it is the clearest example I know of the difference between a system that is governed and a system that says it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The project
&lt;/h3&gt;

&lt;p&gt;Agent mesh is the part of the Agent Governance Toolkit that handles what happens when agents stop working alone. One agent hands work to another. That one hands part of it onward. The toolkit is at roughly 5,900 stars and 1,000 forks, and most of the interest I get on it now is about this layer rather than the single-agent one.&lt;/p&gt;

&lt;p&gt;The reason is that the second hop is where most governance quietly stops working.&lt;/p&gt;

&lt;p&gt;Everything you established about the first agent was established about the first agent. Its identity. Its policy. Its permitted region. The moment it delegates, you are relying on those properties surviving a transfer, and in most systems today nothing carries them across. The sub-agent inherits a task. It does not inherit the constraints under which the task was authorised.&lt;/p&gt;

&lt;h3&gt;
  
  
  Declared is not verified
&lt;/h3&gt;

&lt;p&gt;Residency is the sharpest version of this because it looks solved and is not.&lt;/p&gt;

&lt;p&gt;The way it is modelled today, in the toolkit and nearly everywhere else, is as declared metadata. A policy carries a list of processing regions. An agent’s claims carry a data residency field. Both are strings that somebody typed.&lt;/p&gt;

&lt;p&gt;A declared region string is unverifiable. Nothing binds it to evidence, and nothing carries it across more than one hop. So an agent can satisfy every check the design specifies and still route data out of its permitted jurisdiction through a sub-agent, with no record that it happened.&lt;/p&gt;

&lt;p&gt;I want to be precise about why that is worse than having nothing. An unverifiable residency claim creates documented, auditable confidence in a property nobody checked. You end up with a compliance artifact that is exactly as strong as the honesty of whoever filled in the field, presented with the visual authority of a control.&lt;/p&gt;

&lt;p&gt;The two properties that matter here are easy to state and hard to build:&lt;/p&gt;

&lt;p&gt;Residency is only meaningful if it survives delegation. A constraint that applies to the first agent and evaporates at the first handoff is not a constraint, it is a label on the front door.&lt;/p&gt;

&lt;p&gt;Delegation is only safe if residency composes. Across a chain, a constraint has to intersect at every hop and must never widen. The child can hold less than the parent. It can never hold more.&lt;/p&gt;

&lt;p&gt;The same monotonicity already gets enforced for capabilities in a lot of systems. Almost nobody enforces it for geography.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why detection does not close this
&lt;/h3&gt;

&lt;p&gt;The cheaper answer is to check afterwards, and it is where most of the industry sits.&lt;/p&gt;

&lt;p&gt;The best published agentic attack detector I am aware of is Uber’s ADR, which reports catching 67% of attacks at zero false positives on its own benchmark. That is genuinely state of the art and it still misses roughly one in three.&lt;/p&gt;

&lt;p&gt;A residency violation caught 67% of the time is not a control a regulator will accept. Detection and evidence compose. Neither substitutes for the other, and a system that only detects is a system that finds out.&lt;/p&gt;

&lt;h3&gt;
  
  
  What cA2A is for
&lt;/h3&gt;

&lt;p&gt;This is the project I have never written about here, which is an odd gap given that it exists for exactly the problem above.&lt;/p&gt;

&lt;p&gt;A2A makes a deliberate and, I think, correct design choice: it specifies the transport and the Agent Card, and it leaves the runtime credential layer to implementers. A transport should not be in the attestation business. Putting hardware attestation into a wire protocol would have made it heavier, slower to adopt, and wrong for the majority of deployments that do not need it.&lt;/p&gt;

&lt;p&gt;That choice is what creates the space we work in. The common answers implementers reach for are mTLS and OAuth scopes, which secure the pipe and assert an identity. They are the right default. They do not attenuate authority across a chain, attest runtime integrity, or seal a payload to a measurement, because they were never meant to.&lt;/p&gt;

&lt;p&gt;cA2A is a confidential profile that fills that slot, the same way our confidential MCP work sat on top of MCP rather than beside it. It composes four things:&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%2F731%2F0%2A4rn8FGO86s-eDox_" 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%2F731%2F0%2A4rn8FGO86s-eDox_" width="731" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Attenuated delegation, where each hop’s scope is a provable subset of its parent. Runtime attestation, so a peer proves it is running measured code before it is trusted with a task. A sealed channel, so the payload is readable only inside the verified peer. And a provenance record per hop, chaining into a graph a third party can verify offline without trusting either operator.&lt;/p&gt;

&lt;p&gt;The refusal path ships as an example in the repo: an agent asks for authority nobody delegated to it, is refused, and hands over a record a third party can check offline without trusting the operator that produced it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does not do yet
&lt;/h3&gt;

&lt;p&gt;I would rather you read this from me than discover it.&lt;/p&gt;

&lt;p&gt;cA2A is alpha. The published runtime is 0.1.0a1. The delegation semantics are real and tested, but they live mostly in a sibling project, and the runtime peer path is under construction.&lt;/p&gt;

&lt;p&gt;The sealed channel is implemented as encryption and is not yet bound to a hardware-verified measurement on a live call. Until that lands, do not assume a payload is confined to a specific attested measurement. The reference transport runs in software mode by default, where assurance is explicitly none. Mutual attestation on real silicon in both directions is still outstanding.&lt;/p&gt;

&lt;p&gt;Until this morning the delegation credential had no validity period at all. It signed who issued it, to whom, for what scope, at what depth, and under which parent, and said nothing about when the grant started or ended. So chain verification could check structure and scope narrowing and could not decide whether a grant covered the moment it was being used.&lt;/p&gt;

&lt;p&gt;I am leaving that in rather than quietly editing it out, because how it got fixed is the more interesting fact. It was filed against us on 13 August by somebody outside the project, and closed on 17 August by a different person outside the project, who added not_before and not_after, enforced them per hop, and wrote the conformance cases for expiry and not-yet-valid. That is in main and not yet in a release, so the published 0.1.0a1 still has the gap.&lt;/p&gt;

&lt;p&gt;Three people outside the project have landed commits on this repo in the last week, and a fourth filed the issue that produced one of them. The repo has three stars. I would rather have the four people.&lt;/p&gt;

&lt;p&gt;And the limit that constrains all of this, which I think is the most important sentence in the RFC:&lt;/p&gt;

&lt;p&gt;Hardware attestation proves what code is running. It does not prove where the machine is. There is no trustworthy geography in an SEV-SNP or TDX report, because nothing in the silicon knows what country it is in.&lt;/p&gt;

&lt;p&gt;So the honest ceiling on attested residency is not proof of location. It is a split claim: what is running, rooted in hardware and verifiable offline against a vendor root with no trust in the operator, and where it is running, rooted in a signed statement from an identified operator who can be held to it afterwards. The second half is operator trust, not hardware trust. An operator with physical control of the machine can defeat it.&lt;/p&gt;

&lt;p&gt;What that buys is non-repudiation rather than prevention. Somebody accountable signed a statement, bound to a specific attested instance, and cannot later say they did not. I think that is the strongest claim available, and any project telling you it has cryptographic proof of location is telling you something that is not true.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three layers, one claim
&lt;/h3&gt;

&lt;p&gt;I have been building these as separate things for long enough to see that they are one thing.&lt;/p&gt;

&lt;p&gt;Agent OS is the kernel layer. It governs a single agent: what it is allowed to do, evaluated and enforced at the runtime rather than asked of the model, and written down as it happens. A kernel is the right metaphor because the point is that the governed program does not get to opt out.&lt;/p&gt;

&lt;p&gt;Agent Mesh is the layer above, where agents stop working alone. One agent hands work to another. Constraints have to survive that handoff, and they have to narrow rather than widen at every hop.&lt;/p&gt;

&lt;p&gt;cA2A is the layer that crosses organisations. Inside your own mesh you can plausibly trust your own operator. Across trust domains you cannot, so the payload has to be sealed to a peer whose code you verified, and the record has to be checkable by a third party who trusts neither of you.&lt;/p&gt;

&lt;p&gt;Sovereignty is the claim that needs all three at once. A jurisdiction constraint is meaningless if the runtime does not enforce it, useless if it evaporates at the first delegation, and unprovable if the only evidence is a log written by the party whose behaviour is in question. That is the whole argument: sovereign verifiability is not a deployment region, it is a property that has to hold at the kernel, across the mesh, and between organisations, and be checkable afterwards by someone who trusts nobody.&lt;/p&gt;

&lt;p&gt;Almost every sovereignty story on the market today is layer zero, which is a region on a purchase order.&lt;/p&gt;

&lt;h3&gt;
  
  
  The question
&lt;/h3&gt;

&lt;p&gt;I am fairly confident about the properties: attested rather than declared, composing transitively, failing closed before data moves, verifiable offline by someone who trusts nobody.&lt;/p&gt;

&lt;p&gt;I am much less confident about the geography half, because it is the one part that cannot be rooted in hardware.&lt;/p&gt;

&lt;p&gt;So: if you cannot prove where a machine is, and you can only prove who signed a statement about where it is, is attested residency worth building at all, or does a signed operator statement bound to an attested instance just relocate the trust problem somewhere it is easier to ignore?&lt;/p&gt;

&lt;p&gt;I think it relocates it somewhere it is at least attributable, which is not nothing. Tell me where that is wrong.&lt;/p&gt;

&lt;p&gt;The RFC is open at microsoft/agent-governance-toolkit#3658. cA2A is at &lt;a href="http://github.com/agentrust-io/ca2a" rel="noopener noreferrer"&gt;github.com/agentrust-io/ca2a&lt;/a&gt;, alpha, with its limits written down in &lt;a href="http://LIMITATIONS.md" rel="noopener noreferrer"&gt;LIMITATIONS.md&lt;/a&gt; rather than in a footnote. The validity-window fix is PR #110, landed this morning, against issue #108.&lt;/p&gt;

</description>
      <category>confidentialcomputin</category>
      <category>aigovernance</category>
      <category>opensource</category>
      <category>agenticai</category>
    </item>
    <item>
      <title>The LiteLLM compromise is not in any of the places you would look for it</title>
      <dc:creator>Imran Siddique</dc:creator>
      <pubDate>Fri, 14 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/mosiddi/the-litellm-compromise-is-not-in-any-of-the-places-you-would-look-for-it-4ke6</link>
      <guid>https://dev.to/mosiddi/the-litellm-compromise-is-not-in-any-of-the-places-you-would-look-for-it-4ke6</guid>
      <description>&lt;p&gt;&lt;em&gt;Both poisoned versions 404 on PyPI, no CVE was ever issued, and the KEV entry filed two days later belongs to the scanner rather than the gateway.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In March, two poisoned releases of LiteLLM went up on PyPI. CloudSEK reported on 11 August that they may have reached more than 2,500 companies and 434,000 CI/CD pipelines. On 13 August Hudson Rock published its analysis of a 153GB archive taken in the attack: 433,909 files, of which 118,829 CI runner dumps were attributed to 2,488 corporate domains.&lt;/p&gt;

&lt;p&gt;I went to check the figures and found something more interesting than the figures. If you go looking for this incident in the three places an engineer would normally look, it is not in any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  PyPI
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;litellm&lt;/code&gt; 1.82.7 and 1.82.8 are not on PyPI. Not yanked, which would leave them visible and installable by exact pin. Gone. Both return HTTP 404 from PyPI's own API, and neither appears anywhere in an index that currently lists 1,189 versions of the package.&lt;/p&gt;

&lt;p&gt;What the record shows instead is 1.82.6 on 22 March, then 1.83.0 on 31 March, with nothing between them and no 1.82.9 at all. A reader arriving at the release history today sees a nine day gap and a minor version bump. Nothing on that timeline says an incident happened.&lt;/p&gt;

&lt;p&gt;Removing the artifacts was right. Leaving no marker where they stood is a separate decision, and it is the one that makes the package's own history misleading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CVE
&lt;/h2&gt;

&lt;p&gt;There is no CVE for the LiteLLM package compromise. Searching NVD across March and April 2026 returns only ordinary code vulnerabilities: an unauthenticated &lt;code&gt;/config/update&lt;/code&gt; endpoint, a JWT authentication flaw, a guardrails sandbox escape. LiteLLM appears twice in CISA's Known Exploited Vulnerabilities catalog and both are red herrings, CVE-2026-42208 added on 8 May and CVE-2026-42271 on 8 June, unrelated injection bugs found months later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The KEV entry that does exist
&lt;/h2&gt;

&lt;p&gt;Two days after the poisoned releases there is a KEV entry, and it is worth being precise about whose it is. &lt;strong&gt;CVE-2026-33634, added 26 March, is Aqua Security's Trivy, not LiteLLM.&lt;/strong&gt; CVSS 4.0 base score 9.4. CISA's own description: an embedded malicious code vulnerability that could allow an attacker to reach everything in the CI/CD environment, including all tokens, SSH keys, cloud credentials, database passwords and any sensitive configuration in memory.&lt;/p&gt;

&lt;p&gt;This matters because Trivy was the way in. CloudSEK describes a leaked automation token that was rotated but not fully revoked, leaving roughly a 20 day window in LiteLLM's release pipeline. The MITRE record for the Trivy CVE is more specific: on 19 March a threat actor published a malicious Trivy v0.69.4, &lt;strong&gt;force-pushed 76 of the 77 version tags in &lt;code&gt;aquasecurity/trivy-action&lt;/code&gt;&lt;/strong&gt; to credential-stealing malware, and replaced all seven tags in &lt;code&gt;aquasecurity/setup-trivy&lt;/code&gt;. It was a continuation of an attack that began in late February, and after the 1 March disclosure the credential rotation "was not atomic".&lt;/p&gt;

&lt;p&gt;The dates sit close enough that it is easy to file the 26 March listing under LiteLLM and move on, which inverts the lesson. The component that earned a critical CVE and a federal remediation deadline was the scanner inside the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that does to the usual advice
&lt;/h2&gt;

&lt;p&gt;The standard response to a poisoned package is to pin your dependencies, and for the PyPI half of this that holds. For the Trivy half it fails in the most literal way available: 76 of 77 tags were force-pushed. Anyone pinned to a version tag got the malware precisely because they were pinned. Only a commit SHA survives a force-push, and almost nobody pins actions by SHA.&lt;/p&gt;

&lt;p&gt;Pinning is not integrity. It is a stable name for something somebody else can still overwrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I cannot help with
&lt;/h2&gt;

&lt;p&gt;Five months on, 2,488 organisations established their own exposure by reading someone else's analysis of a stolen archive. Kevin Beaumont tried the credentials belonging to one large US technology company and reported that almost every one still worked.&lt;/p&gt;

&lt;p&gt;That gap is not a detection failure. Detection worked, and the packages were live for about forty minutes. It is that nothing produced a durable record of what the build actually did while it ran, so the only surviving account of the incident is the attacker's copy.&lt;/p&gt;

&lt;p&gt;I should be straight that this sits outside everything I have built. cMCP records what crosses the gateway and TRACE signs the records so a third party can check them, and neither is anywhere near a CI runner at build time. A poisoned package executing inside your pipeline never touches either one.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"CISA added the CVE two days in."&lt;/strong&gt; Cut. No CVE exists for this compromise. The 26 March KEV entry is Trivy's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Shipped a clean version six days later."&lt;/strong&gt; Corrected. 1.82.6 is 22 March and 1.83.0 is 31 March, so nine days after the last clean release and seven after the poisoned ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"PyPI quarantined them inside forty minutes."&lt;/strong&gt; Narrowed. CloudSEK gives forty minutes as the period the packages were live and does not say who removed them. No source I could reach names the actor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"An unpinned Trivy scanner."&lt;/strong&gt; Corrected. The vector was a leaked automation token and a non-atomic rotation, not an unpinned dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"2,488 corporate domains" from 433,909 files.&lt;/strong&gt; Scoped. The domains were attributed from the 118,829 CI runner dumps, a subset of the archive.&lt;/li&gt;
&lt;li&gt;Whether LiteLLM's maintainers disclosed the same day: I could not verify it, so it is not here.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cloudsek.com/blog/ai-supply-chain-breach-2500-companies-434000-cicd-pipelines" rel="noopener noreferrer"&gt;CloudSEK, "2,500+ Companies and 434,000 CI/CD Pipelines Exposed in the Largest AI Supply Chain Breach of 2026", 11 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.helpnetsecurity.com/2026/08/13/litellm-breach-stolen-credentials-leak/" rel="noopener noreferrer"&gt;Help Net Security on Hudson Rock's archive analysis, 13 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/pypi/litellm/json" rel="noopener noreferrer"&gt;PyPI JSON API for litellm, read 14 August 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cveawg.mitre.org/api/cve/CVE-2026-33634" rel="noopener noreferrer"&gt;CVE-2026-33634, "Trivy ecosystem supply chain briefly compromised", MITRE record&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" rel="noopener noreferrer"&gt;CISA Known Exploited Vulnerabilities catalog, version 2026.08.14&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>supplychainsecurity</category>
      <category>cicd</category>
      <category>security</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
