Skip to content

Harden worker: fix XSS, PIN leak, unbounded storage, no rate limiting #2

Closed

Description

@dkritarth

Security/robustness pass on the FreeFlow Cloudflare Worker (src/index.js) and CLI (cli/index.js).

Findings

  1. Stored XSS by design — uploaded HTML is served raw as text/html with no sanitization, no Content-Security-Policy, no X-Frame-Options. Anyone holding SECRET_API_KEY can host arbitrary script on the worker's domain (clickjacking, phishing, credential theft if any auth cookies are ever scoped to this domain).
  2. PIN leaks via URL — the unlock PIN is sent as ?key= query param (resolveAndRenderDraft), which lands in browser history, server/proxy access logs, and Referer headers. Also stored as plaintext in KV alongside the draft.
  3. No TTL / no delete endpoint — drafts live in KV forever; no expiry option on upload, no way to remove a draft once published.
  4. No upload size limit — handleUpload accepts any size html string; KV values cap at 25MB, hit late with a confusing failure. Add an explicit size check (e.g. 1-2MB) with a clear 413 response.
  5. No rate limiting on /upload — single shared bearer key with no per-caller throttling; a leaked key can be used to spam-fill KV.
  6. Guessable draft IDs — createUniqueShortId uses 8 hex chars from crypto.randomUUID(). Small keyspace, enumerable for unlisted (non-PIN) drafts. Widen the ID or require it be treated as unlisted-only (already true), but consider bumping length.
  7. Swallowed errors — the top-level fetch handler catches everything and returns a bare 500 with no logging, making failures hard to diagnose.
  8. CLI has no local file size guard (cli/index.js) — nothing stops a huge local file from being read into memory and POSTed, hitting server-side limits with a poor error message.

Ask

Fix all of the above:

  • Add Content-Security-Policy (script/style restrictive) + X-Frame-Options: DENY (or frame-ancestors 'none') to draft responses.
  • Move PIN verification off the URL: accept it via a POST form / header instead of ?key= query string, or at minimum stop echoing it back into subsequent GET links.
  • Add optional TTL on upload (KV expirationTtl) and a DELETE /d/:id endpoint authenticated with the same bearer key.
  • Enforce a max HTML size (e.g. 2MB) on /upload, return 413 if exceeded.
  • Add basic rate limiting on /upload (e.g. Cloudflare Rate Limiting rule or a simple KV-based counter keyed by caller).
  • Widen draft ID entropy (e.g. 12-16 chars) to reduce guessability.
  • Log errors server-side (or return an error id) instead of a silent generic 500.
  • Add a client-side size check in cli/index.js before upload, matching the server's new limit.

Keep changes scoped to src/index.js and cli/index.js; no new dependencies unless required for rate limiting.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions