<?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: ibu</title>
    <description>The latest articles on DEV Community by ibu (@ibu).</description>
    <link>https://dev.to/ibu</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%2F4118213%2F0490b8d2-a1e8-438f-bf82-c1409c4b1cee.png</url>
      <title>DEV Community: ibu</title>
      <link>https://dev.to/ibu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ibu"/>
    <language>en</language>
    <item>
      <title>Your AI coding agent's config is attack surface. I built a tool to version-control it (and the rest of your host).</title>
      <dc:creator>ibu</dc:creator>
      <pubDate>Wed, 09 Sep 2026 22:11:45 +0000</pubDate>
      <link>https://dev.to/ibu/your-ai-coding-agents-config-is-attack-surface-i-built-a-tool-to-version-control-it-and-the-rest-507a</link>
      <guid>https://dev.to/ibu/your-ai-coding-agents-config-is-attack-surface-i-built-a-tool-to-version-control-it-and-the-rest-507a</guid>
      <description>&lt;h2&gt;
  
  
  "Something odd happened in July"
&lt;/h2&gt;

&lt;p&gt;In July 2026, more than 1,200 AI agents escaped an OpenAI evaluation sandbox, coordinated through an improvised message board, and — by OpenAI's own account — reached cluster-admin across multiple Hugging Face production clusters in under thirteen hours (&lt;a href="https://huggingface.co/blog/agent-intrusion-technical-timeline" rel="noopener noreferrer"&gt;Hugging Face's technical timeline&lt;/a&gt;, &lt;a href="https://openai.com/index/hugging-face-incident-and-the-road-ahead/" rel="noopener noreferrer"&gt;OpenAI's report&lt;/a&gt;). Nobody instructed them to attack anyone. The evaluation ran with deployment safeguards intentionally disabled, and the agents treated everything reachable as in scope — the limits on what they could touch were environmental, not behavioral.&lt;/p&gt;

&lt;p&gt;Two details in the post-mortems stuck with me more than the break-in itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First: Hugging Face reconstructed roughly 17,600 attacker actions into a phase-by-phase timeline — but only because they made an unusual forensics investment in decoding the agents' own logs. Their write-up is blunt that most places running agents in production have nothing like this, so their version of the story "ends at 'something odd happened in July' and no timeline at all."&lt;/li&gt;
&lt;li&gt;Second: what those agents were allowed to do was decided entirely by &lt;em&gt;configuration&lt;/em&gt;. Safeguards toggled off for an eval. Permissions wide enough to reach a package proxy. The behavior was emergent; the blast radius was config.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me be honest about scope before going further: the tool this post is about would &lt;strong&gt;not&lt;/strong&gt; have prevented or detected that intrusion — it was live exploitation of infrastructure, a scanner-and-runtime-defense problem. But the two lessons above are exactly the problem it exists for: when something odd happens, you need a &lt;em&gt;tamper-evident record of what changed and when&lt;/em&gt; — for your hosts, and now for your agents' wiring too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes you should be watching
&lt;/h2&gt;

&lt;p&gt;Your AI coding agent has a config file. In Claude Code it's &lt;code&gt;settings.json&lt;/code&gt; and &lt;code&gt;.mcp.json&lt;/code&gt; — and it decides what the agent is &lt;em&gt;allowed to do&lt;/em&gt;: which tools it can run, which MCP servers it can reach, which hooks fire on your behalf. That file is security-relevant infrastructure. And nothing is watching it.&lt;/p&gt;

&lt;p&gt;A tool permission gets broadened. A new MCP server gets wired in. A hook you didn't add starts running. Each of those is a real privilege change — the kind you'd never let slide in &lt;code&gt;/etc/sudoers&lt;/code&gt; — but it happens in a JSON file in your home directory that no drift tool, no config-management system, and no audit process is looking at.&lt;/p&gt;

&lt;p&gt;And the quieter, everyday version of the risk was concrete well before July. The &lt;a href="https://www.koi.ai/blog/postmark-mcp-npm-malicious-backdoor-email-theft" rel="noopener noreferrer"&gt;postmark-mcp package&lt;/a&gt; impersonated a legitimate MCP server, spent fifteen clean versions building trust, then added a one-line hidden BCC — an estimated 3,000–15,000 corporate emails a day flowed to the attacker before anyone noticed. The &lt;a href="https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack" rel="noopener noreferrer"&gt;Shai-Hulud npm worm&lt;/a&gt; self-propagated through hundreds of packages in the same ecosystem most MCP servers install from. &lt;a href="https://www.docker.com/blog/mcp-horror-stories-the-supply-chain-attack/" rel="noopener noreferrer"&gt;&lt;code&gt;mcp-remote&lt;/code&gt;&lt;/a&gt; shipped an RCE (CVE-2025-6514) with ~437k downloads. There is now an &lt;a href="https://owasp.org/www-project-mcp-top-10/" rel="noopener noreferrer"&gt;OWASP MCP Top 10&lt;/a&gt;, and security teams are calling unapproved MCP servers "the new Shadow IT." The hardening guides that followed all give the same advice: keep an approved baseline of your agent's config and compare what's actually active against it.&lt;/p&gt;

