Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2513 min read

asset-proxy handler exposed all R2 keys publicly SOC 2 evidence leak — QQ F1 (PR #551)

Soren Copenhagen Vesterbro 35-yo 10-yr independent cloud security research practice 5-yr Maersk platform security S3/GCS configuration audits + 5-yr solo Nordic + DACH SaaS audits @soren-r2sec weekly cloud security research blog. Q1 2026 thMenu open-source repo comment-as-spec drift audit Worker handler docstring describe one behavior implementation does another cloudflare/src/handlers/asset-proxy.ts header Serves affiliate marketing materials under /assets prefix backed by menu-images R2 bucket implementation handleAssetProxy url.pathname.slice('/assets/'.length) decode key MENU_IMAGES.get ANY key no path gate. Lab test curl /assets/affiliate/banner1.png 200 banner expected curl /assets/backups/2026-05-12/meta.json 200 backup metadata public table names + row counts + R2 file paths + backup encryption hints. /assets/soc2-evidence/Q1-2026/payouts.txt 200 SOC 2 compliance evidence affiliate payout summary + KYC audit trail + compliance assessor names. /assets/menu-images/<arbitrary_restaurant_id>/ 200 cross-tenant menu images. 3 R2 bucket prefixes public-readable backups/ + soc2-evidence/ + menu-images/ endpoint designed gate only affiliate/ gate documented not enforced. Writeup CVSS 9.1 CRITICAL data exposure + compliance breach + GDPR Art.32. Engineering 30 minutes reproduce first action 503 maintenance mode 60 minutes public asset traffic offline affiliate dashboard marketing-assets minor no operational impact. Second action 90-day Cloudflare access log audit /assets/backups/* + /assets/soc2-evidence/* + /assets/menu-images/* 12 requests 90 days all internal Cloudflare workers backup write + soc2-evidence write 0 external attacker queries door open no attacker walked through after disclosure window external recon could begin shipped fast. 3 wrong theories (1) add affiliate/ prefix validation quick fix correct incomplete maintenance burden future allowed prefixes whitelists right pattern enforcement explicit + auditable; (2) restrict at R2 bucket policy level Cloudflare R2 bucket-level allowlists outside Worker overhead Worker handler enforcing own gate cleaner + more visible; (3) URL whitelist regex frontend client-side bypassable Worker handler server-side gate. Correct pattern ALLOWED_ASSET_PREFIXES Set + explicit path gate + 404 not 403 enumeration-resistance. Forensic header docstring Serves affiliate marketing materials let original author assume implementation enforced const key = url.pathname.slice /assets/.length stripped /assets/ passed remaining path R2.get key affiliate/ prefix supposed there but never enforced Soren generalized comment-as-spec drift pattern CLAUDE.md §17 canonical anti-pattern comments treated like contracts implementations diverge silently sneakiest leak sources. Production audit backups/ 28-day retention + soc2-evidence/ Q1-Q4 + menu-images/ cross-tenant uploads 7500 R2 objects publicly readable. PR #551 batch QQ F1 3-layer fix Layer 1 ALLOWED_ASSET_PREFIXES = ['affiliate/'] isAllowedAssetKey startsWith only affiliate/ accepted backups/ soc2-evidence/ menu-images/ explicit reject. Layer 2 explicit gate + 404 (not 403) enumeration-resistance 403 tell attacker which keys exist 404 surfaces nothing here attacker cannot enumerate. Layer 3 structured audit log console.warn asset_proxy_rejected + key + ip Logpush + Sentry suspicious pattern detection recon attempts. Bonus R2 bucket lifecycle policy per prefix backups/ + soc2-evidence/ read-restricted CORS + worker-only access + one-way write defense-in-depth Worker bypass scenario R2 bucket-level access still block. Sibling-surface sweep comment-as-spec drift sweep all Worker handlers asset-proxy.ts PR #551 QQ F1 + image-proxy.ts path-traversal regex PR #229 match + poll-check.ts auth gate PR #526 HH F2 + BBB F2 sweep match + custom-domain-resolver.ts DNS verification gate PR #568 TT-B F3 match + customer-magic-link.ts TTL + IP rate-limit PR #335 match + mcp-bridge/index.ts bearer auth PR #631 HHH F3 match. Soren only outstanding case doc-implementation parity matrix engineering wiki every new handler check before merging. Production audit 90-day Cloudflare access log 0 external attacker queries disclosure 60 min 503 + 30 min fix + 30 min R2 bucket policy patch total exposure window Q1-Q4 2026 0 exploits. Soren €3500 Wise CVSS 9.1 CRITICAL + Hall of Fame + advisory board priority seat LinkedIn 6.7k Nordic cloud security community benchmark. Cenk Istanbul Bagcilar @cenk-r2sec ex-Vodafone TR platform security 6-yr parallel disclosure €3000 Turkish security community blog 4.7k. Pattern Worker handler header docstring does X must check against implementation actual gates comment-as-spec drift sneakiest leak source ALLOWED_PREFIXES set + explicit gate + 404 enumeration-resistance + audit log quartet canonical R2/storage public-facing handler. Implementation handler docstring-vs-implementation gate matching audit + ALLOWED_PREFIXES const Set + startsWith explicit + 404 not 403 + structured console.warn audit + R2 bucket-level CORS + lifecycle policy + production 90-day access log audit + PR template checkbox + quarterly Worker handler doc-vs-implementation parity audit. PR #551 reference.

th

thMenu Team

thmenu.com

Found this helpful? Share it.