Available

Candidate Recommendation. The normative text for IDOP 1.0 readers and producers.

Markdown

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

  1. Conformance
  2. Terminology
  3. Overview
  4. Package container
  5. Paths
  6. Limits
  7. Manifest
  8. Executable boundary
  9. Reader processing model
  10. Storage, Save and revisions
  11. Runtime API
  12. Capabilities, credentials and permissions
  13. Portable Items (extension)
  14. Bundles and containers
  15. Versioning and compatibility
  16. Error codes
  17. Security considerations
  18. Privacy considerations
  19. Accessibility considerations
  20. IANA considerations
  21. 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 pack command). 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.json at 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:

  1. No bytes MAY precede the first local file header or follow the end of central directory record. (Self-extracting and polyglot files are refused.)
  2. ZIP64, multi-volume and spanned archives MUST NOT be used.
  3. Encryption MUST NOT be used.
  4. Every entry MUST use compression method 0 (Store) or 8 (Deflate).
  5. 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.
  6. The local header and central directory record of each entry MUST agree on name, flags, method, sizes and CRC-32.
  7. No two central directory records MAY reference the same local header.
  8. Directory entries, symbolic links, devices and other special entries MUST NOT appear. Directories exist only as prefixes of file names.
  9. 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:

  1. relative, /-separated, encoded as UTF-8 and normalised to Unicode NFC (IDOP-PATH-004);
  2. at most 240 bytes and at most 16 segments (IDOP-PATH-001, IDOP-PATH-005);
  3. no leading /, no \, no UNC or drive prefix such as C: (IDOP-PATH-002, IDOP-PATH-009);
  4. no empty, . or .. segment (IDOP-PATH-006);
  5. no NUL or control character, no : and no segment ending in . or a space (IDOP-PATH-003, IDOP-PATH-007);
  6. no segment that is a Windows device name (CON, PRN, AUX, NUL, COM1–COM9, LPT1–LPT9, with or without an extension) (IDOP-PATH-008);
  7. 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

  1. Only entries under code/ are executable. An entry under resources/ or storage/ with the extension .html, .htm, .js, .mjs, .css or .wasm MUST cause refusal (IDOP-POLICY-001).
  2. Entries under code/ MUST be UTF-8 text with the extension .html, .js, .mjs, .css or .json (IDOP-POLICY-002, IDOP-POLICY-003).
  3. HTML under code/ MUST NOT contain inline script (every <script> has a src and an empty body), and MUST NOT contain <iframe>, <object>, <embed>, <form>, <base> or remote URLs (IDOP-POLICY-011, IDOP-POLICY-013, IDOP-POLICY-014).
  4. CSS under code/ MUST NOT contain remote URLs or @import (IDOP-POLICY-012).
  5. Document code MUST NOT use dynamic evaluation (eval, new Function), javascript: URLs, WebAssembly, workers, service workers or window.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:

  1. Check the file size against §6.
  2. Parse the ZIP structure independently of any ZIP library — end of central directory, central directory, local headers — and enforce §4.1.
  3. Check the first entry and its content against §4.2 (IDOP-PROFILE-002).
  4. Validate every entry name against §4.3 and §5.
  5. Decompress every entry into a bounded buffer, enforcing per-entry, aggregate and ratio limits and verifying every CRC-32 and size.
  6. Parse and validate idop.json against §7, including §7.5.
  7. Enforce the executable boundary, §8.
  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:

  1. 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”.
  2. The new package MUST be identical to the old one except for storage/** and idop.json.
  3. In idop.json, parentRevisionId becomes the previous revisionId, a new UUID becomes revisionId, and modifiedAt becomes the current UTC time. document.id is unchanged.
  4. 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 provider member (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.

Between validation and execution the Reader MUST:

  1. resolve required credential and environment bindings (asking the user to provide them, or refusing the document if they are not provided);
  2. 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;
  3. 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-Authorization and 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

  • formatVersion is MAJOR.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 a 1.x package 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 type application/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 document reveal 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), the idop command-line tool, and other IDOP Readers.
  • Fragment identifier considerations: none defined.
  • Magic number: 50 4B 03 04 at offset 0, the string mimetype at offset 30, and the string application/vnd.idop+zip at offset 38.
  • File extension: .idop. Macintosh file type code: none.
  • Uniform Type Identifier (informative): com.idoplabs.idop, conforming to public.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 (iframe sandboxing).
  • 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/