<?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: Cakra Budiman</title>
    <description>The latest articles on DEV Community by Cakra Budiman (@cakra_budiman).</description>
    <link>https://dev.to/cakra_budiman</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%2F4074507%2F67f0e618-cd6a-4a84-ba9b-4755209a96b6.jpg</url>
      <title>DEV Community: Cakra Budiman</title>
      <link>https://dev.to/cakra_budiman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cakra_budiman"/>
    <language>en</language>
    <item>
      <title>The state of MCP servers and agent skills, November 2026</title>
      <dc:creator>Cakra Budiman</dc:creator>
      <pubDate>Sat, 14 Nov 2026 11:09:18 +0000</pubDate>
      <link>https://dev.to/cakra_budiman/the-state-of-mcp-servers-and-agent-skills-november-2026-1lj0</link>
      <guid>https://dev.to/cakra_budiman/the-state-of-mcp-servers-and-agent-skills-november-2026-1lj0</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I run &lt;a href="https://hostedmcp.org" rel="noopener noreferrer"&gt;HostedMCP&lt;/a&gt;, an open directory of MCP servers and agent skills. These numbers come from its database; the full report with methodology is on &lt;a href="https://hostedmcp.org/learn/state-of-mcp-november-2026" rel="noopener noreferrer"&gt;hostedmcp.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most writing about the size of the MCP ecosystem quotes one directory's count, or a curated list of a few thousand repositories. We run a catalog that tries to see everything: the official MCP Registry, GitHub, npm and PyPI for servers, and GitHub for agent skills and plugin marketplaces. This report is what that catalog held on 14 November 2026, counted straight from the database.&lt;/p&gt;

&lt;p&gt;Every number below is a measurement, not an estimate. Where a figure depends on a choice we made, such as which repositories count as skills, the methodology section at the end says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  How big the ecosystem is
&lt;/h2&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;Count on 14 Nov 2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Active MCP servers&lt;/td&gt;
&lt;td&gt;118,434&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;of which listed in the official MCP Registry&lt;/td&gt;
&lt;td&gt;62,952&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;of which pass our bar for an indexed page&lt;/td&gt;
&lt;td&gt;28,440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent skills (folders with a SKILL.md)&lt;/td&gt;
&lt;td&gt;52,147&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub repositories those skills come from&lt;/td&gt;
&lt;td&gt;3,339&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code plugins&lt;/td&gt;
&lt;td&gt;4,818&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin marketplaces they come from&lt;/td&gt;
&lt;td&gt;1,142&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two numbers here are worth a second look. More than half of all servers now come through the official MCP Registry, which only launched in 2025. And fewer than a quarter of servers make it to an indexed page: the rest have no stars, no usable description, or come from publishers that post thousands of near-identical entries. We flag 13,357 servers from such bulk publishers.&lt;/p&gt;

&lt;p&gt;Skills are concentrated differently. 52,147 skills come from only 3,339 repositories, about 16 per repository, because collections such as Anthropic's own and the large community libraries hold hundreds or thousands each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attention: a tall head and a very long tail
&lt;/h2&gt;

&lt;p&gt;Here is every active server by the star count of its repository; servers without a public repository count as zero.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;th&gt;Share of servers&lt;/th&gt;
&lt;th&gt;Share of all stars&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;75,507&lt;/td&gt;
&lt;td&gt;63.8%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 to 9&lt;/td&gt;
&lt;td&gt;31,182&lt;/td&gt;
&lt;td&gt;26.3%&lt;/td&gt;
&lt;td&gt;1.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 to 99&lt;/td&gt;
&lt;td&gt;8,856&lt;/td&gt;
&lt;td&gt;7.5%&lt;/td&gt;
&lt;td&gt;4.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 to 999&lt;/td&gt;
&lt;td&gt;2,324&lt;/td&gt;
&lt;td&gt;2.0%&lt;/td&gt;
&lt;td&gt;11.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000 to 9,999&lt;/td&gt;
&lt;td&gt;473&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;23.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000 and more&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;0.08%&lt;/td&gt;
&lt;td&gt;59.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjrfcblscokxw3efcyt3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjrfcblscokxw3efcyt3y.png" alt="Bar chart of MCP servers by GitHub stars on 14 November 2026: 75,507 with no stars, 31,182 with 1 to 9, 8,856 with 10 to 99, 2,324 with 100 to 999, 473 with 1,000 to 9,999 and 92 with 10,000 or more. The 92 largest hold 59.8% of all stars." width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Counting each repository once, 86,922 of them, the star Gini coefficient is 0.984. The median repository has zero stars, 97% have fewer than 100, and the top 1% hold 87.8% of every star in the catalog.&lt;/p&gt;

