IDOP Format Specification 1.0
| Status | Candidate Recommendation — container and manifest frozen; editorial changes only |
| Date | 2026-10-01 |
| This version | https://idoplabs.com/spec/idop/1.0/ |
| Media types | application/vnd.idop+zip (package), application/vnd.idop.bundle+zip (bundle) |
| File extensions | .idop, .idopbundle |
| Editor | IDOP LABS |
| Feedback | spec@idoplabs.com |
| License | Text: CC BY 4.0. Schemas and conformance files: MIT OR Apache-2.0 |
Abstract
IDOP is a file format for interactive documents. One .idop file carries a
document’s user interface (HTML, CSS, JavaScript), its passive resources, and
its portable state. A conforming Reader validates the whole file before any of
its code runs, executes that code in an isolated sandbox with no network access
unless the user grants it, and writes changed state back into the file as a new
revision. Secrets never enter the file.
This specification defines the package container, the manifest, the executable
boundary, the storage and revision model, the Runtime API available to document
code, the permission model for network access, the .idopbundle transport, and
the requirements a Reader must meet.
Table of contents
- Conformance
- Terminology
- Overview
- Package container
- Paths
- Limits
- Manifest
- Executable boundary
- Reader processing model
- Storage, Save and revisions
- Runtime API
- Capabilities, credentials and permissions
- Portable Items (extension)
- Bundles and containers
- Versioning and compatibility
- Error codes
- Security considerations
- Privacy considerations
- Accessibility considerations
- IANA considerations
- References Annex A — JSON Schemas · Annex B — Conformance suite · Annex C — Example · Annex D — Changes from the drafts
1. Conformance
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals.
This specification defines three conformance classes:
- Package — a file that satisfies sections 4–8 and, where it uses them, sections 10–14.
- Producer — software that writes packages (an editor, a generator, an AI
tool, the
idop packcommand). A Producer MUST write only conforming packages, SHOULD write them deterministically (§4.4), and MUST NOT write a secret into a package. - Reader — software that opens packages and runs them. A Reader MUST implement section 9 in full. A Reader that runs document code MUST implement sections 10–12 and 17.1. A Reader MAY refuse any package it cannot process fully; it MUST NOT process a package partially.
Sections marked (informative), examples and notes are not normative.
2. Terminology
- Package — a ZIP archive conforming to this specification, normally with
the extension
.idop. - Document — what a person sees when a package is opened: the running application together with its state.
- Manifest — the JSON file
idop.jsonat the package root. - Entry point — the HTML file under
code/that the Reader loads first. - Document code — every entry under
code/. - State — the entries under
storage/, the last saved portable state. - Overlay — the Reader’s in-memory copy of state during a session (§10).
- Revision — one saved version of a document, identified by a UUID.
- Capability — a permission the document declares and the user may grant, in 1.0 always network access to exact origins (§12).
- Credential binding — a declared need for a secret the Reader holds and the document never sees.
- Origin — scheme, host and port, as defined by [RFC 6454]; in this
specification always
https. - Session — one opening of one document by a Reader.
3. Overview (informative)
my-board.idop (ZIP)
├── mimetype application/vnd.idop+zip first entry, Stored
├── idop.json manifest
├── code/ document code: HTML, JS, CSS, JSON
│ ├── index.html entry point
│ └── app.js
├── resources/ passive files: images, fonts, data
├── storage/ saved state, written by the Reader on Save
└── _idop/ reserved for this specification
The design follows two established formats: the container identification of
OpenDocument and EPUB (a Stored mimetype first entry), and the web platform’s
origin model for isolation. What IDOP adds is the contract between the two: the
file says what it wants, the Reader decides what it gets, and the user is asked
before anything leaves the device.
4. Package container
4.1 ZIP profile
A package MUST be a ZIP archive [APPNOTE] with these restrictions:
- No bytes MAY precede the first local file header or follow the end of central directory record. (Self-extracting and polyglot files are refused.)
- ZIP64, multi-volume and spanned archives MUST NOT be used.
- Encryption MUST NOT be used.
- Every entry MUST use compression method 0 (Store) or 8 (Deflate).
- Entry names that contain non-ASCII characters MUST set the UTF-8 flag (general purpose bit 11), and every name MUST be valid UTF-8.
- The local header and central directory record of each entry MUST agree on name, flags, method, sizes and CRC-32.
- No two central directory records MAY reference the same local header.
- Directory entries, symbolic links, devices and other special entries MUST NOT appear. Directories exist only as prefixes of file names.
- The central directory MUST be contiguous and immediately precede the end of central directory record.
4.2 The mimetype entry
The first local file header MUST start at offset 0 and MUST describe an entry
named exactly mimetype, stored with method 0, without an extra field, whose
content is exactly the ASCII string:
application/vnd.idop+zip
with no byte order mark, no trailing whitespace and no line terminator
(IDOP-PROFILE-002). Because the entry has no extra field (IDOP-ZIP-019) and
its name is eight bytes long, the media type sits at byte offset 38 of every
IDOP 1.0 package — which is what lets software identify the file without
decompressing it (§20).
4.3 Roots
Every entry name MUST begin with one of these roots:
| Root | Role | Executable |
|---|---|---|
mimetype |
container identification | no |
idop.json |
manifest | no |
code/** |
document code: .html, .js, .mjs, .css, .json |
yes |
resources/** |
passive resources | no |
storage/** |
last saved portable state | no |
_idop/** |
reserved for this specification | no |
extensions/<namespace>/** |
data of extensions | no |
Any other root MUST cause the package to be refused (IDOP-PATH-010).
<namespace> MUST be a lowercase reverse-domain identifier
(com.example.feature) followed by at least one child path segment
(IDOP-PATH-012).
The following names under _idop/ are reserved for future versions of this
specification. A 1.0 Reader MUST ignore them, and a 1.0 Producer MUST NOT write
them except as defined by a later version:
| Reserved | Intended use |
|---|---|
_idop/signatures/ |
publisher signatures over the package (planned for 1.1) |
_idop/thumbnail.png |
a preview image for file managers and galleries |
_idop/encryption/ |
encryption metadata |
4.4 Deterministic writing
A Producer SHOULD write packages deterministically: mimetype first, idop.json
second, remaining entries in byte-wise lexicographic order of their names, a
fixed modification time, and fixed permissions. Packing the same inputs twice
SHOULD produce byte-identical files. (The reference idop pack does.)
5. Paths
Every entry name, and every path used by the Runtime API, MUST satisfy:
- relative,
/-separated, encoded as UTF-8 and normalised to Unicode NFC (IDOP-PATH-004); - at most 240 bytes and at most 16 segments (
IDOP-PATH-001,IDOP-PATH-005); - no leading
/, no\, no UNC or drive prefix such asC:(IDOP-PATH-002,IDOP-PATH-009); - no empty,
.or..segment (IDOP-PATH-006); - no NUL or control character, no
:and no segment ending in.or a space (IDOP-PATH-003,IDOP-PATH-007); - no segment that is a Windows device name (
CON,PRN,AUX,NUL,COM1–COM9,LPT1–LPT9, with or without an extension) (IDOP-PATH-008); - no two entries whose names are equal after NFC normalisation and Unicode case
folding (
IDOP-PATH-030).
These rules make every package extractable on every common file system without any entry escaping, colliding with or replacing another.
6. Limits
A Reader MUST accept any package within these limits and MAY refuse any package that exceeds them:
| Limit | Value | Error |
|---|---|---|
| compressed package size | 64 MiB | IDOP-LIMIT-001 |
| number of entries | 4 096 | IDOP-LIMIT-002 |
| uncompressed size of one entry | 16 MiB | IDOP-LIMIT-003 |
| compression ratio of one entry | 100 : 1 | IDOP-LIMIT-004 |
| total uncompressed size | 128 MiB | IDOP-LIMIT-005 |
idop.json size |
256 KiB | IDOP-LIMIT-010 |
| manifest JSON depth / node count | 32 / 4 096 | IDOP-LIMIT-011 |
state (storage/**) during a session |
8 MiB, 1 024 files | IDOP-STORAGE-QUOTA |
A Producer MUST NOT write a package that exceeds them. Future versions MAY raise these limits; they will not lower them.
7. Manifest
7.1 Syntax
idop.json MUST be a UTF-8 JSON object [RFC 8259] that validates against the
schema in Annex A (https://idoplabs.com/spec/idop/1.0/schemas/manifest.json).
Duplicate object keys MUST cause refusal (IDOP-MANIFEST-001). Members not
defined by the schema MUST cause refusal (IDOP-MANIFEST-003): the place for
anything else is extensions.
7.2 Members
| Member | Required | Value |
|---|---|---|
format |
yes | exactly "https://idoplabs.com/ns/idop/package" |
formatVersion |
yes | exactly "1.0" |
runtimeApiVersion |
yes | exactly "1.0" |
entryPoint |
yes | path of an existing .html entry under code/ |
application |
yes | object, §7.3 |
document |
yes | object, §7.4 |
state |
yes | { "schemaVersion": <SemVer> } — version of the document’s own state format |
requiredFeatures |
yes | array of unique strings; 1.0 defines only idop.core-storage-v1 |
optionalFeatures |
yes | array of unique strings |
requiredCapabilities |
yes | array of capabilities, §12.1 |
optionalCapabilities |
yes | array of capabilities, §12.1 |
environmentBindings |
yes | array, §12.3 |
credentialBindings |
yes | array, §12.2 |
extensions |
yes | object; keys are reverse-domain namespaces |
A Reader MUST refuse a package whose requiredFeatures contains a value it does
not implement (IDOP-UNSUPPORTED-002) and MUST ignore unknown
optionalFeatures.
7.3 application
| Member | Required | Value |
|---|---|---|
id |
yes | lowercase reverse-domain identifier, ≤ 160 bytes (com.example.board) |
version |
yes | Semantic Versioning 2.0.0 |
title |
yes | 1–120 characters |
description |
no | ≤ 1 000 characters |
author |
no | ≤ 160 characters |
icon |
no | path of an entry under resources/ |
All application values are self-declared. Nothing in 1.0 proves who wrote
a package. A Reader MUST NOT present them as verified and SHOULD label them as
supplied by the document.
7.4 document
| Member | Required | Value |
|---|---|---|
id |
yes | UUID identifying the document across revisions |
revisionId |
yes | UUID of this revision |
parentRevisionId |
yes | UUID of the previous revision, or null for the first |
createdAt |
yes | RFC 3339 timestamp in UTC (Z) |
modifiedAt |
yes | RFC 3339 timestamp in UTC, not earlier than createdAt |
parentRevisionId MUST NOT equal revisionId (IDOP-MANIFEST-014). A document
identity grants nothing: it is not a security principal.
7.5 Container agreement
The mimetype entry and the manifest MUST agree: a manifest with
formatVersion "1.0" MUST be in a container whose mimetype is
application/vnd.idop+zip (IDOP-PROFILE-005).
8. Executable boundary
- Only entries under
code/are executable. An entry underresources/orstorage/with the extension.html,.htm,.js,.mjs,.cssor.wasmMUST cause refusal (IDOP-POLICY-001). - Entries under
code/MUST be UTF-8 text with the extension.html,.js,.mjs,.cssor.json(IDOP-POLICY-002,IDOP-POLICY-003). - HTML under
code/MUST NOT contain inline script (every<script>has asrcand an empty body), and MUST NOT contain<iframe>,<object>,<embed>,<form>,<base>or remote URLs (IDOP-POLICY-011,IDOP-POLICY-013,IDOP-POLICY-014). - CSS under
code/MUST NOT contain remote URLs or@import(IDOP-POLICY-012). - Document code MUST NOT use dynamic evaluation (
eval,new Function),javascript:URLs, WebAssembly, workers, service workers orwindow.open(IDOP-POLICY-010).
These checks are static and therefore defence in depth, not the boundary. The boundary is the Reader’s sandbox (§9.3, §17.1): a Reader MUST enforce isolation whether or not the static checks would have caught a construct.
9. Reader processing model
9.1 Validation before execution
A Reader MUST perform these steps, in order, and MUST NOT expose any entry to document code, or run any document code, until all of them succeed:
- Check the file size against §6.
- Parse the ZIP structure independently of any ZIP library — end of central directory, central directory, local headers — and enforce §4.1.
- Check the first entry and its content against §4.2 (
IDOP-PROFILE-002). - Validate every entry name against §4.3 and §5.
- Decompress every entry into a bounded buffer, enforcing per-entry, aggregate and ratio limits and verifying every CRC-32 and size.
- Parse and validate
idop.jsonagainst §7, including §7.5. - Enforce the executable boundary, §8.
- Resolve required features, configuration and capabilities (§12.4).
A failure at any step MUST cause the whole package to be refused with a stable error code (§16). There is no “best effort” mode: a Reader MUST NOT run a package that failed validation, and MUST NOT run part of one.
9.2 Loading
The Reader loads entryPoint and serves the other entries under code/ and
resources/ to the document by path. It MUST NOT serve storage/ as files:
state is reached only through the Runtime API (§11). Entries are served with a
media type derived from their extension; unknown extensions MUST be served as
application/octet-stream.
9.3 Isolation
Document code MUST run in an execution context that:
- cannot read or modify the Reader’s own interface, storage, cookies or credentials;
- has no direct network access: no
fetch, XHR, WebSocket, beacon, form submission, navigation or resource load to any remote origin; - cannot open windows or frames other than those the Reader itself provides;
- reaches the Reader only through the Runtime API transport the Reader injects.
In a web browser the RECOMMENDED realisation is an <iframe> with
sandbox="allow-scripts" and without allow-same-origin (an opaque
origin), whose content security policy is
default-src 'none' plus the sources the Reader itself needs to serve package
entries. A Reader MUST NOT grant allow-same-origin to document code.
9.4 One document, one context
Each session MUST have its own context and its own Runtime API channel. One session MUST NOT be able to reach another session’s channel, state or capabilities.
10. Storage, Save and revisions
10.1 Overlay
On opening, the Reader copies storage/** into an in-memory overlay. Reads and
lists see the overlay; writes and deletes change only the overlay. The package
file is not modified until the user saves.
Paths given to the storage API are relative to storage/ and MUST satisfy §5.
A path that names the storage/ prefix itself, or escapes it, MUST be refused
(IDOP-PATH-020).
10.2 Durability states
A Reader SHOULD show the user which of three states a document is in:
| State | Meaning |
|---|---|
memory |
the document changed its own model but has not written to storage |
session |
the overlay changed; the file has not |
file |
the latest overlay has been saved into the file |
10.3 Save
Saving produces a new revision:
- If the Reader saves into the file it opened, it MUST first verify that the
file has not changed since it was opened (by content digest). If it has, it
MUST NOT overwrite it (
IDOP-SAVE-CONFLICT) and SHOULD offer “Save as”. - The new package MUST be identical to the old one except for
storage/**andidop.json. - In
idop.json,parentRevisionIdbecomes the previousrevisionId, a new UUID becomesrevisionId, andmodifiedAtbecomes the current UTC time.document.idis unchanged. - The new package MUST be fully validated (§9.1) before it replaces the old file, and the replacement SHOULD be atomic.
“Save as” follows the same steps without the check in 1. There is no autosave into the package.
11. Runtime API
11.1 Client
Before document code runs, the Reader MUST install a frozen object at
globalThis.idop. Document code MUST use it and MUST NOT infer any endpoint
from location. The transport behind it is the Reader’s choice (in a browser,
a MessagePort per session is RECOMMENDED) and is not part of this contract.
Every method returns a Promise. A failure rejects with an Error whose code
property is a stable error code (§16). Documents MUST branch on code and MUST
NOT parse message, which is a diagnostic and may be localised or changed.
11.2 Methods
| Method | Parameters | Result |
|---|---|---|
idop.runtime.getInfo() |
— | { runtimeApiVersion: "1.0", availableCapabilities: string[] } |
idop.storage.read(path) |
storage-relative path | { text } — UTF-8 content (IDOP-STORAGE-NOT-FOUND, IDOP-STORAGE-UTF8) |
idop.storage.write(path, text) |
path, UTF-8 string | { written: true, ... } (IDOP-STORAGE-QUOTA) |
idop.storage.delete(path) |
path | { deleted: true, ... } |
idop.storage.list() |
— | { paths: string[] } |
idop.storage.transaction(operations) |
[{ op: "write", path, data } | { op: "delete", path }] |
{ committed: true, ... } — all or nothing |
idop.env.get(id) |
environment binding id | { value } (IDOP-ENV-DENIED, IDOP-ENV-MISSING) |
idop.network.request(request) |
§11.3 | { status, headers, body } |
idop.ui.requestSave() |
— | asks the Reader to show its own Save control; the decision stays with the user |
idop.ui.requestConfiguration() |
— | asks the Reader to show its configuration interface |
idop.host.getInputFile() |
— | { name, extension, size, bytes } when the document was opened to display a file (§11.4), else IDOP-HOST-NO-INPUT |
idop.host.putOutputFile(bytes) |
Uint8Array |
hands an edited copy of that file back to the Reader; writes nothing |
There is deliberately no method that returns a secret, enumerates environment variables, or exports content to an arbitrary destination.
11.3 network.request
{ capability: string, url: string, method?: string,
headers?: { [name]: string }, body?: string, timeoutMs?: number }
The Reader performs the request on the document’s behalf, subject to §12. The
response is { status, headers, body }, where headers contains only a
Reader-defined allowlist of response headers and body is text. Errors:
IDOP-NETWORK-DENIED, IDOP-NETWORK-ORIGIN-DENIED,
IDOP-NETWORK-METHOD-DENIED, IDOP-NETWORK-TIMEOUT,
IDOP-NETWORK-REQUEST-TOO-LARGE, IDOP-NETWORK-RESPONSE-TOO-LARGE,
IDOP-NETWORK-CONCURRENCY, IDOP-NETWORK-BUDGET-EXHAUSTED,
IDOP-CREDENTIAL-MISSING, IDOP-CREDENTIAL-ORIGIN-DENIED. A non-2xx response
from the remote service is not an error: it is returned with its status.
11.4 Viewers (informative)
A Reader may let the user open an ordinary file (for example a .docx) with an
IDOP document chosen as its viewer. The viewer receives exactly that one file
through host.getInputFile() and nothing else from the user’s storage.
12. Capabilities, credentials and permissions
12.1 Network capabilities
A capability object has these members:
| Member | Value |
|---|---|
id |
lowercase kebab-case, unique in the package |
type |
"network" (the only type in 1.0) |
origins |
1–16 exact HTTPS origins, without path, query or fragment |
methods |
non-empty subset of GET, POST, PUT, PATCH, DELETE |
credentialBinding |
optional id of a credential binding |
purpose |
1–240 characters shown to the user |
requiredCapabilities are needed for the document to work at all;
optionalCapabilities may be declined while the document still runs.
12.2 Credential bindings
A credential binding is { id, required, description? }. It names no service.
A binding is satisfied by a credential the Reader holds whose allowed origins —
set by the user, never by the document — cover every origin of every capability
that references the binding.
- Every binding MUST be referenced by at least one capability
(
IDOP-MANIFEST-026). - A binding MUST NOT contain a
providermember (IDOP-MANIFEST-025). - A Reader MUST NOT reveal a credential value to document code, write it into
storage/**, a log, an export or a response. - A Reader MUST hold, for each credential, its own list of allowed origins, and
MUST NOT send the credential to any other origin. The manifest can only narrow
where a credential is used; it can never widen it
(
IDOP-CREDENTIAL-ORIGIN-DENIED). - A Reader MUST NOT fall back to an unauthenticated request when a credential is refused.
A Reader MAY offer well-known services as presets in its own settings. Such a preset is matched to a binding by origin, exactly like any other credential.
12.3 Environment bindings
An environment binding is { id, variable, required, description? }, where
variable matches ^[A-Z][A-Z0-9_]*$. Its value is non-secret configuration
held by the Reader and returned by env.get(id) only for declared bindings.
Values are, by definition, readable by document code; a Reader MUST NOT offer an
environment binding as a place to keep secrets.
12.4 Preflight and consent
Between validation and execution the Reader MUST:
- resolve required credential and environment bindings (asking the user to provide them, or refusing the document if they are not provided);
- review every capability against what the Reader will actually permit, and resolve conflicts itself — a capability whose credential may not reach its origins MUST be shown as refused, never offered; a required capability in that state MUST prevent the document from running;
- only then ask the user. The prompt MUST state, for each capability, which credential would travel to which origins and for what declared purpose. The user MAY cancel, allow once, or allow for this version.
“Allow for this version” MUST be keyed by a digest over the application id,
application version, a digest of all code/** entries, and the canonical
capability declarations. A change to code or to declared capabilities
invalidates it; a change to storage/** alone does not.
12.5 Broker rules
For each network.request the Reader MUST:
- refuse a capability that was not granted (
IDOP-NETWORK-DENIED); - refuse a URL whose origin is not exactly one of the capability’s origins
(
IDOP-NETWORK-ORIGIN-DENIED), and a method it does not list (IDOP-NETWORK-METHOD-DENIED); - remove
Authorization,Cookie,Proxy-Authorizationand similar headers the document supplies, and inject a credential only after all checks pass; - not follow redirects;
- bound request size, response size, time and concurrency;
- enforce a finite request budget per session
(
IDOP-NETWORK-BUDGET-EXHAUSTED), so an approved document cannot spend a user’s paid API credit in a loop. A request refused before it reaches the network does not consume the budget.
13. Portable Items (extension)
A document whose state holds user-visible objects (a note, a card, a quiz) MAY
declare under extensions["idop.portable-items"] how they are indexed, so that a
Reader can export one item as a new, valid package with a new document.id. The
declaration, the index format and their errors (IDOP-ITEMS-*) are defined in
IDOP Workspace Containers 1.0, §5, and its schema
https://idoplabs.com/spec/idop/1.0/schemas/portable-items.json. The Reader, not
the document, derives the exported package; there is no export API.
14. Bundles and containers
Directories that group documents — a workspace (idop.workspace.json), a
project (idop.project.json) — and the .idopbundle transport that carries
a workspace, folder or project as one file are defined in IDOP Workspace
Containers 1.0. A bundle is a ZIP archive under the same profile as §4.1, whose
first entry is a Stored mimetype containing application/vnd.idop.bundle+zip,
followed by idop.bundle.json and content/**. A Reader that implements
packages need not implement containers.
15. Versioning and compatibility
formatVersionisMAJOR.MINOR. Minor versions of 1.x only add: optional members, optional features, new capability types gated by a feature name, new error codes. A 1.0 Reader that meets a1.xpackage with x > 0 MAY open it when everything it requires is understood, and MUST refuse it otherwise.- A new major version changes the media type or the meaning of an existing
member, and a Reader MUST refuse a major version it does not implement
(
IDOP-UNSUPPORTED-001). - Error codes, once published, keep their meaning forever.
- Pre-release drafts (
0.1-draft,0.2-draft, media typeapplication/vnd.idop.foundation+zip) are not part of this specification. A Reader MAY continue to open them; a Producer MUST NOT write them.
16. Error codes
Every refusal and every Runtime API failure carries a stable code. Codes are grouped by prefix; the complete registry, with one line per code, is published with the conformance suite (Annex B).
| Prefix | Raised by |
|---|---|
IDOP-ZIP-* |
ZIP structure (§4.1) |
IDOP-PROFILE-* |
container identification and agreement (§4.2, §7.5) |
IDOP-PATH-* |
entry names and storage paths (§4.3, §5) |
IDOP-LIMIT-* |
limits (§6) |
IDOP-MANIFEST-* |
manifest (§7, §12) |
IDOP-UNSUPPORTED-* |
versions and features (§7.2, §15) |
IDOP-POLICY-* |
executable boundary (§8) |
IDOP-STORAGE-*, IDOP-SAVE-* |
storage and Save (§10) |
IDOP-NETWORK-*, IDOP-CREDENTIAL-*, IDOP-ENV-* |
broker and bindings (§11–12) |
IDOP-RUNTIME-*, IDOP-HOST-*, IDOP-SESSION-*, IDOP-BRIDGE-* |
Runtime API transport |
IDOP-ITEMS-*, IDOP-WS-*, IDOP-PROJECT-*, IDOP-BUNDLE-* |
containers and Portable Items |
A Reader SHOULD explain each code to the user in the user’s language. The code itself MUST NOT be localised.
17. Security considerations
17.1 Active content
An IDOP package contains executable code. This is its purpose, and the whole of this specification is organised around it:
- Code runs only after the complete package has been validated (§9.1), so a malformed or truncated file never runs at all.
- Code runs in an isolated context with no network access, no access to the Reader, and no access to other documents (§9.3, §9.4).
- Network access exists only as a capability the user grants, to exact origins, through a broker that injects credentials the document cannot read (§12).
- A document cannot obtain a secret: there is no API that returns one (§11.2).
- The static checks of §8 are an additional layer; the sandbox is the boundary.
Residual risks that this specification does not remove: defects in the browser or engine that hosts the sandbox; side channels; denial of service through CPU or memory use (there is no portable way to bound a sandboxed context’s CPU); and social engineering — a document can display anything, including a convincing imitation of a login form. Readers SHOULD visibly distinguish document content from their own interface and SHOULD warn before running a document received from someone else that requests network access.
17.2 Container attacks
The ZIP profile (§4.1), path rules (§5) and limits (§6) exist to defeat known attacks on archive formats: path traversal (“zip slip”), entry-name collisions across file systems, decompression bombs, polyglot and self-extracting files, divergent local and central headers, and ZIP64 parser differentials. A Reader MUST apply them before any extraction or execution.
17.3 Integrity and authenticity
1.0 provides no authenticity. application metadata is self-declared, and
the digest of code/** is a fingerprint used for consent (§12.4), not a
signature. Anyone can produce a package claiming any title or author. A Reader
MUST NOT present a package as coming from a particular publisher. Signatures are
reserved (_idop/signatures/) for a later version.
17.4 Confidentiality
A package is not encrypted. Anything in storage/** is readable by anyone who
has the file. Encryption is reserved (_idop/encryption/) for a later version.
17.5 Export
A Reader cannot prevent the user from keeping a copy of a document they can open. “Read-only” in a Reader’s interface is not access control and MUST NOT be described as such.
18. Privacy considerations
- A package carries its state. Users SHOULD be told that sending a document sends its saved content.
- Credentials and environment values are Reader state and never travel with the package (§12.2, §12.3); a recipient must configure their own.
- A document cannot contact any server unless the user grants a capability; the prompt names each origin, so the user knows where data could go.
- Timestamps in
documentreveal when a document was created and last saved. Producers MAY round them.
19. Accessibility considerations
Documents are web content and SHOULD meet WCAG 2.2 level AA. A Reader SHOULD make its own interface — prompts, errors, the document frame’s title — accessible, and SHOULD give the document frame an accessible name derived from the document title while labelling it as document content.
20. IANA considerations
This specification requests registration of two media types in the vendor tree
[RFC 6838]. The registration templates are reproduced in
docs/registrations/ of the reference repository.
application/vnd.idop+zip
- Required parameters: none. Optional parameters: none.
- Encoding considerations: binary.
- Security considerations: §17 of this specification.
- Interoperability considerations: §4, §15.
- Published specification: this document.
- Applications that use this media type: IDOP Cloud (
https://cloud.idoplabs.com), theidopcommand-line tool, and other IDOP Readers. - Fragment identifier considerations: none defined.
- Magic number:
50 4B 03 04at offset 0, the stringmimetypeat offset 30, and the stringapplication/vnd.idop+zipat offset 38. - File extension:
.idop. Macintosh file type code: none. - Uniform Type Identifier (informative):
com.idoplabs.idop, conforming topublic.zip-archive.
application/vnd.idop.bundle+zip — as above, with the bundle string at
offset 38 and the extension .idopbundle.
21. References
Normative
- [APPNOTE] PKWARE, .ZIP File Format Specification, version 6.3.10.
- [RFC 2119] Bradner, Key words for use in RFCs to Indicate Requirement Levels.
- [RFC 3339] Klyne, Newman, Date and Time on the Internet: Timestamps.
- [RFC 4122/9562] Universally Unique IDentifiers (UUIDs).
- [RFC 6454] Barth, The Web Origin Concept.
- [RFC 6838] Freed, Klensin, Hansen, Media Type Specifications and Registration Procedures.
- [RFC 6839] Hansen, Melnikov, Additional Media Type Structured Syntax Suffixes (
+zip). - [RFC 8174] Leiba, Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words.
- [RFC 8259] Bray, The JavaScript Object Notation (JSON) Data Interchange Format.
- [SemVer] Semantic Versioning 2.0.0.
- [UAX15] Unicode Standard Annex #15, Unicode Normalization Forms.
- [JSON Schema] JSON Schema 2020-12.
Informative
- OASIS, Open Document Format for Office Applications, Part 3: Packages.
- W3C, EPUB 3.3, Open Container Format.
- W3C, Content Security Policy Level 3; WHATWG, HTML (
iframesandboxing). - W3C, Web Content Accessibility Guidelines 2.2.
Annex A — JSON Schemas (normative)
| Schema | $id |
|---|---|
| Manifest | https://idoplabs.com/spec/idop/1.0/schemas/manifest.json |
| Workspace marker | https://idoplabs.com/spec/idop/1.0/schemas/workspace.json |
| Project marker | https://idoplabs.com/spec/idop/1.0/schemas/project.json |
| Bundle manifest | https://idoplabs.com/spec/idop/1.0/schemas/bundle.json |
| Portable Items | https://idoplabs.com/spec/idop/1.0/schemas/portable-items.json |
The schemas also describe the pre-release drafts so that one validator can
read both; for a 1.0 manifest only the formatVersion: "1.0" branch applies.
Annex B — Conformance suite (normative for claims of conformance)
A Reader or Producer may claim conformance to IDOP 1.0 when it passes the IDOP
1.0 conformance suite: a set of valid packages that MUST open, invalid packages
that MUST be refused with the listed error code, and round-trip cases for Save.
The reference implementation (the idop CLI and the IDOP Cloud Reader, which
share one validator) passes it.
Annex C — Minimal example (informative)
mimetype:
application/vnd.idop+zip
idop.json:
{
"format": "https://idoplabs.com/ns/idop/package",
"formatVersion": "1.0",
"runtimeApiVersion": "1.0",
"entryPoint": "code/index.html",
"application": { "id": "com.example.counter", "version": "1.0.0", "title": "Counter" },
"document": {
"id": "4df56319-e383-4ae8-a519-faa7f4866f90",
"revisionId": "2e43a209-0a66-4f54-b545-dae25d3b89d0",
"parentRevisionId": null,
"createdAt": "2026-10-01T00:00:00Z",
"modifiedAt": "2026-10-01T00:00:00Z"
},
"state": { "schemaVersion": "1.0.0" },
"requiredFeatures": ["idop.core-storage-v1"],
"optionalFeatures": [],
"requiredCapabilities": [],
"optionalCapabilities": [],
"environmentBindings": [],
"credentialBindings": [],
"extensions": {}
}
code/index.html:
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Counter</title><script type="module" src="app.js"></script></head>
<body><button id="add">0</button></body>
</html>
code/app.js:
const button = document.querySelector('#add');
let count = 0;
try {
count = JSON.parse((await idop.storage.read('count.json')).text).count;
} catch (error) {
if (error.code !== 'IDOP-STORAGE-NOT-FOUND') throw error;
}
button.textContent = String(count);
button.addEventListener('click', async () => {
count += 1;
button.textContent = String(count);
await idop.storage.write('count.json', JSON.stringify({ count }));
});
A document that needs an external API adds, for example:
"requiredCapabilities": [{
"id": "ai", "type": "network", "origins": ["https://api.example.com"],
"methods": ["POST"], "credentialBinding": "api-key", "purpose": "Generate answers"
}],
"credentialBindings": [{ "id": "api-key", "required": true, "description": "Key for api.example.com" }]
Annex D — Changes from the pre-release drafts (informative)
| Draft | 1.0 |
|---|---|
media type application/vnd.idop.foundation+zip |
application/vnd.idop+zip |
format: "urn:idop:package" |
https://idoplabs.com/ns/idop/package |
two profiles, 0.1-draft and 0.2-draft |
one profile, 1.0; a document without capabilities is the former 0.1 |
credential binding names a provider (openrouter, bearer) |
names no service; matched by origin (§12.2) |
schemas under idop.foundation |
schemas under idoplabs.com/spec/idop/1.0/ |
| — | reserved _idop/signatures/, _idop/thumbnail.png, _idop/encryption/ |