Bypassing the sandbox

Every layout feature on one page, to see what a finished post looks like

Keep secrets of the implementation. Secrets are assumptions about an implementation that other parts of the system should not depend on.

Butler Lampson, Hints for computer system design

This post is a skeleton. 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.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 sidenote component and support markdown. 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.

Background#

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.Margin notes are the unnumbered version: use them for asides that do not need a citation number. The ⊕ symbol toggles them on narrow screens. The layout here tries to make that ordering physical: contents on the left, argument in the middle, evidence and asides in the margin.

The figure below is resized at build time into three WebP widths and served with srcset, 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.

A grey desk with equipment on it
Figure 1. Captions accept markdown too. Figures stay inside the text column unless you pass wide.

Threat model#

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:

BoundaryAttacker controlsSystem guarantees
Tool argumentsyesschema validation only
Model outputpartiallynone
Filesystemnoread-only mount, no network
Host kernelnoseccomp filter, separate user namespace

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.

What we assume works#

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.

What we do not assume#

Anything the system relies on but does not enforce. This is usually where the interesting bug lives.

Three boxes joined by arrows
Margin figures sit beside the text like a sidenote. They are handy for small diagrams that support a paragraph without interrupting it.

The escape#

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:

import os, json

def probe(path: str) -> dict:
    """Return what the sandbox lets us see at `path`."""
    try:
        entries = os.listdir(path)
    except PermissionError as e:
        return {"path": path, "error": str(e)}
    return {"path": path, "entries": entries[:20]}

print(json.dumps(probe("/"), indent=2))

Line numbers and highlighted lines are available if you need them:

fn main() {
    let uid = unsafe { libc::getuid() };
    assert_ne!(uid, 0, "should not be root inside the sandbox");
    println!("uid = {uid}");
}

A full-width figure spans the text column and the margin. Use it for wide screenshots, traces and timelines:

Three boxes joined by arrows
Figure 2. A full-width figure. On narrow screens it simply fills the column.

Video is self-hosted and just as plain: a <video> element pointing at a file next to the post, with the poster resized like a figure. For terminal sessions, record with asciinema and convert to an animated SVG with svg-term; it plays as an ordinary image.

Figure 3. A self-hosted clip. There is no third-party player and no tracking, and it plays inline on phones.

Mitigations#

Say what would have stopped this, in order of how much it costs the defender. Footnotes are the classic bottom-of-page kind[1] and can coexist with sidenotes; use footnotes for references and sidenotes for commentary.

Blockquotes are for quoting other people’s text at length. The rule on the left matches the timeline and the table of contents.

— someone, somewhere

Timeline#

A disclosure timeline is a table with dates in the first column:

DateEvent
2026-06-30Reported to vendor
2026-07-02Acknowledged
2026-08-01Fix shipped
2026-08-14This writeup
  1. A footnote. It lands at the end of the post. Zola renders these; nothing to configure.