Skip to main content

Package Format and Distribution

Physical Format

A widget package is a standard ZIP archive with the conventional extension .patab.zip (PaTab accepts both .zip and .patab.zip on import). There is no custom file header or magic number — any ZIP tool can open it.

Packing is deterministic: Deflate compression level 9, all entry mtimes fixed to 1980-01-01 00:00:00, entries sorted by path — identical inputs produce byte-identical ZIPs across two pack runs, enabling reproducible builds for verification and auditing.

Package Layout (Allowlist)

Only files declared in the Manifest may appear in the package; one file more or one file less and the installer rejects it:

<id>-<version>.patab.zip
├── patab.manifest.json # 组件清单(规范 JSON 序列化,≤ 64 KiB)
├── surfaces/
│ ├── widget.html # 每个 surface 一个完全自包含 HTML
│ ├── detail.html
│ └── settings.html
├── assets/ # Manifest 声明的素材(icon、screenshots、variant 图标)
│ └── ...
├── integrity.json # SHA-256 完整性清单(必填)
└── signature.json # Ed25519 签名(可选)

Surface HTML

Each surface is built individually by the CLI using the project's own Vite config and fully inlined: <script src> inlined into <script>, styles inlined into <style>, images/fonts converted to data URLs. Rejected items:

  • Dynamic import(), import.meta.hot, @vite/client (HMR)
  • Any external resources (http(s)://, protocol-relative //, external scripts/styles/images)
  • srcset, source map references
  • External scripts/styles/resources remaining after the build

The host checks again after installation: surfaces carrying their own CSP meta, meta refresh, <base>, <iframe>, <object>, <embed>, etc. are all rejected before the production CSP is injected. See Security Model.

integrity.json

{
"schemaVersion": 1,
"algorithm": "sha256",
"files": [{ "path": "surfaces/widget.html", "sha256": "<64 位小写 hex>" }]
}
  • Covers every file in the package (except integrity.json and signature.json themselves), sorted strictly ascending by path
  • The installer recomputes each hash: missing, extra, or mismatched hashes are all rejected
  • Serialized as canonical JSON (JCS, sorted keys, no whitespace) to guarantee byte-identical output in browsers and Node

signature.json (Optional)

{ "schemaVersion": 1, "algorithm": "ed25519", "signature": "<标准 Base64>" }
  • The signing input is the canonical JSON bytes of {"integrity": ..., "manifest": ...}
  • The public key comes from the Manifest's publisher.publicKey (Base64 of SPKI DER), generated by patab-widget keygen and filled in manually
  • Four verification states: unsigned (not signed; installable but clearly labeled), valid, invalid (rejected), publisher-key-changed (same ID with a different public key; treated as a different publisher and blocked from overwriting)

Size and Structure Limits

The installer enforces these limits on the ZIP and its contents:

LimitValue
Total archive size≤ 10 MiB
Total declared uncompressed size≤ 30 MiB
Declared uncompressed size per file≤ 5 MiB
File count≤ 100
Declared compression ratio per file≤ 100 (ZIP bomb protection)
Manifest raw size≤ 64 KiB
IconPNG/WebP, ≤ 512 KiB
Screenshots≤ 5, PNG/JPEG/WebP, each ≤ 2 MiB and ≤ 2560×1440

Extraction streams block by block, checking actual sizes (the central directory's declared values are not trusted); exceeding a limit aborts immediately, and a failure never returns partial files. At the ZIP level it also rejects: ZIP64, multi-disk archives, encrypted entries, non-UTF-8 file names, special files such as symlinks, case-conflicting paths, and trailing garbage appended to the archive.

Path rules: must be NFC-normalized; \, empty segments, ./.. segments, absolute paths, and drive letters are forbidden. Images are sniffed for their real format via magic bytes and genuinely decoded (extensions are not trusted).

Installation Flow

  1. The user selects a .zip / .patab.zip file
  2. A streaming extraction Worker validates the ZIP structure and quotas (no DOM/iframe is created at any point)
  3. Static review: Manifest Schema, file set, image decoding, integrity, signature
  4. The review page shows name/version/developer/signature status/permissions/network usage; the user confirms the required permissions and network origins one by one (the extension requests the optional host permission for each origin within the checkbox gesture)
  5. Atomic write into a dedicated IndexedDB, then a lightweight instance reference is created in the grid

Updates and Rollback

  • Only packages with the same ID and a higher SemVer are accepted as updates; a changed public key is treated as a different publisher and blocked from overwriting
  • New permissions, new network origins, or a key change all pause the update and require the user to re-confirm
  • Updates use a staged mechanism: the new version's data is first written to a staging area; an optional migration surface migrates instance data in an offscreen sandbox (it can only access staged storage — no UI/todos/network/channel/external links — with a combined 5-second budget including the offscreen health handshake); after all instances pass the health handshake, a single transaction switches over atomically, with automatic rollback on failure; one rollback version is retained

Distribution Recommendations

  • Unsigned packages can be installed, but the review page labels them clearly; for official releases, use keygen + pack --sign
  • The private key is stored outside the project in .patab-widget-keys/ by default, protected by a passphrase (≥ 12 characters); do not commit it to version control
  • Before publishing, independently re-check the finished package with patab-widget inspect