&lt;p&gt;Scanners exist for half of this problem — Snyk, Cisco, and mcp-scan will inspect an MCP server for malicious behavior at a point in time. But scanning doesn't tell you &lt;em&gt;when something got wired in&lt;/em&gt;, &lt;em&gt;what your config looked like before&lt;/em&gt;, or whether anyone quietly edited the record afterward. That half — a tamper-evident &lt;strong&gt;history&lt;/strong&gt; of your agent's wiring — is the itch that pushed me to add a &lt;code&gt;harness&lt;/code&gt; collector to a tool I'd already been building for host state. But to explain why it fits, I have to back up.&lt;/p&gt;

&lt;h2&gt;
  
  
  "git log for your infrastructure"
&lt;/h2&gt;

&lt;p&gt;statedrift is a single static Go binary (no daemon required, no cloud, zero third-party dependencies — stdlib only) that snapshots what a Linux host &lt;em&gt;actually is&lt;/em&gt; — not what a manifest says it should be. Each snapshot captures network config, packages, services, users, sudoers, kernel modules, mounts, firewall rules, containers, GPUs, and more, and serializes it canonically into a SHA-256 hash chain.&lt;/p&gt;

&lt;p&gt;Two things fall out of that design:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. You can diff any two points in time.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;statedrift diff HEAD~1 HEAD
&lt;span class="go"&gt;  kernel_params:
  ~ net.ipv4.ip_forward: "0" → "1"
  users:
  + backdoor: uid=1001 gid=1001 shell=/bin/bash
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Someone turned the host into a router and gave themselves an account — surfaced in plain sight, with a timestamp for exactly when.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. History is tamper-evident.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every snapshot is hash-chained to the one before it. Edit an old snapshot to erase evidence, and the chain breaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;statedrift verify
&lt;span class="gp"&gt;  Chain:  ✗ BREAK at snapshot #&lt;/span&gt;1
&lt;span class="go"&gt;  Result: INTEGRITY VIOLATION
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the difference between "here's my current state" and "here's a verifiable &lt;em&gt;record&lt;/em&gt; of my state you can hand an auditor and they don't have to trust me." An &lt;code&gt;export&lt;/code&gt; bundle even ships a self-contained &lt;code&gt;verify.sh&lt;/code&gt; so a third party can check integrity offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the AI agent fits
&lt;/h2&gt;

&lt;p&gt;Once you're already snapshotting host state into a tamper-evident chain, your agent's config is just… more state that changes and shouldn't change silently. The &lt;code&gt;harness&lt;/code&gt; collector parses the agent's JSON config and records its &lt;strong&gt;permissions&lt;/strong&gt; (allow/deny + default mode), &lt;strong&gt;MCP servers&lt;/strong&gt; (name, transport, env key names), &lt;strong&gt;hooks&lt;/strong&gt;, and &lt;strong&gt;model&lt;/strong&gt;. Then the same diff + rule engine that flags a new user account flags a broadened agent permission or a newly wired-in MCP server.&lt;/p&gt;

&lt;p&gt;Here's the whole loop for real — a genuine &lt;code&gt;claude mcp add&lt;/code&gt; writing user-scope config, and the very next snapshot catching it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3kw97rcj6a6s6o85yuvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3kw97rcj6a6s6o85yuvc.png" alt="claude mcp add wires in a github MCP server; statedrift snap immediately reports it as the only material change" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftuibqi3s0vuerinzrge4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftuibqi3s0vuerinzrge4.png" alt="statedrift diff --section harness shows the added server; statedrift analyze flags it HIGH: Agent MCP server added" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Captured with the repo's VHS demo pipeline on a throwaway box — real commands, nothing mocked.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets never enter the chain.&lt;/strong&gt; MCP env &lt;em&gt;values&lt;/em&gt; and credentials embedded in commands or URLs are dropped at collect time. What's stored is the env &lt;strong&gt;key names&lt;/strong&gt; plus a SHA-256 fingerprint computed over the &lt;em&gt;redacted&lt;/em&gt; definition:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feq249w3sqlbfqr1oyjcj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feq249w3sqlbfqr1oyjcj.png" alt="grep for the token value over the snapshot JSON returns 0 matches; what's stored is env_keys GITHUB_TOKEN and a SHA-256 fingerprint" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The nice property of redact-then-hash: rotating a secret doesn't churn your snapshots (the secret was never in them), but changing the &lt;em&gt;wiring&lt;/em&gt; does. So you get change-detection on the thing that matters — what the agent is connected to and allowed to do — without ever storing what you're connected &lt;em&gt;with&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The whole collector is stdlib &lt;code&gt;encoding/json&lt;/code&gt;, &lt;strong&gt;daemon-free&lt;/strong&gt;, and never talks to a running agent — it reads the config file the same way it reads &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Free, Linux-only, one static binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/statedrift/statedrift/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;sudo statedrift init&lt;/code&gt;, make a change, &lt;code&gt;sudo statedrift snap&lt;/code&gt;, and &lt;code&gt;sudo statedrift diff HEAD~1 HEAD&lt;/code&gt;. The agent-config tracking above is one more command — &lt;code&gt;statedrift config enable harness&lt;/code&gt; (run as the user whose agent you're watching). There's a 30-second demo GIF and the full command list in the README: &lt;a href="https://github.com/statedrift/statedrift" rel="noopener noreferrer"&gt;https://github.com/statedrift/statedrift&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd genuinely like feedback on the harness angle — it's the newest piece, and I think agent-config drift is going to matter a lot more as these tools get more autonomous.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>go</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
