Engineering

Inside the reader: what happens before a document runs

A walk through the IDOP reader's path from an untrusted file to a running, sandboxed document.

IDOP LABS2 min read

When you open an .idop file in IDOP Cloud, a lot happens before the first line of the document’s code runs. This post walks through it in order.

One core, compiled twice

The rules of the format are implemented once, in Rust. The same crate is compiled to WebAssembly for the browser and to a native binary for the idop command-line tool. When a developer runs idop validate and gets OK, the reader will accept the same bytes — they are checked by the same code.

Step 1: parse the archive twice

The reader parses the ZIP structure with its own code — end of central directory, central directory, every local header — and separately through a ZIP library. If the two views disagree on any entry’s name, size, method or CRC, the file is refused. Archive attacks generally rely on two parsers seeing different files; requiring agreement removes the ambiguity.

Step 2: names, sizes and the manifest

Every entry name is checked: valid UTF-8, normalised, a permitted root, no traversal, no Windows device names, no two names that collide after case folding. Every entry is decompressed into a bounded buffer with per-entry, total and ratio limits. Then idop.json is validated against its schema and against rules a schema cannot express — for example, that every credential binding is used by a capability.

Step 3: the executable boundary

Only code/ may contain HTML, JavaScript or CSS. The reader rejects inline scripts, remote URLs, <iframe> and <form>, @import, eval and workers. These checks catch mistakes early, with precise error codes — but they are not what contains the document.

Step 4: the sandbox

What contains the document is an <iframe> with sandbox="allow-scripts" and without allow-same-origin: an opaque origin, with a content security policy of default-src 'none' plus exactly what the reader needs to serve the package’s own files. The document cannot read the reader’s storage or cookies, cannot reach the network, and cannot see other documents. It talks to the reader through one MessagePort per session.

If the manifest declares network capabilities, the reader resolves credentials and shows the user which credential would travel to which origins, for what purpose. Only then does the document start. Every idop.network.request is checked against the grant, stripped of authorisation headers, given the credential on the reader’s side, kept from following redirects, and counted against a per-session budget.

Step 6: saving is a new revision

The document writes to a working copy of storage/. Nothing touches the file until the user saves. Saving builds a new package that differs only in storage/ and the revision fields of idop.json, validates it completely, and only then replaces the old file — refusing if the file changed on disk in the meantime.

That is the whole path. It is deliberately strict, and deliberately boring: the goal is that opening a document is never the risky part of using it.