&lt;p&gt;Analyses built on curated catalogs of around 12,500 repositories have reported a Gini near 0.87 and described a missing middle. With the full long tail included, the picture is starker still: it is less a missing middle than a near-empty everything except the very top. Most of the tail is not abandoned work, either. 77,578 servers, about two thirds, were pushed to in the last six months. They are small, recent and unnoticed.&lt;/p&gt;

&lt;p&gt;Skill repositories are less extreme, partly because we only ingest skill repositories with at least five stars. Across 3,339 of them the Gini is 0.960, the median is 19 stars, and the top 1% hold 73.2% of stars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local or remote
&lt;/h2&gt;

&lt;p&gt;The transport is known for 63,847 servers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Remote only (hosted URL)&lt;/td&gt;
&lt;td&gt;38,223&lt;/td&gt;
&lt;td&gt;59.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local only (stdio)&lt;/td&gt;
&lt;td&gt;20,709&lt;/td&gt;
&lt;td&gt;32.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;4,915&lt;/td&gt;
&lt;td&gt;7.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Remote has overtaken local. When MCP launched in November 2024, servers were processes you ran on your own machine; today most entries, especially in the MCP Registry, are hosted endpoints you connect to by URL. For users that means less to install and more trust placed in whoever runs the endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;We could determine how a server authenticates for 50,247 of them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;th&gt;Share of known&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API key or token&lt;/td&gt;
&lt;td&gt;18,728&lt;/td&gt;
&lt;td&gt;37.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;18,607&lt;/td&gt;
&lt;td&gt;37.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;More than one method&lt;/td&gt;
&lt;td&gt;10,521&lt;/td&gt;
&lt;td&gt;20.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth&lt;/td&gt;
&lt;td&gt;2,391&lt;/td&gt;
&lt;td&gt;4.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5jgf4c2pd6qgqeo9goh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5jgf4c2pd6qgqeo9goh.png" alt="Two charts from the HostedMCP catalog on 14 November 2026. Transport, where known: remote only 59.9%, local only 32.4%, both 7.7%. Authentication, where known: API key 37.3%, none 37.0%, mixed 20.9%, OAuth 4.8%." width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OAuth is the authorization method the MCP specification defines for remote servers, and the one that lets a user grant and revoke access without pasting a secret into a config file. Fewer than one in twenty servers use it. API keys in environment variables remain the default, which is why so many setup guides ask you to put a token in a JSON file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Licenses
&lt;/h2&gt;

&lt;p&gt;MIT dominates with 53,950 servers (45.6%), followed by Apache-2.0 with 8,623 and AGPL-3.0 with 1,612. But 52,226 servers, 44.1%, name no license at all. Without one, nobody else has the right to copy or modify the code, whatever the repository looks like. Among skills, 6,853 of 52,147 (13.1%) carry no license.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many tools a server exposes
&lt;/h2&gt;

&lt;p&gt;For 14,429 servers we know the exact tool list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 to 5&lt;/td&gt;
&lt;td&gt;4,012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6 to 15&lt;/td&gt;
&lt;td&gt;6,416&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 to 40&lt;/td&gt;
&lt;td&gt;2,977&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41 or more&lt;/td&gt;
&lt;td&gt;1,024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The typical server exposes between 6 and 15 tools. The 1,024 servers with 41 or more matter for a different reason: every tool definition is sent to the model with each request, and in our own measurements a large server's definitions alone can run past 100 KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the checks found
&lt;/h2&gt;

