5 Commits (36d13cf2661b1631ca34abda26956206a9c15c12)

Author SHA1 Message Date
Elijah Voigt 36d13cf266 feat(pages): raise per-file upload limit to 32 MiB for large wasm
An optimized 22 MiB bin_bg.wasm exceeded the 16 MiB per-file cap. Bump
max_file 16 -> 32 MiB (still < the 48 MiB total). max_uncompressed_total
(48 MiB) and max_compressed / MAX_UPLOAD_BYTES (24 MiB) are left
unchanged, so worst-case Worker memory (buffered body + decompressed
manifest) stays well under the ~128 MiB isolate ceiling.

Note: prefer zstd/gzip over xz for large archives — lzma-rs is slow in
wasm and can trip Cloudflare's per-request CPU limit (503 exceededCpu).

Bean: pages-yhxp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 day ago
Elijah Voigt f7fa11071d feat(pages): raise upload limits for large wasm (per-file 16 MiB, total 48 MiB)
The 5 MiB per-file cap rejected wasm binaries larger than that
("exceeds the maximum per-file size of 5242880 bytes"). Raise the
coherent limit set to fit large wasm while staying well under the
Worker's ~128 MiB memory ceiling (the body is buffered and decompressed
in memory):

- max_file:               5  -> 16 MiB
- max_uncompressed_total: 25 -> 48 MiB
- max_compressed / MAX_UPLOAD_BYTES / axum body cap: 10 -> 24 MiB
- max_entries / max_path_len / max_depth: unchanged

Updates UploadLimits::default, routes::api::MAX_UPLOAD_BYTES (the
DefaultBodyLimit layer follows it), the frontend client-side pre-check
and message, and the design doc §2.2 numbers.

Bean: pages-ufk2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 day ago
Elijah Voigt f42ae56bb4 fix(pages): normalize '.' path segments so ./-prefixed tar entries upload
GNU tar prefixes every entry with ./ when archiving a directory
(tar czf out.tgz . yields ./index.html, ./bin_bg.wasm, …). The path
validator rejected any '.' segment as traversal, so such tarballs failed
with "archive entry has an unsafe path: './...'" and their ./index.html
never satisfied the root-index.html requirement — making ./-prefixed
archives unusable. (.wasm was never the issue: extensions aren't checked
and mime.rs maps .wasm to application/wasm.)

check_entry_path now strips '.' segments (leading and interior) and
returns the normalized path used as the R2 key, while still rejecting
'..', absolute paths, backslashes, NUL, drive letters, empty (//)
segments, and names that normalize to nothing; depth is measured on the
normalized path. Both the zip and tar manifest builders store the
normalized name. +10 tests.

Bean: pages-aalu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 day ago
Elijah Voigt 0aa6a6862e feat(pages): accept gzip/zstd/xz compressed tar uploads
Extend the upload validator to accept .tar.gz/.tgz, .tar.zst, and
.tar.xz archives alongside raw .html and .zip. Upload-kind dispatch is
now magic-byte sniffing (detect_kind) rather than trusting Content-Type,
matching the module's never-trust-the-archive posture.

build_tar_manifest reuses every zip-path structural and size rule
(path-traversal checks, dedup, entry cap, root index.html requirement).
The streaming zip-bomb defense is preserved end-to-end: gzip/zstd decode
lazily as each tar entry is read, and one-shot xz decompresses into a
size-bounded LimitedWriter so a bomb aborts mid-decompress. All decoders
are pure Rust (flate2 rust_backend, ruzstd, lzma-rs) and the tar crate
compiles cleanly for wasm32-unknown-unknown; bzip2 is intentionally out
of scope.

Frontend accept list and Content-Type helper updated; the backend now
sniffs the real format so archives are sent as application/octet-stream.

Beans: pages-w7ow (feature) + pages-r4ob/g39t/wadp/u3zc (tasks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 days ago
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