<?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: Leo</title>
    <description>The latest articles on DEV Community by Leo (@lbrauer).</description>
    <link>https://dev.to/lbrauer</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3927459%2F52f3985c-a4ee-4797-b6f7-b1075c72a9a0.png</url>
      <title>DEV Community: Leo</title>
      <link>https://dev.to/lbrauer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lbrauer"/>
    <language>en</language>
    <item>
      <title>LocalFirst – I built a harness for my AI tool proxy, found 2 bypasses</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Tue, 12 May 2026 15:15:30 +0000</pubDate>
      <link>https://dev.to/lbrauer/localfirst-i-built-a-harness-for-my-ai-tool-proxy-found-2-bypasses-41ll</link>
      <guid>https://dev.to/lbrauer/localfirst-i-built-a-harness-for-my-ai-tool-proxy-found-2-bypasses-41ll</guid>
      <description>&lt;p&gt;Hi — I built &lt;strong&gt;LocalFirst&lt;/strong&gt;, a local boundary layer for AI coding agents like Claude Code and MCP clients.&lt;/p&gt;

&lt;p&gt;It sits between the agent and the cloud model and decides, per tool result, what is allowed to re-enter the next request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LOCAL&lt;/strong&gt; – run in-process; the tool result's bytes are not forwarded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PASS&lt;/strong&gt; – forward unchanged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BLOCK&lt;/strong&gt; – synthetic refusal goes back to the model; the action never runs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TRANSFORM&lt;/strong&gt; – redact secrets, distill an 800-line grep result to the relevant 50 lines, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One human-readable &lt;code&gt;policy.yml&lt;/code&gt; drives all of it. The same engine governs Claude Code traffic via an HTTP proxy on port 8081 and MCP traffic, so a single &lt;code&gt;deny_paths&lt;/code&gt; rule produces byte-identical BLOCK rows under both protocols.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I'm posting v0.8.0
&lt;/h2&gt;

&lt;p&gt;I built a real test harness for it. The harness spawns an actual Claude Code subordinate session in a scratch tempdir with a scratch policy and audit chain, points it at the proxy, and runs adversarial scenarios end-to-end against a real model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two real bypasses fell out on the first useful run.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bypass 1: deny_paths via Claude Code auto-context
&lt;/h3&gt;

&lt;p&gt;The host runtime injects synthetic &lt;code&gt;tool_use Read&lt;/code&gt; + &lt;code&gt;tool_result &amp;lt;file content&amp;gt;&lt;/code&gt; pairs into the OUTBOUND request body as agentic context, before any model-emitted tool call.&lt;/p&gt;

&lt;p&gt;My original gate sat at the tool-call boundary on the response side and never saw them. A denied file's bytes were reaching the model anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bypass 2: Secret-redaction via the same path
&lt;/h3&gt;

&lt;p&gt;An AKIA-shaped fixture inside an auto-context &lt;code&gt;tool_result&lt;/code&gt; was forwarded unredacted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit tests could not have caught either of these.&lt;/strong&gt; They were not bugs in my policy logic. They were a category of traffic the policy logic never saw, because that body shape only appears when a real host runtime is exercising the proxy.&lt;/p&gt;




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

&lt;p&gt;A second enforcement gate on the outbound request body: &lt;code&gt;src/outbound-policy.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same policy now applies at both boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path 1&lt;/strong&gt; – tool-call boundary, model-emitted &lt;code&gt;tool_use&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path 2&lt;/strong&gt; – outbound auto-context boundary, client-injected &lt;code&gt;tool_result&lt;/code&gt; in the next request body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That symmetry now covers &lt;code&gt;deny_paths&lt;/code&gt;, secret redaction, distillation, and &lt;code&gt;max_output_tokens&lt;/code&gt;. Each path-2 enforcement writes its own audit row with &lt;code&gt;direction: "outbound"&lt;/code&gt;, so the report command and the independent verifier see both gates uniformly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit story
&lt;/h2&gt;

&lt;p&gt;The log is a SHA-256 hash chain from a fixed genesis sentinel.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docs/AUDIT.md&lt;/code&gt; contains a ~30-line standalone Python walker, so a buyer or auditor can confirm chain integrity without trusting LocalFirst's own code.&lt;/p&gt;

&lt;p&gt;The harness re-walks the chain on every scenario, so a defended-but-chain-broken outcome is correctly failed instead of silently passing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What LocalFirst is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a sandbox against the developer on a single-user box.&lt;/strong&gt; The shell alias is trivially bypassable. The point is enforceable org policy plus tamper-evident evidence, not adversarial isolation from the user. The real-world fit is the same as a corporate HTTP proxy: meaningful when the base URL is set by something the user cannot edit on their own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not provider-agnostic yet.&lt;/strong&gt; Adapters currently cover Claude Code, Cline, and MCP. OpenAI and Gemini agents are not in scope today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parts of the adapter layer carry Claude-Code-specific shape knowledge.&lt;/strong&gt; The auto-context body format that produced bypass #1 is a Claude Code-specific pattern, not a universal one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform support:&lt;/strong&gt; I develop on Windows and the harness is validated there. macOS and Linux should work, but I have not run the harness on them yet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node ≥18&lt;/li&gt;
&lt;li&gt;2,372 unit tests, 113 smoke tests&lt;/li&gt;
&lt;li&gt;Apache-2.0 with explicit patent grant&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&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; &lt;span class="nt"&gt;-g&lt;/span&gt; @localfirst-ai/localfirst
localfirst policy init
localfirst register
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/localfirst-ai/localfirst" rel="noopener noreferrer"&gt;https://github.com/localfirst-ai/localfirst&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cross-protocol demo: &lt;a href="https://github.com/localfirst-ai/localfirst/blob/main/docs/demos/mcp-block.md" rel="noopener noreferrer"&gt;https://github.com/localfirst-ai/localfirst/blob/main/docs/demos/mcp-block.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Audit verifier: &lt;a href="https://github.com/localfirst-ai/localfirst/blob/main/docs/AUDIT.md" rel="noopener noreferrer"&gt;https://github.com/localfirst-ai/localfirst/blob/main/docs/AUDIT.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
