Skip to content

Security Model

Daniël Smith edited this page Oct 7, 2026 · 1 revision

Security Model

What JAZMIN protects, how, and what it deliberately doesn't. For day-to-day key handling see Encryption and Keys; for per-person access, Access Control.

Building blocks

Only standard algorithms, available natively in browsers (WebCrypto), Node.js and .NET. No custom cryptography.

Purpose Algorithm
Authenticated encryption of every section AES-256-GCM, 96-bit random nonce, 128-bit tag
Keys per section, from a key HKDF-SHA256
Key from a password PBKDF2-HMAC-SHA256, 600,000 iterations by default (readers refuse below 1,000 and above 10,000,000)
Owner signatures on shared files ECDSA P-256 with SHA-256
Integrity of plain (unencrypted) files CRC-32: accidental damage only

Keys

flowchart TD
  K["Key (jzk1-...) or password"] -->|HKDF / PBKDF2| M[Master key]
  M -->|HKDF + file salt| HK[Header key]
  HK --> H["Header: tables, columns, metadata,<br/>directories, keyring (3 random secrets)"]
  M -->|"HKDF + keyring secret + section id"| SK["A unique key for every<br/>chunk, index and file section"]
Loading
  • Every section has its own key, derived from the master key and a random secret stored in the encrypted header.
  • Every ciphertext is bound to its file id, its codec and length, and its section's identity. Moving, swapping or copying sections between files, changing the codec byte, or stripping encryption all fail authentication.
  • A new version of a file (update, compact, rotation) gets a new file id, salt and secrets.
  • Keys carry a checksum: a mistyped jzk1- or jza1- key is reported as a typo, not as wrong data.

Shared files

An access-controlled file has an owner key and access keys:

  • Each file version has fresh random secrets; every partition and every column group gets a secret derived from them. A chunk of partition p and column group g needs both secrets to open.
  • Each access key has a key slot holding only the secrets it was granted. From those, the owner secret and all other secrets can't be computed. Partition ids are keyed by the owner secret, so they reveal nothing about partition names.
  • The owner signs the key-slot list and header with an ECDSA key derived from the owner key. Each access key carries the owner's fingerprint, so a key holder can't produce a file that verifies as the owner's, and readers reject changed files.
  • Indexes are owner-only, because an index would reveal every partition's values.
  • Online grants seal the key slot with the access key and a random share that only the owner's side holds. The share is the unlock token (jzu1-...), which the key service issues only before the expiry, so the file can't be opened without asking it. The key service needs only the shares, not the owner key.

What is protected

In an encrypted file Protected?
Row values, column names and types, metadata, indexes, embedded files and their names ✔ encrypted and authenticated
Any change to the bytes ✔ detected
A wrong key ✔ refused (Decryption failed), never misread
A plain file passed off as encrypted (downgrade) ✔ refused when a key is given (A key was supplied but the file is not encrypted)
The total size, number and sizes of sections ✘ visible
Whether a password was used, and the PBKDF2 iteration count ✘ visible
Rough row counts ✘ can be inferred from sizes

In shared files, every key holder can also see: the default column group's names and types; the names and column counts of restricted groups; file metadata; the number of partitions (as opaque ids) and of key slots; the package settings (entry page and title); the ids of deleted rows. Don't put secrets in metadata or default-group column names.

Limits by design

  • Revocation applies to new versions. Someone who kept a copy of an old version, and their old key, can still read what that key was granted in that copy.
  • Offline expiry relies on the reader. It catches clocks set back, but someone who deletes their last-seen record and winds the clock back, or runs modified software, can still open a copy they hold. Use online keys when it matters. See Time-Limited Access.
  • The owner key is everything. It reads all, changes the file, grants and revokes, and can't be revoked itself. Keep it on servers you control, in a secret manager. Browsers refuse it. If it leaks, rotateOwnerKey issues a new owner key and new access keys.
  • A signature proves who produced a file, not who saw it. Anyone can copy data they're allowed to see into a file of their own; it just won't verify as the owner's.
  • Appended files can be rolled back to an earlier version by cutting them at an earlier trailer: that version was signed by the owner too. If you need to know a file is the latest, record its appendCount or length elsewhere.
  • Compression can leak through sizes when attacker-chosen text shares a section with secrets and the attacker sees the file size. Use codec: 'none' for such data.
  • Plain files have CRC checks only: they catch accidents, not tampering.

Hostile files

Readers treat every file as untrusted: declared lengths and counts are never trusted, memory is bounded by each section's recorded size, PBKDF2 iteration counts outside 1,000–10,000,000 are refused before any work, and unknown flags or features are refused. Both libraries have fuzz tests that feed the readers damaged and hostile input, and a long fuzz run (hours, on both libraries) is part of the checks before a release.

Embedded documents are untrusted content. The viewer runs a package's pages in a sandbox with no network (beyond the package's allowed https:// origins), no storage and no access to the key; servers serve pages and SVG with a strict content security policy and a sandbox.

Package scanners

Scanners such as Socket list what the npm package does. These are expected:

  • Code generation (new Function): the Node reader builds each row with one generated object literal, 2–3× faster than without. Only column names (as JSON strings) and integers go into the generated code; with code generation disabled, the reader falls back to building rows without it. The browser module generates no code.
  • Environment variables: only LOCALAPPDATA on Windows (where expiring keys keep their last-seen record); the CLI also reads JAZMIN_KEY and JAZMIN_PASSWORD.
  • File system: .jzm files, lock files and last-seen records.
  • No network requests, except the browser module's openUrl, to the address it's given.

Reporting a vulnerability

Please report security problems privately, as described in the repository's SECURITY.md, not in a public issue. Include what is affected (library and version, viewer, or format), how to reproduce it, and what an attacker could achieve.

Clone this wiki locally