{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Notes from the margin",
  "home_page_url": "https://x8svc.dev/",
  "feed_url": "https://x8svc.dev/feed.json",
  "description": "Writeups and working notes on agent security, isolation, and inference internals.",
  "authors": [{ "name": "x8svc" }],
  "language": "en",
  "items": [
    {
      "id": "https://x8svc.dev/bypassing-the-sandbox/",
      "url": "https://x8svc.dev/bypassing-the-sandbox/",
      "title": "Bypassing the sandbox",
      "summary": "A writeup skeleton: how this site handles sidenotes, figures, code, tables and footnotes.",
      "content_html": "<div class=\"epigraph\"><blockquote><p>Keep secrets of the implementation. Secrets are assumptions about an implementation that other parts of the system should not depend on.</p><footer>Butler Lampson, <em>Hints for computer system design</em></footer></blockquote></div>\n<p><span class=\"newthought\">This post is a skeleton.</span> It uses every feature of the layout so I can see what a finished writeup looks like, and the section headings below are the ones a security writeup usually needs. The text column is set in ET Book at roughly seventy characters per line, which is about as wide as a serif face stays comfortable.<label class=\"margin-toggle sidenote-number\"><input type=\"checkbox\"></label><span class=\"sidenote\">Sidenotes look like this. On wide screens they sit in the right margin beside the sentence that cites them; on a phone, tap the number to reveal one. They are written with the <code>sidenote</code> component and support <em>markdown</em>.</span> Headings become the table of contents automatically. On a wide screen it is pinned to the left of the text; on a narrow one it appears at the top.</p>\n<h2 id=\"background\">Background<a class=\"zola-anchor\" href=\"#background\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>Nothing about a writeup format is neutral. A reader should be able to find the threat model in ten seconds, skip the reproduction if they trust you, and land on the mitigations without scrolling past a wall of hexdumps.<label class=\"margin-toggle\"><input type=\"checkbox\">⊕</label><span class=\"marginnote\">Margin notes are the unnumbered version: use them for asides that do not need a citation number. The ⊕ symbol toggles them on narrow screens.</span> The layout here tries to make that ordering physical: contents on the left, argument in the middle, evidence and asides in the margin.</p>\n<p>The figure below is resized at build time into three WebP widths and served with <code>srcset</code>, so a phone downloads the 700-pixel one. Click it to enlarge; the lightbox is plain CSS and needs no script. The original file never leaves the repository, and the build fails if any image in it carries GPS metadata.</p>\n<figure>\n<a href=\"#lb-bench-jpg\" class=\"zoom\" aria-label=\"Enlarge image\"><img src=\"https://x8svc.dev/processed_images/bench.5e2393cb6c63a885.webp\" srcset=\"https://x8svc.dev/processed_images/bench.90ad8080b6289e24.webp 700w, https://x8svc.dev/processed_images/bench.5e2393cb6c63a885.webp 1400w, https://x8svc.dev/processed_images/bench.b4c0d5ecfa50e785.webp 2100w\" sizes=\"(min-width: 900px) 44rem, 100vw\" width=\"1400\" height=\"933\" alt=\"A grey desk with equipment on it\" loading=\"lazy\" decoding=\"async\"></a>\n<figcaption>Figure 1. Captions accept <em>markdown</em> too. Figures stay inside the text column unless you pass <code>wide</code>.</figcaption>\n</figure>\n<div class=\"lightbox\" id=\"lb-bench-jpg\" role=\"dialog\" aria-label=\"A grey desk with equipment on it\"><a href=\"#_\" class=\"lightbox-close\" aria-label=\"Close\"></a><img src=\"https://x8svc.dev/processed_images/bench.b4c0d5ecfa50e785.webp\" width=\"2100\" height=\"1400\" alt=\"A grey desk with equipment on it\" loading=\"lazy\"><a href=\"#_\" class=\"lightbox-x\" aria-label=\"Close\">&times;</a></div>\n<h2 id=\"threat-model\">Threat model<a class=\"zola-anchor\" href=\"#threat-model\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>State the assumptions explicitly. What does the attacker control, what do they not, and what does the system claim to guarantee at that boundary? A table is often the clearest way to put it:</p>\n<table><thead><tr><th>Boundary</th><th>Attacker controls</th><th>System guarantees</th></tr></thead><tbody>\n<tr><td>Tool arguments</td><td>yes</td><td>schema validation only</td></tr>\n<tr><td>Model output</td><td>partially</td><td>none</td></tr>\n<tr><td>Filesystem</td><td>no</td><td>read-only mount, no network</td></tr>\n<tr><td>Host kernel</td><td>no</td><td>seccomp filter, separate user namespace</td></tr>\n</tbody></table>\n<p>Numerals in tables use lining figures so the columns align; body text uses old-style figures, which sit in the x-height like lowercase letters. Compare 2026 in this sentence with the table above.</p>\n<h3 id=\"what-we-assume-works\">What we assume works<a class=\"zola-anchor\" href=\"#what-we-assume-works\" aria-label=\"Link to this section\">#</a>\n</h3>\n<p>Anything below the boundary you are attacking should be listed here and taken as given. If you later break one of these assumptions, that is a separate finding.</p>\n<h3 id=\"what-we-do-not-assume\">What we do not assume<a class=\"zola-anchor\" href=\"#what-we-do-not-assume\" aria-label=\"Link to this section\">#</a>\n</h3>\n<p>Anything the system relies on but does not enforce. This is usually where the interesting bug lives.</p>\n<p><label class=\"margin-toggle\"><input type=\"checkbox\">⊕</label><span class=\"marginnote\"><a href=\"https://x8svc.dev/bypassing-the-sandbox/flow.png\"><img src=\"https://x8svc.dev/processed_images/flow.ab004aa7d4293f37.webp\" width=\"700\" height=\"306\" alt=\"Three boxes joined by arrows\" loading=\"lazy\" decoding=\"async\"></a><br>Margin figures sit beside the text like a sidenote. They are handy for small diagrams that support a paragraph without interrupting it.</span></p>\n<h2 id=\"the-escape\">The escape<a class=\"zola-anchor\" href=\"#the-escape\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>The reproduction goes here. Code blocks are highlighted at build time into CSS classes, so they follow the light and dark colour schemes without any script:</p>\n<pre class=\"giallo z-l-code z-d-code\" ><code data-lang=\"python\"><span class=\"giallo-l\"><span class=\"z-l-17 z-d-17\">import</span><span> os</span><span class=\"z-l-6 z-d-9\">,</span><span> json</span></span>\n<span class=\"giallo-l\"></span>\n<span class=\"giallo-l\"><span class=\"z-l-17 z-d-17\">def</span><span class=\"z-l-14 z-d-18\"> probe</span><span>(</span><span class=\"z-l-9 z-d-11\">path</span><span class=\"z-l-6 z-d-9\">:</span><span class=\"z-l-4 z-d-1\"> str</span><span>)</span><span class=\"z-l-6 z-d-9\"> -&gt;</span><span class=\"z-l-4 z-d-1\"> dict</span><span>:</span></span>\n<span class=\"giallo-l\"><span class=\"z-l-8 z-d-8\">    &quot;&quot;&quot;</span><span class=\"z-l-8 z-d-8\">Return what the sandbox lets us see at `path`.</span><span class=\"z-l-8 z-d-8\">&quot;&quot;&quot;</span></span>\n<span class=\"giallo-l\"><span class=\"z-l-17 z-d-17\">    try</span><span class=\"z-l-6 z-d-9\">:</span></span>\n<span class=\"giallo-l\"><span>        entries</span><span class=\"z-l-16 z-d-16\"> =</span><span> os</span><span class=\"z-l-6 z-d-9\">.</span><span class=\"z-l-14 z-d-18\">listdir</span><span>(</span><span>path</span><span>)</span></span>\n<span class=\"giallo-l\"><span class=\"z-l-17 z-d-17\">    except</span><span class=\"z-l-4 z-d-1\"> PermissionError</span><span class=\"z-l-17 z-d-17\"> as</span><span> e</span><span class=\"z-l-6 z-d-9\">:</span></span>\n<span class=\"giallo-l\"><span class=\"z-l-17 z-d-17\">        return</span><span> {</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-8 z-d-8\">path</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-6 z-d-9\">:</span><span> path</span><span class=\"z-l-6 z-d-9\">,</span><span class=\"z-l-8 z-d-8\"> &quot;</span><span class=\"z-l-8 z-d-8\">error</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-6 z-d-9\">:</span><span class=\"z-l-4 z-d-1\"> str</span><span>(</span><span>e</span><span>)</span><span>}</span></span>\n<span class=\"giallo-l\"><span class=\"z-l-17 z-d-17\">    return</span><span> {</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-8 z-d-8\">path</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-6 z-d-9\">:</span><span> path</span><span class=\"z-l-6 z-d-9\">,</span><span class=\"z-l-8 z-d-8\"> &quot;</span><span class=\"z-l-8 z-d-8\">entries</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-6 z-d-9\">:</span><span> entries</span><span>[</span><span class=\"z-l-6 z-d-9\">:</span><span class=\"z-l-9 z-d-11\">20</span><span>]</span><span>}</span></span>\n<span class=\"giallo-l\"></span>\n<span class=\"giallo-l\"><span class=\"z-l-15 z-d-14\">print</span><span>(</span><span>json</span><span class=\"z-l-6 z-d-9\">.</span><span class=\"z-l-14 z-d-18\">dumps</span><span>(</span><span class=\"z-l-14 z-d-18\">probe</span><span>(</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-8 z-d-8\">/</span><span class=\"z-l-8 z-d-8\">&quot;</span><span>)</span><span class=\"z-l-6 z-d-9\">,</span><span> indent</span><span class=\"z-l-16 z-d-16\">=</span><span class=\"z-l-9 z-d-11\">2</span><span>)</span><span>)</span></span></code></pre>\n<p>Line numbers and highlighted lines are available if you need them:</p>\n<pre class=\"giallo z-l-code z-d-code\" ><code data-lang=\"rust\"><span class=\"giallo-l\"><span aria-hidden=\"true\" class=\"giallo-ln\">1</span><span class=\"z-l-17 z-d-17\">fn</span><span class=\"z-l-14 z-d-18\"> main</span><span>(</span><span>)</span><span> {</span></span>\n<span class=\"giallo-l\"><span aria-hidden=\"true\" class=\"giallo-ln\">2</span><span class=\"z-l-17 z-d-17\">    let</span><span> uid</span><span class=\"z-l-16 z-d-16\"> =</span><span class=\"z-l-17 z-d-17\"> unsafe</span><span> {</span><span class=\"z-l-1 z-d-3\"> libc</span><span class=\"z-l-16 z-d-16\">::</span><span class=\"z-l-14 z-d-18\">getuid</span><span>(</span><span>)</span><span> }</span><span>;</span></span>\n<span class=\"giallo-l z-l-hl z-d-hl\"><span aria-hidden=\"true\" class=\"giallo-ln\">3</span><span class=\"z-l-14 z-d-18\">    assert_ne!</span><span>(</span><span>uid</span><span>,</span><span class=\"z-l-9 z-d-11\"> 0</span><span>,</span><span class=\"z-l-8 z-d-8\"> &quot;</span><span class=\"z-l-8 z-d-8\">should not be root inside the sandbox</span><span class=\"z-l-8 z-d-8\">&quot;</span><span>)</span><span>;</span></span>\n<span class=\"giallo-l\"><span aria-hidden=\"true\" class=\"giallo-ln\">4</span><span class=\"z-l-14 z-d-18\">    println!</span><span>(</span><span class=\"z-l-8 z-d-8\">&quot;</span><span class=\"z-l-8 z-d-8\">uid = </span><span class=\"z-l-8 z-d-8\">{</span><span class=\"z-l-8 z-d-8\">uid</span><span class=\"z-l-8 z-d-8\">}</span><span class=\"z-l-8 z-d-8\">&quot;</span><span>)</span><span>;</span></span>\n<span class=\"giallo-l\"><span aria-hidden=\"true\" class=\"giallo-ln\">5</span><span>}</span></span></code></pre>\n<p>A full-width figure spans the text column and the margin. Use it for wide screenshots, traces and timelines:</p>\n<figure class=\"fullwidth\">\n<a href=\"#lb-flow-png\" class=\"zoom\" aria-label=\"Enlarge image\"><img src=\"https://x8svc.dev/processed_images/flow.2c35902b634a350c.webp\" srcset=\"https://x8svc.dev/processed_images/flow.ab004aa7d4293f37.webp 700w, https://x8svc.dev/processed_images/flow.2c35902b634a350c.webp 1400w\" sizes=\"(min-width: 900px) 72rem, 100vw\" width=\"1400\" height=\"612\" alt=\"Three boxes joined by arrows\" loading=\"lazy\" decoding=\"async\"></a>\n<figcaption>Figure 2. A full-width figure. On narrow screens it simply fills the column.</figcaption>\n</figure>\n<div class=\"lightbox\" id=\"lb-flow-png\" role=\"dialog\" aria-label=\"Three boxes joined by arrows\"><a href=\"#_\" class=\"lightbox-close\" aria-label=\"Close\"></a><img src=\"https://x8svc.dev/processed_images/flow.ccfad77598621fa4.webp\" width=\"2100\" height=\"918\" alt=\"Three boxes joined by arrows\" loading=\"lazy\"><a href=\"#_\" class=\"lightbox-x\" aria-label=\"Close\">&times;</a></div>\n<p>Video is self-hosted and just as plain: a <code>&lt;video&gt;</code> element pointing at a file next to the post, with the poster resized like a figure. For terminal sessions, record with <code>asciinema</code> and convert to an animated SVG with <code>svg-term</code>; it plays as an ordinary image.</p>\n<figure class=\"video\">\n<video controls preload=\"metadata\" playsinline poster=\"https://x8svc.dev/processed_images/clip-poster.e22b661ba34ea401.webp\"><source src=\"https://x8svc.dev/bypassing-the-sandbox/clip.mp4\" type=\"video/mp4\">Your browser can't play this inline; <a href=\"https://x8svc.dev/bypassing-the-sandbox/clip.mp4\">download the file</a>.</video>\n<figcaption>Figure 3. A self-hosted clip. There is no third-party player and no tracking, and it plays inline on phones.</figcaption>\n</figure>\n<h2 id=\"mitigations\">Mitigations<a class=\"zola-anchor\" href=\"#mitigations\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>Say what would have stopped this, in order of how much it costs the defender. Footnotes are the classic bottom-of-page kind<sup class=\"footnote-reference\" id=\"fr-1-1\"><a href=\"https://x8svc.dev/bypassing-the-sandbox/#fn-1\">[1]</a></sup> and can coexist with sidenotes; use footnotes for references and sidenotes for commentary.</p>\n<blockquote>\n<p>Blockquotes are for quoting other people’s text at length. The rule on the left matches the timeline and the table of contents.</p>\n<footer>— someone, somewhere</footer>\n</blockquote>\n<h2 id=\"timeline\">Timeline<a class=\"zola-anchor\" href=\"#timeline\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>A disclosure timeline is a table with dates in the first column:</p>\n<table><thead><tr><th>Date</th><th>Event</th></tr></thead><tbody>\n<tr><td>2026-06-30</td><td>Reported to vendor</td></tr>\n<tr><td>2026-07-02</td><td>Acknowledged</td></tr>\n<tr><td>2026-08-01</td><td>Fix shipped</td></tr>\n<tr><td>2026-08-14</td><td>This writeup</td></tr>\n</tbody></table>\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn-1\">\n<p>A footnote. It lands at the end of the post. Zola renders these; nothing to configure. <a href=\"https://x8svc.dev/bypassing-the-sandbox/#fr-1-1\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2026-08-14T00:00:00Z",
      "tags": ["writeup","sandboxing","meta"]
    },
    {
      "id": "https://x8svc.dev/notes-on-agent-isolation/",
      "url": "https://x8svc.dev/notes-on-agent-isolation/",
      "title": "Notes on agent isolation",
      "summary": "Working notes, and a demonstration of maths rendering.",
      "content_html": "<p><span class=\"newthought\">Notes posts</span> are shorter and less finished than writeups. This one exists to show that maths works. Setting <code>math = true</code> in a post’s front matter loads a self-hosted copy of KaTeX for that page only; every other page ships with no script at all.</p>\n<h2 id=\"inline-and-display\">Inline and display<a class=\"zola-anchor\" href=\"#inline-and-display\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>Inline maths uses dollar signs: the expected number of probes before a collision is about $\\sqrt{\\pi n / 2}$ for $n$ buckets. Display maths uses double dollars:</p>\n<p>$$\nP(\\text{escape}) = 1 - \\prod_{i=1}^{k} (1 - p_i)\n$$</p>\n<h2 id=\"when-the-markdown-parser-gets-in-the-way\">When the Markdown parser gets in the way<a class=\"zola-anchor\" href=\"#when-the-markdown-parser-gets-in-the-way\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>Underscores and asterisks inside <code>$…$</code> are sometimes eaten by the Markdown parser before KaTeX sees them. When that happens, wrap the expression in the <code>math</code> component, which passes the body through untouched:</p>\n<div class=\"math-block\">\\[\\hat{\\theta}_{\\text{MLE}} = \\arg\\max_{\\theta} \\sum_{i=1}^{n} \\log f(x_i \\mid \\theta)\\]</div>\n<figure>\n<a href=\"#lb-layers-png\" class=\"zoom\" aria-label=\"Enlarge image\"><img src=\"https://x8svc.dev/processed_images/layers.754fd0eafb294e86.webp\" srcset=\"https://x8svc.dev/processed_images/layers.09cf36c1105c0ae1.webp 700w\" sizes=\"(min-width: 900px) 44rem, 100vw\" width=\"1400\" height=\"612\" alt=\"Three boxes joined by arrows\" loading=\"lazy\" decoding=\"async\"></a>\n<figcaption>Isolation layers, left to right. A colocated image, resized at build time.</figcaption>\n</figure>\n<div class=\"lightbox\" id=\"lb-layers-png\" role=\"dialog\" aria-label=\"Three boxes joined by arrows\"><a href=\"#_\" class=\"lightbox-close\" aria-label=\"Close\"></a><img src=\"https://x8svc.dev/processed_images/layers.9571163d93557e44.webp\" width=\"2100\" height=\"918\" alt=\"Three boxes joined by arrows\" loading=\"lazy\"><a href=\"#_\" class=\"lightbox-x\" aria-label=\"Close\">&times;</a></div>\n<h2 id=\"open-questions\">Open questions<a class=\"zola-anchor\" href=\"#open-questions\" aria-label=\"Link to this section\">#</a>\n</h2>\n<ul>\n<li>Where does the policy live, and who can change it?</li>\n<li>What is logged when it is violated, and who reads the log?</li>\n</ul>\n",
      "date_published": "2026-06-02T00:00:00Z",
      "tags": ["notes","isolation"]
    },
    {
      "id": "https://x8svc.dev/reading-inference-internals/",
      "url": "https://x8svc.dev/reading-inference-internals/",
      "title": "Reading inference internals",
      "summary": "A reading list, kept as a post so it has a permanent address.",
      "content_html": "<p><span class=\"newthought\">Some posts are just lists.</span> This one is a reading list. It has headings so that the table of contents does something useful.</p>\n<h2 id=\"serving-stacks\">Serving stacks<a class=\"zola-anchor\" href=\"#serving-stacks\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>What the request path looks like between the socket and the first token. Batching, scheduling, KV-cache placement.</p>\n<h2 id=\"kernels\">Kernels<a class=\"zola-anchor\" href=\"#kernels\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>Where the numbers actually get multiplied, and what a kernel can and cannot see about the request it is serving.</p>\n<h2 id=\"provenance\">Provenance<a class=\"zola-anchor\" href=\"#provenance\" aria-label=\"Link to this section\">#</a>\n</h2>\n<p>Attaching an identity to a token stream and keeping it attached across tool calls.<label class=\"margin-toggle sidenote-number\"><input type=\"checkbox\"></label><span class=\"sidenote\">This is the part most people skip and the part that decides whether the rest of the pipeline is auditable.</span></p>\n",
      "date_published": "2026-03-27T00:00:00Z",
      "tags": ["notes","inference"]
    },
    {
      "id": "https://x8svc.dev/hello-world/",
      "url": "https://x8svc.dev/hello-world/",
      "title": "Hello, world",
      "summary": "Why this site exists.",
      "content_html": "<p><span class=\"newthought\">A short first post,</span> mostly so the index has two years on it and the year marker on the timeline gets used. There are no headings here, so this page has no table of contents, and the text sits in the same place it would with one. The columns are fixed; things appear in them only when a post has something to put there.</p>\n",
      "date_published": "2025-11-03T00:00:00Z",
      "tags": ["meta"]
    }
  ]
}
