<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>AI Coding Security on Code is cheap, let&#39;s talk</title>
    <link>https://blog.ferstar.org/en/series/ai-coding-security/</link>
    <description>Code is cheap, let&#39;s talk</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <copyright>© 2026 ferstar · [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright>
    <lastBuildDate>Sat, 26 Sep 2026 19:00:00 +0800</lastBuildDate>
    <ttl>60</ttl><atom:link href="https://blog.ferstar.org/en/series/ai-coding-security/index.xml" rel="self" type="application/rss+xml" /><image>
      <url>https://blog.ferstar.org/site-logo.png</url>
      <title>Code is cheap, let&#39;s talk</title>
      <link>https://blog.ferstar.org/</link>
    </image>
    
    <item>
      <title>Why I Built RunSeal: Setting an Execution Boundary for Local Coding Agents</title>
      <link>https://blog.ferstar.org/en/posts/local-first-agent-execution-boundary-runseal/</link>
      <pubDate>Sat, 26 Sep 2026 19:00:00 +0800</pubDate>
      
      <guid isPermaLink="true">https://blog.ferstar.org/en/posts/local-first-agent-execution-boundary-runseal/</guid>
      <description>Coding agents have long compromised between running naked on host systems and suffering from bloated Docker setups, facing structural risks of credential theft and uncontrolled egress; introducing RunSeal, an OS-native project conceived and shaped early on that enforces lightweight boundaries via workspace containment, managed proxies, and fail-closed architecture; delivering a millisecond-launch, cross-platform, non-intrusive safe execution boundary for local commands.</description><content:encoded><![CDATA[<blockquote><p>I am not a native English speaker; this article was translated by AI.</p>
</blockquote><p><a href="https://github.com/runseal-labs"  target="_blank" rel="noreferrer">RunSeal</a> took shape quite a long time ago.</p>
<p>The core execution logic and test suites had been working for a while, and I originally intended to write a quick note on it. But daily engineering work kept piling up, and open-source preparations stayed on the back burner. Now that boundary escapes and privacy risks have brought agent security back into focus, it is a good time to walk through the background, design trade-offs, and technical implementation.</p>
<hr>

<h2 class="relative group">The Trust Deficit in Local Command Execution
    <div id="the-trust-deficit-in-local-command-execution" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-trust-deficit-in-local-command-execution" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Coding agents have become daily drivers for many developers. From early tools like Aider and Open Interpreter to systems like Cursor, Claude Code, Devin, and Codex, running commands via AI has become routine: editing code, running test suites, executing build scripts, and installing dependencies.</p>
<p>Looking at raw execution logs, however, several concerns become apparent:</p>
<ol>
<li><strong>The agent runs as a standard child process on your machine.</strong> Whatever permissions your terminal holds, the agent inherits. Reading and writing within the project is expected; reading <code>~/.ssh/id_rsa</code>, scanning <code>~/.aws/credentials</code>, or inspecting neighboring codebases happens with zero kernel-level intervention.</li>
<li><strong>Interactive <code>[Y/n]</code> confirmations degrade quickly.</strong> Complex debugging sessions often trigger dozens of commands per turn. Sustaining close review of every command every few seconds is unrealistic. After a few iterations, confirmation prompts turn into routine keystrokes of Enter.</li>
<li><strong>Existing containment options introduce significant friction.</strong>
<ul>
<li>Docker containers: On macOS, bind mounts across VM boundaries introduce high I/O latency, making operations like <code>npm install</code> sluggish. Local virtual environments, compiler caches, and host utilities cannot be reused directly. Granting outbound network access to fetch packages means exfiltration paths remain open under adversarial prompt injection.</li>
<li>Regex command filters: Pattern-matching against strings like <code>rm -rf /</code> or <code>cat ~/.ssh</code> is fragile against simple string manipulation, Base64 decoding, or small wrapper scripts.</li>
<li>Minimal scripts: Many implementations focus solely on Linux primitives like <code>bwrap</code> or <code>landlock</code>, offering no equivalent on macOS or Windows. Network controls are often all-or-nothing: either disconnected entirely or left completely unrestricted.</li>
</ul>
</li>
</ol>
<p>Development often ends up caught between running unconfined for agility or accepting significant friction for security.</p>
<hr>

<h2 class="relative group">RunSeal’s Design Scope
    <div id="runseals-design-scope" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#runseals-design-scope" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>When starting RunSeal, the operational boundary in <code>AGENTS.md</code> was explicitly defined:</p>
<blockquote><p>RunSeal is an OS-native, policy-governed local command execution environment—focused strictly on local command containment, without enterprise approval dashboards or general automation frameworks.</p>
</blockquote><p>The objective is specific: confine command execution to the designated workspace at the OS kernel layer, without virtual machines or sacrificing local execution performance.</p>
<pre class="not-prose mermaid">
flowchart TD
    A[Agent Requests Command Execution] --> B[RunSeal CLI / RPC]
    B --> C{Check Host Capability}
    C -->|Capability Mismatch| D[Fail-Closed Immediate Error<br/>Never Silently Degrade]
    C -->|Policy Validated| E[Construct OS-Native Sandbox]
    
    subgraph OS[OS Kernel Boundary]
        E --> F[Filesystem: Workspace-Contained<br/>Confined to Workspace + Ephemeral Root]
        E --> G[Network Guard: Managed Proxy<br/>Block Raw Egress & Unauthorized Loopback]
    end
    
    F & G --> H[Execute Command & Capture Exit Status]
    H --> I[Append Immutable JSONL Audit Log]
    I --> J[Return Results to Agent]
</pre>

<p>The implementation focuses on four main areas:</p>

<h3 class="relative group">1. Millisecond Native Launch Without Virtualization
    <div id="1-millisecond-native-launch-without-virtualization" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-millisecond-native-launch-without-virtualization" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>RunSeal starts no virtual machines and runs no background daemon, calling OS isolation primitives directly:</p>
<ul>
<li><strong>Windows Reference Implementation</strong>: Implemented via Restricted Tokens, Job Objects, and specific ACLs, with policies mapped to execution plans through the <code>PlatformSandboxPlan</code> contract;</li>
<li><strong>macOS and Linux Alignment</strong>: macOS utilizes Seatbelt (<code>sandbox-exec</code>), while Linux combines Bubblewrap and Landlock. All three sandbox levels and network modes run natively, with managed proxy boundaries implemented across both platforms (RFC-0019 / RFC-0020).</li>
</ul>
<p>Because execution is native, launch latencies remain sub-millisecond, preserving compiler caches and local toolchains.</p>

<h3 class="relative group">2. Workspace Containment
    <div id="2-workspace-containment" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-workspace-containment" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Filesystem access is classified into four sandbox tiers: <code>read-only</code>, <code>workspace-write</code>, <code>workspace-contained</code>, and an explicit <code>danger-full-access</code> opt-out.</p>
<p>At <code>workspace-contained</code>:</p>
<ul>
<li>The process can only access the current workspace directory, an ephemeral runtime root, explicitly declared read-only paths, and minimal OS runtime baselines;</li>
<li>Remaining filesystem locations (such as <code>~/.ssh</code>, <code>~/.aws</code>, <code>~/.config</code>, or neighboring directories) are either invisible or denied access;</li>
<li>If prompt injection triggers access to <code>cat ~/.ssh/id_rsa</code> or path traversal, the kernel returns <code>Operation not permitted</code>.</li>
</ul>

<h3 class="relative group">3. Controlled Outbound Access (<code>network.proxy</code>)
    <div id="3-controlled-outbound-access-networkproxy" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-controlled-outbound-access-networkproxy" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Completely disabling network access prevents downloading packages or fetching documentation. Unrestricted outbound access, however, leaves egress unmonitored.</p>
<p>RunSeal’s <code>network.proxy</code> mode:</p>
<ul>
<li>Restricts direct connections to external IP addresses or domains;</li>
<li>Blocks unauthorized local loopback and host IPC;</li>
<li>Directs outbound traffic through a designated Managed Proxy endpoint.</li>
</ul>
<p>This allows the proxy layer to handle domain allowlists, credential redaction, and structured auditing.</p>

<h3 class="relative group">4. Fail-Closed over Silent Degradation
    <div id="4-fail-closed-over-silent-degradation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-fail-closed-over-silent-degradation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>If execution requests <code>workspace-contained</code> and <code>network.proxy</code>, but the host lacks sufficient permissions or kernel support, <strong>RunSeal exits immediately with a Fail-Closed error rather than silently degrading to an unconfined state</strong>.</p>
<p>Black-box test suites (RFC-0016) evaluate symlink traversal, directory escape, environment pollution, and process cleanup to ensure declared capabilities match actual system guarantees.</p>
<hr>

<h2 class="relative group">How It Looks in Practice
    <div id="how-it-looks-in-practice" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-looks-in-practice" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>RunSeal’s CLI is kept deliberately minimal: just a handful of parameters, making it effortless to integrate into agent automation harnesses or frameworks.</p>

<h3 class="relative group">1. Probe Host Capabilities
    <div id="1-probe-host-capabilities" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-probe-host-capabilities" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">runseal capabilities</span></span></code></pre></div></div>
<p>Outputs a JSON payload detailing what the current machine can strictly guarantee (excerpt; full output includes <code>features</code>, <code>capability_probes</code>, etc.):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">"platform"</span><span class="p">:</span> <span class="s2">"macos"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">"sandbox_levels"</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"read-only"</span><span class="p">:</span> <span class="s2">"supported"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"workspace-write"</span><span class="p">:</span> <span class="s2">"supported"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"workspace-contained"</span><span class="p">:</span> <span class="s2">"supported"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"danger-full-access"</span><span class="p">:</span> <span class="s2">"supported"</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">"network_modes"</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"unmanaged"</span><span class="p">:</span> <span class="s2">"supported"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"disabled"</span><span class="p">:</span> <span class="s2">"supported"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"proxy"</span><span class="p">:</span> <span class="s2">"supported"</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Protocol and policy versions are reported separately via <code>runseal version --json</code>.</p>

<h3 class="relative group">2. Run a Confined Command
    <div id="2-run-a-confined-command" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-run-a-confined-command" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Confine a command to the current workspace with network disabled:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">runseal <span class="nb">exec</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --policy workspace-contained <span class="se">\
</span></span></span><span class="line"><span class="cl">  --network disabled <span class="se">\
</span></span></span><span class="line"><span class="cl">  --cwd /Users/ferstar/myprojects/demo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -- python3 test_pipeline.py</span></span></code></pre></div></div>
<p>If a command attempts to read outside the workspace, the kernel rejects the read directly. Under default mode, the terminal echoes no errors—the command is blocked silently—but passing <code>--json</code> exposes the full result (excerpt):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">runseal <span class="nb">exec</span> --json <span class="se">\
</span></span></span><span class="line"><span class="cl">  --policy workspace-contained <span class="se">\
</span></span></span><span class="line"><span class="cl">  --network disabled <span class="se">\
</span></span></span><span class="line"><span class="cl">  --cwd /Users/ferstar/myprojects/demo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -- cat ~/.ssh/id_rsa</span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">"exit_code"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">"stderr"</span><span class="p">:</span> <span class="s2">"cat: /Users/ferstar/.ssh/id_rsa: Operation not permitted"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">"sandbox"</span><span class="p">:</span> <span class="p">{</span><span class="nt">"enforced"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">"level"</span><span class="p">:</span> <span class="s2">"workspace-contained"</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Note that the exit code of <code>runseal exec</code> itself is currently fixed at 0; the true exit status of the child process is reflected in the JSON result and audit logs. For automated pipelines, <code>--json</code> yields structured outputs, while <code>--events</code> streams newline-delimited lifecycle events.</p>

<h3 class="relative group">3. Local Structured Auditing
    <div id="3-local-structured-auditing" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-local-structured-auditing" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Every invocation logs its policy parameters, Canonical Policy Hash, network decisions, and exit statuses to an immutable local JSONL audit trail (excerpt; each event also includes <code>execution_id</code>, <code>session_id</code>, <code>policy_epoch</code>, etc.):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">"type"</span><span class="p">:</span><span class="s2">"policy.resolved"</span><span class="p">,</span><span class="nt">"policy_id"</span><span class="p">:</span><span class="s2">"workspace-contained"</span><span class="p">,</span><span class="nt">"policy_hash"</span><span class="p">:</span><span class="s2">"sha256:7cb64a73..."</span><span class="p">,</span><span class="nt">"sandbox_level"</span><span class="p">:</span><span class="s2">"workspace-contained"</span><span class="p">,</span><span class="nt">"network"</span><span class="p">:{</span><span class="nt">"mode"</span><span class="p">:</span><span class="s2">"disabled"</span><span class="p">,</span><span class="nt">"routes"</span><span class="p">:[]},</span><span class="nt">"time"</span><span class="p">:</span><span class="s2">"2026-09-26T11:21:26.967279Z"</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="nt">"type"</span><span class="p">:</span><span class="s2">"execution.finished"</span><span class="p">,</span><span class="nt">"exit_code"</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="nt">"status"</span><span class="p">:</span><span class="s2">"finished"</span><span class="p">,</span><span class="nt">"time"</span><span class="p">:</span><span class="s2">"2026-09-26T11:21:26.971042Z"</span><span class="p">}</span></span></span></code></pre></div></div>
<hr>

<h2 class="relative group">Final Thoughts
    <div id="final-thoughts" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#final-thoughts" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The recent ZCode snapshot upload incident simply thrust the long-standing risk of unconfined local agent execution into the public spotlight.</p>
<p>AI coding tools are evolving at breakneck speed. Models are getting sharper, and their autonomous execution loops are getting longer. But we cannot enjoy 5x code output while handing over the master keys to our machines on the assumption that vendors will always practice good hygiene or adhere to ambiguous privacy policies.</p>
<p>Day to day, we shouldn’t have to treat our own machines like hostile territory, but we also shouldn’t wait until our credentials are exposed before taking containment seriously.</p>
<p>Locking boundaries down at the OS kernel layer—allowing what should run and blocking what shouldn’t—is the only way to let local agents do real work safely.</p>
<p><em>(RunSeal’s RFC specifications and implementation are open source. Feel free to explore and contribute: <a href="https://github.com/runseal-labs"  target="_blank" rel="noreferrer">github.com/runseal-labs</a>)</em></p>
]]></content:encoded>
      
    </item>
    
  </channel>
</rss>
