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.jsonandsignature.jsonthemselves), 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 bypatab-widget keygenand 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:
| Limit | Value |
|---|---|
| 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 |
| Icon | PNG/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
- The user selects a
.zip/.patab.zipfile - A streaming extraction Worker validates the ZIP structure and quotas (no DOM/iframe is created at any point)
- Static review: Manifest Schema, file set, image decoding, integrity, signature
- 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)
- 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
migrationsurface 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