&lt;p&gt;We scan servers and skills weekly with automated checks that read public data: the repository, the license, OpenSSF Scorecard, package provenance, known advisories in OSV, package names that imitate popular ones, and the text a model will read, looking for instructions aimed at it. Nothing is executed. On 14 November 2026, 15,380 servers and 7,500 skills had a recent report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Servers (15,380 scanned):&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;Finding&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instructions aimed at the model in tool descriptions or README&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package name one edit away from a popular package&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency with a critical advisory&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package names a different repository as its source&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote endpoint did not answer the MCP handshake&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No license, or not OSI-approved&lt;/td&gt;
&lt;td&gt;1,113&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;About 1.2% of scanned servers carry at least one high-severity finding. That is a small share and a real number: up to 188 servers that an agent could be pointed at today. Each finding links to its evidence on the server's checks report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills (7,500 scanned):&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;Finding&lt;/th&gt;
&lt;th&gt;Skills&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;allowed-tools&lt;/code&gt; lets the agent run shell commands without asking&lt;/td&gt;
&lt;td&gt;669&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instructions aimed at the model in SKILL.md&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripts that download and run code, or send credentials out&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No license, or not OSI-approved&lt;/td&gt;
&lt;td&gt;806&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpzvhi99pnqzfq6mel32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpzvhi99pnqzfq6mel32.png" alt="Chart of automated check findings on 14 November 2026. Servers, 15,380 scanned: 97 with instructions aimed at the model, 55 possible typosquats, 36 with a critical advisory, 114 with a package naming another repository, 130 endpoints that did not answer. Skills, 7,500 scanned: 669 with unrestricted shell in allowed-tools, 50 with instructions aimed at the model, 16 with scripts that download and run code." width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The skill finding that stands out is not the rare malicious script but the common shortcut: almost one skill in eleven pre-approves shell access, so the agent can run commands without asking the user. Usually that is convenience, not malice. It is still the setting that turns a bad instruction into a bad command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much we could check.&lt;/strong&gt; Coverage is uneven. 31.7% of scanned servers could be checked on fewer than 60 of our 100 points, mostly because they publish no package, so there is no provenance, advisory or typosquat check to run. OpenSSF Scorecard results existed for only 163 of the 8,360 servers where that check ran. Until 14 November our score divided by the points we could check, which let a server checked on 35 points show 100/100; scores now never count on fewer than 60 points. 10,327 servers meet our Verified rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you use MCP
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stars tell you little below the top.&lt;/strong&gt; With a median of zero, most servers have no social proof at all. Read the repository, the license and the checks report instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer servers that ask for less.&lt;/strong&gt; OAuth over a pasted token, read-only modes where offered, and skills that do not pre-approve shell access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote is convenient and is trust.&lt;/strong&gt; A hosted endpoint updates itself and needs nothing installed, and runs code you cannot see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A license is not a formality.&lt;/strong&gt; Nearly half of servers have none, which matters the moment you fork or ship one inside a product.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Methodology and limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Date.&lt;/strong&gt; All numbers come from the HostedMCP database on 14 November 2026. The catalog changes daily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Servers.&lt;/strong&gt; Active servers from the MCP Registry, GitHub search, npm and PyPI, after merging duplicates of the same package or repository. Star figures cover the 95,967 servers that link a repository; distribution tables use distinct repository URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills.&lt;/strong&gt; Folders containing a SKILL.md in public GitHub repositories with at least five stars. Repositories below that threshold are not ingested, so skill star figures start higher than server figures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport, auth and tool counts&lt;/strong&gt; come from registry metadata, package manifests and, for tool lists, servers that publish them. Unknown values are left out of the shares, not counted as negatives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checks&lt;/strong&gt; read public data and the files an agent would read. They do not run code and are not a security audit. A finding is a pattern match with evidence, and a few may be false positives; the report for each listing shows exactly what was matched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full catalog, with every server's checks report, is at &lt;a href="https://hostedmcp.org/servers" rel="noopener noreferrer"&gt;hostedmcp.org/servers&lt;/a&gt;, and skills at &lt;a href="https://hostedmcp.org/skills" rel="noopener noreferrer"&gt;hostedmcp.org/skills&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>The build flag that made our encryption 11 slower (and no one noticed for months)</title>
      <dc:creator>Cakra Budiman</dc:creator>
      <pubDate>Wed, 12 Aug 2026 09:52:43 +0000</pubDate>
      <link>https://dev.to/cakra_budiman/the-build-flag-that-made-our-encryption-11x-slower-and-no-one-noticed-for-months-3e7c</link>
      <guid>https://dev.to/cakra_budiman/the-build-flag-that-made-our-encryption-11x-slower-and-no-one-noticed-for-months-3e7c</guid>
      <description>&lt;p&gt;Shipping one Rust crypto core to five platforms with Flutter — and the cargokit gotcha that silently disabled hardware AES in every release build.&lt;br&gt;
