Security and privacy for your Instagram export
Processing a ZIP of your entire Instagram history comes with concrete threats. This is the engineering posture: harden parsing against ZIP bombs/path traversal, XSS/prototype pollution, worker DoS, and the split-key crypto envelope for the opt-in sync tier.
P3 is "who should hold this file and how to verify that claim". This is the concrete threat model and the engineering that answers it: ZIP bombs/path traversal, XSS/prototype pollution, worker DoS, and the sync tier's crypto envelope.
Threat model
Processing untrusted archives requires defense-in-depth: archive extraction, JSON/HTML parsing, string handling, and any optional network paths (which are opt-in only). The design treats every byte in the ZIP as untrusted.
Archive extraction hardening
- ZIP bombs / resource exhaustion. Archive entries are streamed with size limits and entry count caps; decompression ratio checks prevent zip-bombs. Paths are normalized.
- Path traversal. Entries are resolved against an extraction root; symlinks/traversal (
.., absolute paths) are rejected to prevent writing outside the sandbox.
Parsing hardening
- Prototype pollution. When merging untrusted objects (e.g.
string_map_data), keys like__proto__,constructor,prototypeare filtered. Assignments useObject.definePropertyand only known-safe fields are written where applicable (see personal parser). - XSS / HTML injection. HTML parsing is used in controlled contexts; any rendered content is escaped/normalized. Text is repaired for mojibake but not executed as code.
- Malformed inputs. JSON parse errors are caught per file; HTML parsing failures are swallowed safely; unknown shapes are ignored rather than crashing the worker.
- Resource limits. Large archives are processed incrementally; the parser avoids deep unbounded recursion where practical and caps work per file.
Runtime isolation
- Web Worker. Parsing runs in a Web Worker to keep the UI responsive and to isolate CPU-heavy work. Worker messages are bounded.
- DoS in worker/UI. Bounded queues, entry caps and time/size budgets prevent a single pathological file from blocking the app indefinitely.
- CSP posture. CSP reports are console-only, bounded, deduped and discarded.
Crypto envelope (opt-in sync tier)
For the optional, opt-in profile sync: data is wrapped in a crypto envelope with split-key custody (2-of-4: {A server, B device, C recovery codes, D email escrow}) so no single party has the full key material to decrypt alone. Media is never stored. Messages synced only under explicit opt-in. Retention is instant erase with 30-day grace. This is off by default and requires explicit configuration.
Principle of least trust
Local-first by default. Any networked features are strictly opt-in, gated by configuration, and designed so the archive contents are never uploaded unless the user has explicitly opted into a feature that requires it - and even then, only under the constraints above.
How does the app defend against ZIP bombs?
Extraction uses size/entry caps and decompression ratio checks; traversal is blocked. Processing is bounded and incremental.
What about prototype pollution in archive JSON?
Untrusted keys are filtered (__proto__, constructor, prototype) and controlled assignment paths are used (e.g. Object.defineProperty) in merge points.
Is the sync tier on by default?
No. It is opt-in only, uses split-key custody, never stores media, and requires explicit configuration. Local mode remains the default.
Why run parsing in a Web Worker?
For isolation, responsiveness, and to enforce bounded work against untrusted input.
Sources of truth
SECURITY.md- security posture and controls.AGENTS.md- local-first default, claim layer opt-in, CSP report behavior (console-only, bounded, deduped, discarded), no AI/tracker defaults.- Sync tier design (profile sync): split-key 2-of-4, no media stored, messages only under explicit opt-in, instant erase + 30-day grace (documented in profile sync plan; this post states the engineering posture).