Skip to content

Reader requirements

These four rules are what make the format survivable. They are normative.

  1. Readers MUST ignore unknown JSON keys and unknown container entries. New keys and new entries appear in minor revisions. A reader that fails on one breaks on files it was supposed to handle.
  2. formatVersion increases only on breaking changes. A reader encountering a version greater than it supports MUST refuse with a clear “created with a newer version” error. It MUST NOT guess-read: a partial parse of a format you do not know produces a design that is wrong in ways nobody can see.
  3. Entries marked "derived": true are regenerable. Readers and re-writers MAY drop them.
  4. document.json is the single source of truth. In particular, plan.json MUST NOT be imported back into editable state (why).

.hoop files are untrusted input — including files your own application wrote, because you do not know what happened to them between then and now. A conforming reader enforces all of the following.

LimitReference valueWhy
Entry count≤ 256A project has a handful of entries; thousands means an attack or a bug.
Per-entry uncompressed size≤ 64 MB
Total uncompressed size≤ 256 MB
Compression ratio per entry≤ 100:1Zip bombs. A 1 MB entry inflating to 1 GB is not a design.

These are the values the reference implementation uses. A reader MUST enforce caps of this kind; the exact numbers are an implementation choice, and a reader that sets them higher should be able to say why.

Reject any archive containing an entry name that contains a .. component, begins with /, or contains a NUL byte. Never construct a filesystem path from an archive entry name without this check — this is the classic Zip Slip, and it is how an embroidery file overwrites something outside its folder.

  • CRC-32 per entry, as the archive is read. This is ZIP’s own check.
  • SHA-256 per manifest record, afterwards. Recovery behaviour on failure is defined in manifest.json: a bad document.json is fatal, a bad derived or reference entry is dropped.

Parsing succeeding does not make the content safe.

CheckReference value
Objects per document≤ 10 000
Points per path≤ 50 000
Palette entries≤ 512
Reference images≤ 64
CoordinatesReject non-finite values (NaN, ±∞)
Hoop sizeReject absurd dimensions

Non-finite coordinates deserve particular attention: JSON permits a parser to produce them, geometry code rarely checks for them, and a single NaN propagates through a bounding box into a layout that renders nothing and reports no error.

Numeric parameters SHOULD additionally be clamped into their sensible ranges while decoding rather than validated afterwards — a density of 0.0001 mm is not a file to reject, it is a file to bring back into range.

Decode reference images through a hardened decoder with a pixel-count limit. An image decoder is the largest attack surface in the whole format; it is also the one part of a .hoop reader that is usually somebody else’s code.

Writers have far fewer obligations than readers, and the ones they have are in Container. The asymmetry is deliberate: a strict writer and a lenient reader is how formats rot. This one asks for a strict reader and a conservative writer.

ZipArchive.swift and HoopFile.swift in the StitchPencil source show one conforming approach, including every limit above and the tests that hold them in place. They are Swift, but nothing in the format depends on the language — the container is ZIP and the payload is JSON.

Swift and C# packages are planned. Until they exist, the pages here plus the sample files are the intended starting point, and the description above is complete enough to implement from without reading any Swift.