Users kept telling us transfers were slow. Our benchmarks kept telling us they were fast. Both were right — and the gap between those two sentences cost us months.&lt;/p&gt;

&lt;p&gt;This is a story about shipping one Rust cryptography core to five platforms from a Flutter app, and about the single most instructive bug we hit doing it: a build flag that quietly turned hardware-accelerated AES &lt;em&gt;off&lt;/em&gt; in every release build we shipped, while every build we benchmarked had it &lt;em&gt;on&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I build &lt;a href="https://bishare.app" rel="noopener noreferrer"&gt;BIShare&lt;/a&gt;, an open-source AirDrop-style app: direct device-to-device file transfer between iPhone, Android, macOS, Windows, and Linux. MIT licensed, Flutter UI, Rust core.&lt;/p&gt;

&lt;p&gt;The architecture decision that matters for this story: all cryptography and protocol framing lives in a single Rust crate, bridged into Dart with &lt;a href="https://github.com/fzyzcjy/flutter_rust_bridge" rel="noopener noreferrer"&gt;flutter_rust_bridge&lt;/a&gt;. X25519 for key agreement, AES-256-GCM for the data path — implemented once, reviewed once, shipped to all five platforms. The Dart side never touches key material.&lt;/p&gt;

&lt;p&gt;On paper this is the clean version of the diagram. One implementation instead of five means one place for bugs instead of five — which is true, and which is exactly why the bug that follows was so effective: we shipped it &lt;em&gt;everywhere, identically&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Local-network transfers should saturate Wi-Fi. Our release-build benchmarks on real devices showed 40–50 MB/s on ordinary Wi-Fi 5 hardware — a 1 GB video in about 25 seconds. That's the whole pitch of skipping the cloud.&lt;/p&gt;

&lt;p&gt;But App Store users reported transfers crawling at about 2 MB/s. Twenty times slower. Same app, same network conditions, same code — allegedly.&lt;/p&gt;

&lt;p&gt;We did the usual rounds: network path, buffer sizes, the Dart/native boundary, thermal throttling. Everything checked out. The transfer pipeline was fine. The &lt;em&gt;cipher&lt;/em&gt; was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause
&lt;/h2&gt;

&lt;p&gt;Modern ARM chips have dedicated AES instructions. With them, AES-256-GCM encrypts multiple gigabytes per second and is never your bottleneck. Without them, the same cipher falls back to a software implementation that is — on the hardware we measured — about 11× slower. Slow enough that encryption, of all things, becomes the ceiling on your transfer speed.&lt;/p&gt;

