Design note
Validation before execution — why IDOP has no best-effort mode
An IDOP reader checks every byte of a package before any of it runs, and refuses the whole file on the first failure. This note explains the reasoning, and what it costs.
Secure executionFile formats
Most document formats are forgiving. A reader that meets something it does not understand skips it, repairs it, or shows what it can. For passive content this is often the right choice: a partially rendered page is better than none.
For a format that carries code, forgiveness is a liability. Every repair path is a second interpretation of the same bytes, and every second interpretation is a place where what the user was shown and what actually runs can diverge. IDOP therefore takes the opposite position: a reader validates the entire package before exposing any entry to document code, and a single failure refuses the whole file (IDOP 1.0, §9.1).
What is checked, and in what order
The order matters, because each step protects the next:
- Size — the file is compared against the reader’s limits before it is parsed.
- ZIP structure, independently — the end-of-central-directory record, the central directory and every local header are parsed by the reader’s own code, not only by a ZIP library. The two views must agree. This is what rejects overlapping entries, polyglot files and archives that read differently in different tools.
- Identification — the first entry must be
mimetype, stored, with exactlyapplication/vnd.idop+zip. - Paths — every name must be valid UTF-8 in NFC, use one of the permitted roots, and avoid traversal, device names and case collisions.
- Decompression, bounded — every entry is inflated into a bounded buffer with per-entry, aggregate and ratio limits, and every CRC is verified.
- Manifest —
idop.jsonis validated against its JSON Schema and then against rules a schema cannot express. - Executable boundary — only
code/may contain HTML, JavaScript or CSS, and that code must not contain inline scripts, remote URLs or dynamic evaluation. - Capabilities — required configuration and credentials are resolved before the user is asked anything.
Why refuse instead of repair
There are three reasons.
Predictability. If a package opens in one conforming reader, it opens in all of them, and does the same thing. A validator that a developer runs and a reader that a user runs apply the same rules; in our implementation they are literally the same code.
A single interpretation. Attacks on archive formats usually work by making two parsers disagree. Requiring agreement between an independent parser and a library — and refusing on disagreement — removes the ambiguity rather than choosing a side.
Clear errors. Every refusal carries a stable error code (IDOP-ZIP-…, IDOP-PATH-…, IDOP-POLICY-…). A developer learns exactly which rule failed; a user is not left with a document that half works.
What it costs
Strictness has a price. Packages produced by careless tools are refused, even when they would probably have worked. Some legitimate web code — inline event handlers, a script tag with a body, a font loaded from a CDN — must be rewritten. And a document cannot degrade gracefully when one of its own files is damaged.
We think the trade is right for a format whose purpose is to be safe to open. It also makes the format easier to implement correctly: a reader has one path, not a family of recovery strategies.
Defence in depth, not the boundary
The static checks in step 7 are deliberately described as defence in depth. They catch common mistakes and obvious attacks early, with good error messages. They are not what keeps a document contained: the sandbox is (§9.3). A reader must enforce isolation whether or not a static check would have caught a construct, because static analysis of a programming language is never complete.