Repository navigation
Harden worker: fix XSS, PIN leak, unbounded storage, no rate limiting #2
Copy link
Copy link
Closed
Description
Security/robustness pass on the FreeFlow Cloudflare Worker (src/index.js) and CLI (cli/index.js).
Findings
- Stored XSS by design — uploaded HTML is served raw as
text/htmlwith no sanitization, noContent-Security-Policy, noX-Frame-Options. Anyone holdingSECRET_API_KEYcan host arbitrary script on the worker's domain (clickjacking, phishing, credential theft if any auth cookies are ever scoped to this domain). - PIN leaks via URL — the unlock PIN is sent as
?key=query param (resolveAndRenderDraft), which lands in browser history, server/proxy access logs, andRefererheaders. Also stored as plaintext in KV alongside the draft. - No TTL / no delete endpoint — drafts live in KV forever; no expiry option on upload, no way to remove a draft once published.
- No upload size limit —
handleUploadaccepts any sizehtmlstring; 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. - No rate limiting on
/upload— single shared bearer key with no per-caller throttling; a leaked key can be used to spam-fill KV. - Guessable draft IDs —
createUniqueShortIduses 8 hex chars fromcrypto.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. - Swallowed errors — the top-level
fetchhandler catches everything and returns a bare 500 with no logging, making failures hard to diagnose. - 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(orframe-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 aDELETE /d/:idendpoint 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.jsbefore 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.
Reactions are currently unavailable
Activity