5 MBmax upload size
20uploads per 30 days
12 charsfast prefix lookup
slug collision retries
01
1Authorize
Key Auth,
Two Steps
Every request validates twice — an indexed prefix lookup filters candidates before the expensive bcrypt check runs.
Fast-path prefix lookup
🔑
Extract Bearer token
Authorization header must start with Bearer . Key must be ≥ 12 chars — 401 fires immediately with no DB hit.
Indexed prefix scan
First 12 chars become the prefix — queried against a btree-indexed column with revoked_at IS NULL. Shields bcrypt from brute force.
🔐
bcrypt comparison
bcrypt.compare() runs only for candidates matched by prefix. 12 rounds — slow by design. After match, userId + tier set on context.
result
No prefix match or revoked — 401 returned before bcrypt ever runs.
bcrypt mismatch — key format valid but hash doesn't match → 401.
Match — userId + tier injected into context. last_used_at updated fire-and-forget.
xp_live_…production format
xp_test_…dev/staging format
12 charsstored plain for index
02
2Process
7 Checks,
No Exceptions
HTML passes a two-stage pipeline — DOMPurify for structural threats, then regex for secrets, exfil, and injection. Uploads always succeed.
Stage 1 · DOMPurify
1
Script & embed tags removed script, iframe, object, embed, applet, frame, noscript, form
STRIP
2
Event handlers stripped all on* attributes, srcdoc, sandbox, formaction, ping
STRIP
3
URI allowlist enforced href/src must be https: or data:image/ — javascript: blocked
STRIP
4
Links hardened All <a>/<area> forced: nofollow noopener noreferrer + target=_blank
PATCH
then regex scan
Stage 2 · Regex patterns
5
Credential detection AWS keys, GitHub PATs, Stripe secrets, PEM keys, Bearer JWTs — Shannon entropy gated, code context de-weighted
[REDACT]
6
Exfil beacon detection fetch() to external hosts, navigator.sendBeacon(), document.location=
FLAG
7
Prompt injection "", , , zero-width chars, HTML comment injection
REMOVE
✓ Clean
sanitized: false — HTML untouched, stored as-is
⚠ Threats found
sanitized: true — content stripped, upload still succeeds, threats logged
03
3Publish
Store &
Return URL
A unique 10-char slug is generated, HTML lands in private R2, a DB record is created with a 30-day expiry, and a shareable URL is returned.
Gate · rate limit
HARD STOP 🚫
≥ 20 uploads in rolling 30-day window
429 returned with current count + upgrade_url. No storage touched.
PASS
Count below limit
Attribution footer injected before </body>, then proceed to store.
then atomic store
Atomic R2 + DB with rollback
🎲
10-char slug generated
nanoid, lowercase alphanumeric. R2 key: {userId}/{slug}.html
☁️
Upload to private R2
text/html; charset=utf-8. IPv4-forced HTTPS agent. R2 failure → 502 immediately.
🗄️
INSERT to PostgreSQL
Slug collision (23505) → delete R2 object + retry up to 3×. Other DB error → delete R2 + 500.
📨
Return 201
{ url, slug, expires_at, sanitized } — url is /e/{slug}, never a raw R2 URL.
5 MBbody limit (before auth)
30 daysfree tier retention
1 hrpresigned URL TTL at serve time