&lt;p&gt;In Rust, enabling those instructions for aarch64 targets took a compiler flag, which lived in our &lt;code&gt;.cargo/config.toml&lt;/code&gt;. And here's the trap: cargokit — the build helper that compiles Rust crates inside a Flutter build — was invoking &lt;code&gt;cargo&lt;/code&gt; from the &lt;em&gt;build system's&lt;/em&gt; working directory (Xcode's &lt;code&gt;DerivedData&lt;/code&gt;, in the iOS/macOS case), not from the crate's directory.&lt;/p&gt;

&lt;p&gt;Cargo discovers &lt;code&gt;.cargo/config.toml&lt;/code&gt; by walking up from the current working directory. From &lt;code&gt;DerivedData&lt;/code&gt;, our config file was never on that path. So the flag never applied. So every iOS and macOS release build we ever shipped ran software AES.&lt;/p&gt;

&lt;p&gt;Why didn't the benchmarks catch it? Because when you benchmark "the Rust core," you tend to run it from the crate directory — where the config file &lt;em&gt;is&lt;/em&gt; found. Our fast numbers and our users' slow numbers were both completely real. They were just measuring two different binaries that happened to share source code.&lt;/p&gt;

&lt;p&gt;The fix was one line of understanding: make cargokit set &lt;code&gt;workingDirectory&lt;/code&gt; to the crate's manifest directory before invoking cargo. (On Android the same class of problem needed a patch in cargokit's &lt;code&gt;android_environment.dart&lt;/code&gt; to inject the &lt;code&gt;aes&lt;/code&gt; target feature.) After the fix, on-device throughput matched the benchmarks, verified on a mid-range Galaxy A24 — the kind of phone your users actually own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell any team doing crypto behind FRB
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Benchmark the shipped artifact, on a device, in release mode. Not the crate, not the simulator, not a debug build. Debug-mode Dart is roughly 10× slower on hot loops, so debug numbers are noise anyway — and as we learned, even a release &lt;em&gt;crate&lt;/em&gt; benchmark can be measuring a different binary than the one in your &lt;code&gt;.ipa&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treat build-system working directories as hostile. Any configuration that's discovered by walking up from CWD — &lt;code&gt;.cargo/config.toml&lt;/code&gt; is not the only offender — will betray you the moment a build helper invokes the toolchain from somewhere unexpected. Prefer flags that travel &lt;em&gt;with the invocation&lt;/em&gt; (env vars, explicit &lt;code&gt;--config&lt;/code&gt;) or pin the working directory yourself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make performance a test, not a vibe. After this incident we added a benchmark mode to the app itself (&lt;code&gt;BISHARE_BENCH=1&lt;/code&gt;) so throughput on a real device is one command away. If a regression like this ships again, we want a user-visible number to disagree with us immediately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete work the cipher already does. While profiling, we also found our Dart layer computing an extra SHA-256 over data that had already passed through per-chunk &lt;em&gt;authenticated&lt;/em&gt; encryption. AES-GCM's tags already prove integrity, chunk by chunk — the checksum was proving it again, slower. We verified received bytes were identical with and without it, deleted it, and got a free speed bump. If you're layering integrity checks on top of an AEAD, ask what, exactly, the second check catches.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The part where this is a pitch (briefly)
&lt;/h2&gt;

&lt;p&gt;The app this happened to is open source and on the stores: LAN transfers with mDNS discovery, a browser receive path so the other side needs no app, QR-stream transfer for the no-network case, 13 languages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/BIShare-project/bishare-flutter" rel="noopener noreferrer"&gt;https://github.com/BIShare-project/bishare-flutter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Rust protocol crate: &lt;a href="https://github.com/BIShare-project/bishare-protocol" rel="noopener noreferrer"&gt;https://github.com/BIShare-project/bishare-protocol&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;App/downloads: &lt;a href="https://bishare.app" rel="noopener noreferrer"&gt;https://bishare.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've hit your own cursed FRB or cargokit build behavior, I'd genuinely like to hear it — and if you speak a language we don't ship yet, translations are a 15-minute PR.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>rust</category>
      <category>opensource</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
