You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Elijah Voigt d9c4830cf9 feat(pages): static-site host at pages.elijah.run (v0)
Micro static-site host running entirely on Cloudflare: a Rust->wasm
Worker (workers-rs 0.7 + axum 0.8) serving user-uploaded .html/.zip
content from R2 at pages.elijah.run/<slug>/, with D1 for users/
sessions/page metadata and GitHub OAuth for login. Admins approve
uploaders, delete pages, and can mark pages/users trusted.

Security design (docs/plans/2026-07-12-pages-design.md): hosted
content is served with a CSP sandbox (opaque origin — no cookies or
storage, cannot make credentialed /api calls) unless admin-trusted;
strict slug grammar + reserved names; streaming zip validation
(traversal, bombs, size/entry/depth limits); SHA-256-hashed session
tokens in __Host- cookies; Origin checks on all mutations; upload
quotas and size caps.

Includes OpenTofu infra (R2, D1, DNS; wrangler owns the Worker
deploy), an embedded single-file management UI, deployment guide,
and 148 native unit tests + 18 doctests (clippy -D warnings clean on
native and wasm32). Deployed and verified in production.

Squash of 15 commits from pages-v0: each ticket implemented by a
Claude Sonnet agent, reviewed by a Claude Opus agent, tracked with
beans (pages-lxjz..pages-shqc).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 week ago
..
.beans feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
docs feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
infra feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
migrations feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
src feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
static feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
.beans.yml feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
.dev.vars.example feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
.gitignore feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
CLAUDE.md feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
Cargo.lock feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
Cargo.toml feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
README.md feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
justfile feat(pages): static-site host at pages.elijah.run (v0) 1 week ago
wrangler.toml feat(pages): static-site host at pages.elijah.run (v0) 1 week ago

README.md

pages

A micro static-site host at pages.elijah.run, running entirely on Cloudflare (Workers + R2 + D1) with the Worker written in Rust compiled to WebAssembly.

What it does

  • Browse all live pages at pages.elijah.run.
  • Log in with GitHub, upload a single .html file or a .zip of static assets (drag & drop), pick a name, and the content is served at pages.elijah.run/<name>/.
  • Admins approve which users may upload, and can delete any page.

Hosted pages are intended to be client-side-only single-page apps. They are served with a sandboxing Content-Security-Policy (opaque origin): no cookies, no localStorage — see docs/plans/2026-07-12-pages-design.md §2.1. An admin can mark a specific page, or all of a user's pages, as trusted to opt them out of that sandbox (e.g. for local-first apps that need localStorage) — see the "Trust escape hatch" addendum in §2.1 for the exact semantics and the security trade-off that comes with it.

How it works

One Cloudflare Worker routes everything:

  • / — embedded management UI (list, login, upload, admin)
  • /auth/* — GitHub OAuth + session cookies (sessions in D1, hashed)
  • /api/* — JSON API (list/upload/delete pages, manage users)
  • /<slug>/* — page content served from R2 with strict security headers

Uploads are validated hard: strict slug grammar + reserved names, zip path-traversal rejection, decompression limits (10 MiB compressed, 25 MiB uncompressed, 300 files). See the design doc for the full security analysis.

Run locally

# from pages/
cp .dev.vars.example .dev.vars   # fill in a GitHub OAuth app's id/secret
wrangler d1 execute pages --local --file migrations/schema.sql
wrangler dev                      # builds via worker-build and serves locally

Test

# from pages/
cargo fmt
cargo check --target wasm32-unknown-unknown
cargo clippy --all-targets -- -D warnings
cargo test

Deploy

Infrastructure (R2 bucket, D1 database, DNS, rate limits) is OpenTofu in infra/; the Worker itself deploys with wrangler deploy (see infra/README.md). Secrets via wrangler secret put GITHUB_CLIENT_SECRET.

License

Dual-licensed under Apache-2.0 and MIT, like the rest of this repository.

Disclaimer

This software was written with Claude Code. Design, review and orchestration by Claude Fable 5 (claude-fable-5); implementation by Claude Sonnet agents with Claude Opus review agents.