PageSourceSearch

https://capframe.ai/blog

html capframe.ai collected 2026-10-05 11:54:19 UTC 63,787 bytes, 1 lines download raw bytes

1<!DOCTYPE html><html data-dpl-id="dpl_C5VZ5Gw2C1rWXgZRXzFiVwTEuos6" lang="en" class="geist_456cb025-module__bi1xZG__variable jetbrains_mono_9a2f2d6c-module__wsyXyG__variable antialiased"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/immutable/media/70bc3e132a0a741e-s.p.269kn9uafm0ti.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/immutable/media/caa3a2e1cccd8315-s.p.0zr6hhvz-h9nw.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/immutable/chunks/1-pbzz7xe-6g3.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/immutable/chunks/25kvgcvbp6c14.js"/>
1<script src="/_next/static/immutable/chunks/20es43nb27oti.js" async=""></script>
1<script src="/_next/static/immutable/chunks/0j5a_qkw23a8h.js" async=""></script>
1<script src="/_next/static/immutable/chunks/27nx0w5vplu1q.js" async=""></script>
1<script src="/_next/static/immutable/chunks/turbopack-0-76quk2p9y02.js" async=""></script>
1<script src="/_next/static/immutable/chunks/2corl6wyj4669.js" async=""></script>
1<script src="/_next/static/immutable/chunks/31u8hbeg166vc.js" async=""></script>
1<script src="/_next/static/immutable/chunks/1kp1o4kqi1096.js" async=""></script>
1<title>Blog · Capframe</title><meta name="description" content="Long-form notes on AI agent security, capability-token design, and the findings.v1 schema."/><meta name="application-name" content="Capframe"/><meta name="author" content="Euan Crosson"/><meta name="keywords" content="AI agent security,MCP,Model Context Protocol,capability tokens,tool call policy,OWASP LLM,NIST AI RMF,MITRE ATLAS,agent runtime,Rust,Python"/><meta name="robots" content="index, follow"/><link rel="canonical" href="https://capframe.ai/blog"/><meta property="og:title" content="Capframe — Capability security for AI agents"/><meta property="og:description" content="Find. Bind. Guard. — Three modules for AI-agent discovery, capability tokens, and runtime enforcement, audit-mapped to OWASP / NIST / ATLAS."/><meta property="og:url" content="https://capframe.ai"/><meta property="og:site_name" content="Capframe"/><meta property="og:locale" content="en_US"/><meta property="og:image" content="https://capframe.ai/opengraph-image?97747759eab2bec1"/><meta property="og:image:type" content="image/png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Capframe — Capability security for AI agents"/><meta property="og:type" content="website"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:title" content="Capframe — Capability security for AI agents"/><meta name="twitter:description" content="Find. Bind. Guard."/><meta name="twitter:image" content="https://capframe.ai/twitter-image?cc40476c69a0e798"/><meta name="twitter:image:alt" content="Capframe — Capability security for AI agents"/><meta name="twitter:image:type" content="image/png"/><meta name="twitter:image:width" content="1200"/><meta name="twitter:image:height" content="630"/><link rel="icon" href="/icon.svg?icon.1eu5o1pocww4s.svg" sizes="any" type="image/svg+xml"/><meta name="next-size-adjust" content=""/>
1<script src="/_next/static/immutable/chunks/0c0hxoamwjsbw.js" noModule=""></script>
1</head><body class="min-h-screen bg-bg text-fg selection:bg-accent/30 selection:text-fg"><div hidden=""><!--$--><!--/$--></div><div class="border-b border-[var(--color-line)] bg-[var(--color-bg-2)]/60 backdrop-blur supports-[backdrop-filter]:bg-[var(--color-bg-2)]/40"><div class="max-w-[1440px] mx-auto px-6 sm:px-10 lg:px-16 py-2.5 flex items-center justify-between mono text-[10.5px] tracking-[0.18em] uppercase text-[var(--color-fg-3)]"><div class="flex items-center gap-6"><span class="inline-flex items-center gap-2"><span class="pulse"></span> <!-- -->v0.2.0<!-- --> · live</span><span class="hidden sm:inline">MIT</span><span class="hidden md:inline">MCP-native</span></div><div class="hidden sm:flex items-center gap-5"><span>OWASP LLM</span><span class="text-[var(--color-fg-4)]">·</span><span>NIST AI RMF</span><span class="text-[var(--color-fg-4)]">·</span><span>MITRE ATLAS</span></div></div></div><header class="sticky top-0 z-40 backdrop-blur supports-[backdrop-filter]:bg-[var(--color-bg)]/70 border-b border-[var(--color-line)]/80"><div class="max-w-[1440px] mx-auto px-6 sm:px-10 lg:px-16 h-14 flex items-center justify-between"><a class="flex items-center gap-2.5 group" href="/"><span class="w-7 h-7 rounded border border-[var(--color-accent)]/50 flex items-center justify-center font-mono text-[14px] text-[var(--color-accent)] group-hover:shadow-[0_0_16px_rgba(0,245,160,0.4)] transition-shadow">C</span><span class="mono text-[13px] tracking-[0.16em] uppercase">capframe</span></a><nav class="hidden md:flex items-center gap-7 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-fg-2)]"><a class="hover:text-fg transition-colors" href="/#modules">Modules</a><a class="hover:text-fg transition-colors" href="/#install">Install</a><a class="hover:text-fg transition-colors" href="/changelog">Changelog</a><a class="hover:text-fg transition-colors text-[var(--color-accent-3)]" href="/blog">Blog</a><a href="https://github.com/capframe/capframe" class="hover:text-fg transition-colors">GitHub ↗</a></nav><a class="md:hidden mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent)]" href="/#install">Install ↗</a></div></header><main class="px-6 sm:px-10 lg:px-16"><section class="max-w-[800px] mx-auto pt-16 sm:pt-24 pb-24"><div class="flex items-baseline gap-3 mb-4"><span class="mono text-[12px] text-[var(--color-accent)]">§ blog</span><span class="label">Notes from the build</span></div><h1 class="text-[clamp(2.4rem,5vw,3.6rem)] font-semibold tracking-[-0.03em] leading-[1.05]">Blog.</h1><p class="mt-6 text-[1.02rem] text-[var(--color-fg-2)] max-w-[36rem]">Long-form notes on AI agent security, capability tokens, the<code class="mx-1 text-[var(--color-accent-3)]">findings.v1</code>schema, and decisions we’ve made building Capframe.</p><ul class="mt-14 divide-y divide-[var(--color-line)]"><li class="py-7"><a class="group block" href="/blog/declared-reversibility"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-07-10">2026-07-10</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">ai agents</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Declared reversibility: the MCP primitive nobody ships</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">A model can&#x27;t tell drop_database from list_tables — and neither can the policy gate in front of it, because no MCP tool declares whether an action can be undone. Today reversibility is inferred from the verb in a tool&#x27;s name. Here&#x27;s why that&#x27;s the wrong layer, and what a one-field fix would change.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/scanning-agentic-banks"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-07-10">2026-07-10</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">ai agents</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Scanning the bank an AI agent runs on</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">Agentic banking shipped in 2026: MCP servers that let an AI agent open a business account, issue cards, and move money — ACH, wire, USDC on multiple chains — from a natural-language prompt. It&#x27;s the highest-blast-radius tool surface we&#x27;ve looked at. Here&#x27;s what a scan of that surface actually finds, and why it&#x27;s an attestation problem, not a gotcha.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/schema-vs-approval-gate"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-07-10">2026-07-10</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">ai agents</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Your approval gate lives below the agent</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">The best agent products gate money-moving actions behind human approval, spend limits, and role-based controls — at the infrastru
1cture level. That&#x27;s the right backstop. But the tool schema is what an injected agent actually reads, and it sits above the gate. Here&#x27;s the layer nobody audits.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/github-mcp-scored-16"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-06-27">2026-06-27</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">classifier</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">The official GitHub MCP server scored 16/100 on our leaderboard. Here&#x27;s what that actually means.</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">The lowest-scoring official server on the capframe.ai leaderboard is the one from GitHub. Not because it&#x27;s broken — because it exposes the most write surface, and the MCP protocol has no way to describe any of it. That&#x27;s the interesting part.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/heroku-mcp-scored-8"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-06-12">2026-06-12</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">ai-agents</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Heroku&#x27;s official MCP server scored 8/100. This one actually runs code.</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">Salesforce&#x27;s official Heroku MCP server scored 8/100 on our leaderboard — the lowest we&#x27;ve seen from a major vendor. Two of its 33 tools are arbitrary execution: a SQL console (pg_psql) and a shell with network + filesystem access (deploy_one_off_dyno). Not a CVE — a capability-hygiene gap, and a vivid one. Here&#x27;s the honest risk model.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/the-mcp-security-spread"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-06-03">2026-06-03</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">leaderboard</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">The MCP security spread: the servers that do the most score the worst</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">We now grade 84 MCP servers by running them in an ephemeral sandbox and scoring their real tool schemas. A clear pattern fell out: read-only docs servers score in the 90s, while the servers that actually act — scrape, automate, run code — score in the 0s to 40s. Tool surface is attack surface.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/red-teaming-my-own-engine"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-06-02">2026-06-02</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">ai agents</span><span class="text-[var(--color-accent-3)]">mcp</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">50.000000000000001 — how a floating-point number beat my own authorization engine</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">I pointed a four-agent red team at my own capability-security engine. It found four HIGH-severity authorization bypasses — including a number a hair over a $50 cap that the engine read as exactly $50. Here&#x27;s the worst one, and the honest scorecard.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/public-mcp-security-leaderboard"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-30">2026-05-30</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">leaderboard</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">We built a public security leaderboard for 48 MCP servers. The official GitHub server ties for worst.</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">capframe.ai/leaderboard ranks every MCP server we can find against deterministic security rules. 4 Critical and 23 High findings across the ecosystem. Updated daily. The score formula 
1is public and the rules are open-source. Here&#x27;s what it found, how we built it, and what&#x27;s wrong with it.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/r7-cairo-false-positive"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-30">2026-05-30</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">leaderboard</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">How a Critical became a documented false positive: R7 on code generators</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">While expanding the capframe HTTP corpus, our R7 classifier rule flagged two Critical findings on OpenZeppelin&#x27;s Cairo MCP server. Both were wrong. Here&#x27;s how we caught it before publishing, what the fix looks like, and why the discipline matters more than the rule.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/sandbox-grade-scoring"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-30">2026-05-30</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">leaderboard</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Sandbox-grade scoring: the MCP leaderboard now runs your server before grading it</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">We upgraded capframe.ai/leaderboard to actually run MCP servers in an ephemeral Docker container instead of reading their READMEs. Four official @modelcontextprotocol/server-* packages dropped from a false-clean score of 100 to 64–98. Here&#x27;s the diff and the design.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/scanning-16-popular-mcp-servers"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-27">2026-05-27</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">classifier</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">We scanned 16 popular MCP servers. Three ship arbitrary code execution — and our own scanner was wrong in both directions.</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">After the lab extremes — a server built to be broken (2 critical) and the official reference servers (0 critical) — we pointed Capframe at the messy middle: 16 MCP servers people actually npm-install. 111 tools, live. Three expose arbitrary JavaScript execution. Getting there meant fixing a false-positive class AND a false-negative class in our own classifier, both shipped.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/scanning-the-damn-vulnerable-mcp-server"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-27">2026-05-27</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">dogfooding</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">We pointed Capframe at the Damn Vulnerable MCP Server (and it found a gap in itself)</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">Dogfooding the Find module against a purpose-built insecure MCP server. It flagged all 10 tools for unconstrained input — but rated an arbitrary-shell-execution tool identically to a username lookup. Here&#x27;s the gap that surfaced, the rule we added, and what the scanner still misses.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/scanning-the-official-mcp-reference-servers"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-27">2026-05-27</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">mcp</span><span class="text-[var(--color-accent-3)]">classifier</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">We scanned the official MCP reference servers — live. Zero critical, nine high, and a hole in the protocol.</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">Last time we pointed Capframe&#x27;s Find module at a server built to be broken — 2 critical. This time we ran it live against the official Anthropic MCP reference servers. Same seven rules, opposite verdict: 0 critical. But all nine high-severity findings traced back to one thing the MCP protocol can&#x27;t express — and that&#x27;s the interesting part.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/capability-tokens-macaroon"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-26">2026-05-26</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">capabilities</span><span class="text-[var(--color-accent-3)]">macaroons</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Capability tokens for AI agents, the macaroon way</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">Most agent-permission systems check an ACL at the tool-call boundary. Capframe hands the agent a token that encodes what it&#x27;s allowed to do — macaroon-style, attenuable without phoning home, bound to a key so a stolen token is useless. Here&#x27;s the construction and where it stops working.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li><li class="py-7"><a class="group block" href="/blog/why-findings-v1"><div class="flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]"><time dateTime="2026-05-20">2026-05-20</time><span class="hidden sm:inline">·</span><span class="text-[var(--color-accent-3)]">security</span><span class="text-[var(--color-accent-3)]">schema</span><span class="text-[var(--color-accent-3)]">ai-agents</span></div><h2 class="text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors">Why we shipped findings.v1 as a public JSON Schema</h2><p class="mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]">AI agent security tools each emit findings in their own shape. We think they shouldn&#x27;t. Here&#x27;s the wire format we&#x27;re proposing as a starting point — and why we put it in the repo before writing the scanner.</p><span class="inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]">Read →</span></a></li></ul></section></main><footer class="mt-12 border-t border-[var(--color-line-2)] bg-[var(--color-bg-2)]"><div class="max-w-[1440px] mx-auto px-6 sm:px-10 lg:px-16 py-10 text-center mono text-[11px] tracking-[0.18em] uppercase text-[var(--color-fg-3)]"><span class="text-fg font-medium">Capframe</span><span class="mx-2 text-[var(--color-fg-4)]">·</span><span>v0.2.0</span><span class="mx-2 text-[var(--color-fg-4)]">·</span><span class="text-[var(--color-accent)]">find. bind. guard.</span></div></footer><!--$--><!--/$--><!--$!--><template data-dgst="BAILOUT_TO_CLIENT_SIDE_RENDERING"></template><!--/$-->
1<script src="/_next/static/immutable/chunks/25kvgcvbp6c14.js" id="_R_" async=""></script>
1<script>(self.__next_f=self.__next_f||[]).push([0])</script>
1<script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/immutable/chunks/2corl6wyj4669.js\"],\"default\"]\n3:I[37457,[\"/_next/static/immutable/chunks/2corl6wyj4669.js\"],\"default\"]\n4:I[2355,[\"/_next/static/immutable/chunks/31u8hbeg166vc.js\"],\"Analytics\"]\n6:I[97367,[\"/_next/static/immutable/chunks/2corl6wyj4669.js\"],\"OutletBoundary\"]\n7:\"$Sreact.suspense\"\na:I[97367,[\"/_next/static/immutable/chunks/2corl6wyj4669.js\"],\"ViewportBoundary\"]\nc:I[97367,[\"/_next/static/immutable/chunks/2corl6wyj4669.js\"],\"MetadataBoundary\"]\ne:I[68027,[\"/_next/static/immutable/chunks/31u8hbeg166vc.js\"],\"default\",1]\n:HL[\"/_next/static/immutable/chunks/1-pbzz7xe-6g3.css\",\"style\"]\n:HL[\"/_next/static/immutable/media/70bc3e132a0a741e-s.p.269kn9uafm0ti.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/immutable/media/caa3a2e1cccd8315-s.p.0zr6hhvz-h9nw.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n9:X\n0:{\"P\":null,\"c\":[\"\",\"blog\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"blog\",{\"children\":[\"__PAGE__\",{},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4624],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/immutable/chunks/1-pbzz7xe-6g3.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/immutable/chunks/31u8hbeg166vc.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"geist_456cb025-module__bi1xZG__variable jetbrains_mono_9a2f2d6c-module__wsyXyG__variable antialiased\",\"suppressHydrationWarning\":true,\"children\":[\"$\",\"body\",null,{\"className\":\"min-h-screen bg-bg text-fg selection:bg-accent/30 selection:text-fg\",\"children\":[[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}],[\"$\",\"$L4\",null,{}]]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[\"$L5\",[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/immutable/chunks/1kp1o4kqi1096.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$7\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@8\"}]}]]}],{}
1,null,false,null]},null,false,\"$9\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$La\",null,{\"children\":\"$Lb\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Lc\",null,{\"children\":[\"$\",\"$7\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Ld\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$e\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/immutable/chunks/1-pbzz7xe-6g3.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"r\":\"$undefined\",\"s\":\"$undefined\",\"a\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\"}\n9:C\nb:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nf:I[27201,[\"/_next/static/immutable/chunks/2corl6wyj4669.js\"],\"IconMark\"]\n8:null\nd:[[\"$\",\"title\",\"0\",{\"children\":\"Blog · Capframe\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Long-form notes on AI agent security, capability-token design, and the findings.v1 schema.\"}],[\"$\",\"meta\",\"2\",{\"name\":\"application-name\",\"content\":\"Capframe\"}],[\"$\",\"meta\",\"3\",{\"name\":\"author\",\"content\":\"Euan Crosson\"}],[\"$\",\"meta\",\"4\",{\"name\":\"keywords\",\"content\":\"AI agent security,MCP,Model Context Protocol,capability tokens,tool call policy,OWASP LLM,NIST AI RMF,MITRE ATLAS,agent runtime,Rust,Python\"}],[\"$\",\"meta\",\"5\",{\"name\":\"robots\",\"content\":\"index, follow\"}],[\"$\",\"link\",\"6\",{\"rel\":\"canonical\",\"href\":\"https://capframe.ai/blog\"}],[\"$\",\"meta\",\"7\",{\"property\":\"og:title\",\"content\":\"Capframe — Capability security for AI agents\"}],[\"$\",\"meta\",\"8\",{\"property\":\"og:description\",\"content\":\"Find. Bind. Guard. — Three modules for AI-agent discovery, capability tokens, and runtime enforcement, audit-mapped to OWASP / NIST / ATLAS.\"}],[\"$\",\"meta\",\"9\",{\"property\":\"og:url\",\"content\":\"https://capframe.ai\"}],[\"$\",\"meta\",\"10\",{\"property\":\"og:site_name\",\"content\":\"Capframe\"}],[\"$\",\"meta\",\"11\",{\"property\":\"og:locale\",\"content\":\"en_US\"}],[\"$\",\"meta\",\"12\",{\"property\":\"og:image\",\"content\":\"https://capframe.ai/opengraph-image?97747759eab2bec1\"}],[\"$\",\"meta\",\"13\",{\"property\":\"og:image:type\",\"content\":\"image/png\"}],[\"$\",\"meta\",\"14\",{\"property\":\"og:image:width\",\"content\":\"1200\"}],[\"$\",\"meta\",\"15\",{\"property\":\"og:image:height\",\"content\":\"630\"}],[\"$\",\"meta\",\"16\",{\"property\":\"og:image:alt\",\"content\":\"Capframe — Capability security for AI agents\"}],[\"$\",\"meta\",\"17\",{\"property\":\"og:type\",\"content\":\"website\"}],[\"$\",\"meta\",\"18\",{\"name\":\"twitter:card\",\"content\":\"summary_large_image\"}],[\"$\",\"meta\",\"19\",{\"name\":\"twitter:title\",\"content\":\"Capframe — Capability security for AI agents\"}],[\"$\",\"meta\",\"20\",{\"name\":\"twitter:description\",\"content\":\"Find. Bind. Guard.\"}],[\"$\",\"meta\",\"21\",{\"name\":\"twitter:image\",\"content\":\"https://capframe.ai/twitter-image?cc40476c69a0e798\"}],[\"$\",\"meta\",\"22\",{\"name\":\"twitter:image:alt\",\"content\":\"Capframe — Capability security for AI agents\"}],[\"$\",\"meta\",\"23\",{\"name\":\"twitter:image:type\",\"content\":\"image/png\"}],[\"$\",\"meta\",\"24\",{\"name\":\"twitter:image:width\",\"content\":\"1200\"}],[\"$\",\"meta\",\"25\",{\"name\":\"twitter:image:height\",\"content\":\"630\"}],[\"$\",\"link\",\"26\",{\"rel\":\"icon\",\"href\":\"/icon.svg?icon.1eu5o1pocww4s.svg\",\"sizes\":\"any\",\"type\":\"image/svg+xml\"}],[\"$\",\"$Lf\",\"27\",{}]]\n"])</script>
1<script>self.__next_f.push([1,"10:I[22016,[\"/_next/static/immutable/chunks/31u8hbeg166vc.js\",\"/_next/static/immutable/chunks/1kp1o4kqi1096.js\"],\"\"]\n5:[[\"$\",\"div\",null,{\"className\":\"border-b border-[var(--color-line)] bg-[var(--color-bg-2)]/60 backdrop-blur supports-[backdrop-filter]:bg-[var(--color-bg-2)]/40\",\"children\":[\"$\",\"div\",null,{\"className\":\"max-w-[1440px] mx-auto px-6 sm:px-10 lg:px-16 py-2.5 flex items-center justify-between mono text-[10.5px] tracking-[0.18em] uppercase text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-center gap-6\",\"children\":[[\"$\",\"span\",null,{\"className\":\"inline-flex items-center gap-2\",\"children\":[[\"$\",\"span\",null,{\"className\":\"pulse\"}],\" \",\"v0.2.0\",\" · live\"]}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"MIT\"}],[\"$\",\"span\",null,{\"className\":\"hidden md:inline\",\"children\":\"MCP-native\"}]]}],[\"$\",\"div\",null,{\"className\":\"hidden sm:flex items-center gap-5\",\"children\":[[\"$\",\"span\",null,{\"children\":\"OWASP LLM\"}],[\"$\",\"span\",null,{\"className\":\"text-[var(--color-fg-4)]\",\"children\":\"·\"}],[\"$\",\"span\",null,{\"children\":\"NIST AI RMF\"}],[\"$\",\"span\",null,{\"className\":\"text-[var(--color-fg-4)]\",\"children\":\"·\"}],[\"$\",\"span\",null,{\"children\":\"MITRE ATLAS\"}]]}]]}]}],[\"$\",\"header\",null,{\"className\":\"sticky top-0 z-40 backdrop-blur supports-[backdrop-filter]:bg-[var(--color-bg)]/70 border-b border-[var(--color-line)]/80\",\"children\":[\"$\",\"div\",null,{\"className\":\"max-w-[1440px] mx-auto px-6 sm:px-10 lg:px-16 h-14 flex items-center justify-between\",\"children\":[[\"$\",\"$L10\",null,{\"href\":\"/\",\"className\":\"flex items-center gap-2.5 group\",\"children\":[[\"$\",\"span\",null,{\"className\":\"w-7 h-7 rounded border border-[var(--color-accent)]/50 flex items-center justify-center font-mono text-[14px] text-[var(--color-accent)] group-hover:shadow-[0_0_16px_rgba(0,245,160,0.4)] transition-shadow\",\"children\":\"C\"}],[\"$\",\"span\",null,{\"className\":\"mono text-[13px] tracking-[0.16em] uppercase\",\"children\":\"capframe\"}]]}],[\"$\",\"nav\",null,{\"className\":\"hidden md:flex items-center gap-7 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-fg-2)]\",\"children\":[[\"$\",\"$L10\",null,{\"href\":\"/#modules\",\"className\":\"hover:text-fg transition-colors\",\"children\":\"Modules\"}],[\"$\",\"$L10\",null,{\"href\":\"/#install\",\"className\":\"hover:text-fg transition-colors\",\"children\":\"Install\"}],[\"$\",\"$L10\",null,{\"href\":\"/changelog\",\"className\":\"hover:text-fg transition-colors\",\"children\":\"Changelog\"}],[\"$\",\"$L10\",null,{\"href\":\"/blog\",\"className\":\"hover:text-fg transition-colors text-[var(--color-accent-3)]\",\"children\":\"Blog\"}],[\"$\",\"a\",null,{\"href\":\"https://github.com/capframe/capframe\",\"className\":\"hover:text-fg transition-colors\",\"children\":\"GitHub ↗\"}]]}],[\"$\",\"$L10\",null,{\"href\":\"/#install\",\"className\":\"md:hidden mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent)]\",\"children\":\"Install ↗\"}]]}]}],[\"$\",\"main\",null,{\"className\":\"px-6 sm:px-10 lg:px-16\",\"children\":[\"$\",\"section\",null,{\"className\":\"max-w-[800px] mx-auto pt-16 sm:pt-24 pb-24\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-4\",\"children\":[[\"$\",\"span\",null,{\"className\":\"mono text-[12px] text-[var(--color-accent)]\",\"children\":\"§ blog\"}],[\"$\",\"span\",null,{\"className\":\"label\",\"children\":\"Notes from the build\"}]]}],[\"$\",\"h1\",null,{\"className\":\"text-[clamp(2.4rem,5vw,3.6rem)] font-semibold tracking-[-0.03em] leading-[1.05]\",\"children\":\"Blog.\"}],[\"$\",\"p\",null,{\"className\":\"mt-6 text-[1.02rem] text-[var(--color-fg-2)] max-w-[36rem]\",\"children\":[\"Long-form notes on AI agent security, capability tokens, the\",[\"$\",\"code\",null,{\"className\":\"mx-1 text-[var(--color-accent-3)]\",\"children\":\"findings.v1\"}],\"schema, and decisions we’ve made building Capframe.\"]}],[\"$\",\"ul\",null,{\"className\":\"mt-14 divide-y divide-[var(--color-line)]\",\"children\":[[\"$\",\"li\",\"declared-reversibility\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/declared-reversibility\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-07-10\",\"children\":\"2026-07-10\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],\"$L11\",\"$L12\"]]}],\"$L13\",\"$L14\",\"$L15\"]}]}],\"$L16\",\"$L17\",\"$L18\",\"$L19\",\"$L1a\",\"$L1b\",\"$L1c\",\"$L1d\",\"$L1e\",\"$L1f\",\"$L20\",\"$L21\",\"$L22\",\"$L23\"]}],false]}]}],\"$L24\"]\n"])</script>
1<script>self.__next_f.push([1,"11:[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}]\n12:[\"$\",\"span\",\"ai agents\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"ai agents\"}]\n13:[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Declared reversibility: the MCP primitive nobody ships\"}]\n14:[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"A model can't tell drop_database from list_tables — and neither can the policy gate in front of it, because no MCP tool declares whether an action can be undone. Today reversibility is inferred from the verb in a tool's name. Here's why that's the wrong layer, and what a one-field fix would change.\"}]\n15:[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]\n16:[\"$\",\"li\",\"scanning-agentic-banks\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/scanning-agentic-banks\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-07-10\",\"children\":\"2026-07-10\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"ai agents\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"ai agents\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Scanning the bank an AI agent runs on\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"Agentic banking shipped in 2026: MCP servers that let an AI agent open a business account, issue cards, and move money — ACH, wire, USDC on multiple chains — from a natural-language prompt. It's the highest-blast-radius tool surface we've looked at. Here's what a scan of that surface actually finds, and why it's an attestation problem, not a gotcha.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n17:[\"$\",\"li\",\"schema-vs-approval-gate\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/schema-vs-approval-gate\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-07-10\",\"children\":\"2026-07-10\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"ai agents\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"ai agents\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Your approval gate lives below the agent\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"The best agent products gate money-moving actions behind human approval, spend limits, and role-based controls — at the infrastructure level. That's the right backstop. But the tool schema is what an injected agent actually reads, and it sits above the gate. Here's the layer nobody audits.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n18:[\"$\",\"li\",\"github-mcp-scored-16\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/github-mcp-scored-16\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-06-27\",\"children\":\"2026-06-27\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"classifier\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"classifier\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"The official GitHub MCP server scored 16/100 on our leaderboard. Here's what that actually means.\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"The lowest-scoring official server on the capframe.ai leaderboard is the one from GitHub. Not because it's broken — because it exposes the most write surface, and the MCP protocol has no way to describe any of it. That's the interesting part.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n19:[\"$\",\"li\",\"heroku-mcp-scored-8\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/heroku-mcp-scored-8\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-06-12\",\"children\":\"2026-06-12\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"ai-agents\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"ai-agents\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Heroku's official MCP server scored 8/100. This one actually runs code.\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"Salesforce's official Heroku MCP server scored 8/100 on our leaderboard — the lowest we've seen from a major vendor. Two of its 33 tools are arbitrary execution: a SQL console (pg_psql) and a shell with network + filesystem access (deploy_one_off_dyno). Not a CVE — a capability-hygiene gap, and a vivid one. Here's the honest risk model.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n1a:[\"$\",\"li\",\"the-mcp-security-spread\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/the-mcp-security-spread\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-06-03\",\"children\":\"2026-06-03\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"leaderboard\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"leaderboard\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"The MCP security spread: the servers that do the most score the worst\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"We now grade 84 MCP servers by running them in an ephemeral sandbox and scoring their real tool schemas. A clear pattern fell out: read-only docs servers score in the 90s, while the servers that actually act — scrape, automate, run code — score in the 0s to 40s. Tool surface is attack surface.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n1b:[\"$\",\"li\",\"red-teaming-my-own-engine\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/red-teaming-my-own-engine\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-06-02\",\"children\":\"2026-06-02\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"ai agents\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"ai agents\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"50.000000000000001 — how a floating-point number beat my own authorization engine\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"I pointed a four-agent red team at my own capability-security engine. It found four HIGH-severity authorization bypasses — including a number a hair over a $50 cap that the engine read as exactly $50. Here's the worst one, and the honest scorecard.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n1c:[\"$\",\"li\",\"public-mcp-security-leaderboard\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/public-mcp-security-leaderboard\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-30\",\"children\":\"2026-05-30\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"leaderboard\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"leaderboard\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"We built a public security leaderboard for 48 MCP servers. The official GitHub server ties for worst.\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"capframe.ai/leaderboard ranks every MCP server we can find against deterministic security rules. 4 Critical and 23 High findings across the ecosystem. Updated daily. The score formula 
1is public and the rules are open-source. Here's what it found, how we built it, and what's wrong with it.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n1d:[\"$\",\"li\",\"r7-cairo-false-positive\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/r7-cairo-false-positive\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-30\",\"children\":\"2026-05-30\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"leaderboard\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"leaderboard\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"How a Critical became a documented false positive: R7 on code generators\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"While expanding the capframe HTTP corpus, our R7 classifier rule flagged two Critical findings on OpenZeppelin's Cairo MCP server. Both were wrong. Here's how we caught it before publishing, what the fix looks like, and why the discipline matters more than the rule.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n1e:[\"$\",\"li\",\"sandbox-grade-scoring\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/sandbox-grade-scoring\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-30\",\"children\":\"2026-05-30\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"leaderboard\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"leaderboard\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Sandbox-grade scoring: the MCP leaderboard now runs your server before grading it\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"We upgraded capframe.ai/leaderboard to actually run MCP servers in an ephemeral Docker container instead of reading their READMEs. Four official @modelcontextprotocol/server-* packages dropped from a false-clean score of 100 to 64–98. Here's the diff and the design.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n1f:[\"$\",\"li\",\"scanning-16-popular-mcp-servers\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/scanning-16-popular-mcp-servers\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-27\",\"children\":\"2026-05-27\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"classifier\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"classifier\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"We scanned 16 popular MCP servers. Three ship arbitrary code execution — and our own scanner was wrong in both directions.\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"After the lab extremes — a server built to be broken (2 critical) and the official reference servers (0 critical) — we pointed Capframe at the messy middle: 16 MCP servers peo
1ple actually npm-install. 111 tools, live. Three expose arbitrary JavaScript execution. Getting there meant fixing a false-positive class AND a false-negative class in our own classifier, both shipped.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n20:[\"$\",\"li\",\"scanning-the-damn-vulnerable-mcp-server\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/scanning-the-damn-vulnerable-mcp-server\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-27\",\"children\":\"2026-05-27\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"dogfooding\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"dogfooding\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"We pointed Capframe at the Damn Vulnerable MCP Server (and it found a gap in itself)\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"Dogfooding the Find module against a purpose-built insecure MCP server. It flagged all 10 tools for unconstrained input — but rated an arbitrary-shell-execution tool identically to a username lookup. Here's the gap that surfaced, the rule we added, and what the scanner still misses.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n21:[\"$\",\"li\",\"scanning-the-official-mcp-reference-servers\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/scanning-the-official-mcp-reference-servers\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-27\",\"children\":\"2026-05-27\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"mcp\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"mcp\"}],[\"$\",\"span\",\"classifier\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"classifier\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"We scanned the official MCP reference servers — live. Zero critical, nine high, and a hole in the protocol.\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"Last time we pointed Capframe's Find module at a server built to be broken — 2 critical. This time we ran it live against the official Anthropic MCP reference servers. Same seven rules, opposite verdict: 0 critical. But all nine high-severity findings traced back to one thing the MCP protocol can't express — and that's the interesting part.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n22:[\"$\",\"li\",\"capability-tokens-macaroon\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/capability-tokens-macaroon\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-26\",\"children\":\"2026-05-26\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"capabilities\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"capabilities\"}],[\"$\",\"span\",\"macaroons\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"macaroons\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Capability tokens for AI agents, the macaroon way\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"Most agent-permission systems check an ACL at the tool-call boundary. Capframe hands the agent a token that encodes what it's allowed to do — macaroon-style, attenuable without phoning home, bound to a key so a stolen token is useless. Here's the construction and where it stops working.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n23:[\"$\",\"li\",\"why-findings-v1\",{\"className\":\"py-7\",\"children\":[\"$\",\"$L10\",null,{\"href\":\"/blog/why-findings-v1\",\"className\":\"group block\",\"children\":[[\"$\",\"div\",null,{\"className\":\"flex items-baseline gap-3 mb-2 mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"time\",null,{\"dateTime\":\"2026-05-20\",\"children\":\"2026-05-20\"}],[\"$\",\"span\",null,{\"className\":\"hidden sm:inline\",\"children\":\"·\"}],[[\"$\",\"span\",\"security\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"security\"}],[\"$\",\"span\",\"schema\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"sc
1hema\"}],[\"$\",\"span\",\"ai-agents\",{\"className\":\"text-[var(--color-accent-3)]\",\"children\":\"ai-agents\"}]]]}],[\"$\",\"h2\",null,{\"className\":\"text-[1.45rem] font-semibold tracking-[-0.015em] text-fg group-hover:text-[var(--color-accent)] transition-colors\",\"children\":\"Why we shipped findings.v1 as a public JSON Schema\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-[0.95rem] leading-[1.65] text-[var(--color-fg-2)]\",\"children\":\"AI agent security tools each emit findings in their own shape. We think they shouldn't. Here's the wire format we're proposing as a starting point — and why we put it in the repo before writing the scanner.\"}],[\"$\",\"span\",null,{\"className\":\"inline-block mt-3 mono text-[12px] tracking-[0.12em] uppercase text-[var(--color-accent-3)] group-hover:text-[var(--color-accent)]\",\"children\":\"Read →\"}]]}]}]\n24:[\"$\",\"footer\",null,{\"className\":\"mt-12 border-t border-[var(--color-line-2)] bg-[var(--color-bg-2)]\",\"children\":[\"$\",\"div\",null,{\"className\":\"max-w-[1440px] mx-auto px-6 sm:px-10 lg:px-16 py-10 text-center mono text-[11px] tracking-[0.18em] uppercase text-[var(--color-fg-3)]\",\"children\":[[\"$\",\"span\",null,{\"className\":\"text-fg font-medium\",\"children\":\"Capframe\"}],[\"$\",\"span\",null,{\"className\":\"mx-2 text-[var(--color-fg-4)]\",\"children\":\"·\"}],[\"$\",\"span\",null,{\"children\":\"v0.2.0\"}],[\"$\",\"span\",null,{\"className\":\"mx-2 text-[var(--color-fg-4)]\",\"children\":\"·\"}],[\"$\",\"span\",null,{\"className\":\"text-[var(--color-accent)]\",\"children\":\"find. bind. guard.\"}]]}]}]\n"])</script>
1</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.