Skip to content

Threat model ​

Attackers, assets, and mitigations for paste, SVG, ZIP, S3, and variable paths. Verification: pnpm run test:security.

Assets ​

  • PortableDocument JSON (user content)
  • Assets (images) in S3-compatible storage
  • Revision history in PostgreSQL

Attackers ​

  • Unauthenticated content injection via paste, variables, SVG, DOCX/ZIP
  • Tenant isolation bypass
  • Prototype pollution via variable paths

Mitigations ​

ThreatMitigationLocation
XSS via pasted HTMLAllowlist sanitization, strip javascript: URLs, event handlerssrc/editor-web/clipboard/index.ts:12
XXE via SVG/XMLInternal XML writer with escaping, no entity expansion, forbid <!ENTITYsrc/export/xml/writer.ts:1, src/assets/svg/index.ts:1
Zip bombsSize limits 1 MB paste, dimensions anti-bomb, stream limits, no auto-extractsrc/assets/sniff/index.ts:1, src/editor-web/clipboard/index.ts:15
SSRF via asset URLsOnly allow S3 presigned URLs with limited lifetime, validate storageKey prefix tenants/{tenant}src/adapters/s3-compatible/index.ts:1
Prototype pollutionBlock __proto__, prototype, constructor in safeResolve, own-property checkssrc/template/resolver/resolver.ts:12
Path traversal in ZIPSanitize entry names, reject ../, control charssrc/editor-web/clipboard/index.ts:85
Tenant isolationCheck tenant_id on every PG query, idempotency keys scopedmigrations/001_init.sql:1, src/adapters/postgres-contract/index.ts:1
ReDoS in LaTeXLength limit 2000, no backtracking regexsrc/core/equation/index.ts:12

Verification ​

  • pnpm run test:security — corpus in tests/security/
  • Fuzz harness scripts/fuzz.js with reproducible seeds
  • pnpm run lint ensures zero window/document in src/core TypeScript

Zero runtime dependencies — TypeScript strict — MIT