State-channel HTML hosting
Status — coming soon. The threat model is locked; the snippet pipeline is live; the hosting routes ship in the next phase release. This page documents the planned contract.
You spin up a state machine and you can host a webapp on it. The pitch is spin-up-as-webapp — no separate Vercel, no separate Netlify, no separate CDN. Your zsm_ key already authorizes uploads; the machine is already on the entangled state; the bytes you upload are hash-committed to your audit log on every change.
The framework runs the server. The framework enforces a strict CSP at serve time. The framework forces MIME from extension. The framework path-normalizes against your machine root. The framework writes an entangled state row on every serve. You get a fully-audited, sandboxed mini-webapp, billed against your tally.
What it is
- A per-slug HTML hosting tier served at
https://zeqapi.com/s/:slug/*(or a separate origin likehttps://*.zeqsite.app— see open questions). - Bytes uploaded via authenticated PUT to
/api/chain/:slug/site/:path. - Bytes served from a sandboxed iframe — separate origin, isolated cookies, no
zeq.devcredentials in scope. - Strict CSP (see security model §2.2).
- Hash commitment on the entangled state on every byte stream served.
- Free tier: 1 GB/month egress, 10K serves/day, hash-only (no byte storage). Paid tier: bytes stored alongside, separate billing.
What it is not
- Not a CDN. Throughput is rate-limited. For high-volume static, use a CDN and beacon to your machine from the CDN edge.
- Not a runtime. There's no server-side execution; this is static hosting + framework-controlled response headers.
- Not a database. State should live in your audit entangled state, your contracts, your tally — not in the static bytes.
Planned routes
| Method | Path | Auth | Notes |
|---|---|---|---|
PUT | /api/chain/:slug/site/:path | Bearer (operator+) | Upload a byte stream at :path. Hash committed on the entangled state. |
GET | /api/chain/:slug/site/:path | optional | Read back the byte stream (paid tier) or the hash (free tier). |
DELETE | /api/chain/:slug/site/:path | Bearer (admin+) | Remove. Records a deletion row on the entangled state. |
GET | /s/:slug/* (or https://:slug.zeqsite.app/*) | none | Public serve. Strict CSP. MIME-forced. |
GET | /api/chain/:slug/site/manifest | Bearer (viewer+) | List of paths + hashes. |
Each PUT writes:
- A byte commitment to the audit entangled state (
transition_type: "site_put",state_hash: sha256(bytes),zspEnvelope: {path, mime, bytes}). - (Paid tier) the bytes themselves to the machine's bucket.
- A free-tier 404 on the serve route until the bytes are mirrored to a CDN-equivalent surface.
Each public serve writes:
- An audit row (
transition_type: "site_serve",state_hash: sha256(bytes),zspEnvelope: {path, ip_hash, zeqond}). - The egress counter against your tally.
CSP at serve time
Every served byte stream gets the same response headers, framework-set, NOT user-overridable:
Content-Type: <forced from extension>
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline'; img-src 'self' data:;
font-src 'self' data:; connect-src 'self';
frame-ancestors 'none'; form-action 'self';
base-uri 'none';
sandbox allow-scripts allow-forms allow-same-origin
allow-popups allow-popups-to-escape-sandbox;
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
X-Zsm-Content-Sha256: <hash of bytes>
The CSP allows 'unsafe-inline' for scripts and styles (so embedded apps work) but locks connect-src to 'self'. Any external-API call must go through the per-slug proxy (planned: /api/chain/:slug/proxy?url=..., owner-allowlisted URLs only).
X-Zsm-Content-Sha256 lets external verifiers hash the bytes themselves and compare to the entangled state commitment — proof that the framework served what the entangled state claims.
The hash-commitment-only doctrine
Free-tier hosting does not store the bytes. The framework commits the hash on PUT and on the first serve mirrors the bytes to an ephemeral CDN-edge cache. If the cache evicts, the next serve returns 503 byte_eviction and the owner re-uploads. The entangled state remains the ground truth.
This is consistent with the framework's audit-log doctrine: bytes are off-chain; hashes are on-chain; the entangled state is the witness.
For paid-tier hosting, bytes are persisted in a machine-isolated bucket and the manifest tracks (path, hash, bucket_url, bytes). The entangled state row also points to the bucket, so a future verifier can fetch and re-hash without trusting the framework's serve.
Use cases this is built for
- Marketing landing for a machine. Spin up
my-launch, uploadindex.html+style.css, sharehttps://my-launch.zeqsite.app. Every visitor's view is on your entangled state. - Client-side dashboard for an entangled state. Upload an HTML/JS dashboard that talks to
/api/chain/:slug/explore/ssevia the same-origin gate. Public dashboards foris_publicmachines work without auth. - Companion docs for a contract. Upload a static page describing your deployed contract; link from the contract definition's
descriptionfield. - Static API stub. Upload a JSON file at
/manifest.json, have a partner read it viaGET /s/:slug/manifest.json. Hash on the entangled state proves what they read.
Use cases this is NOT built for
- Real-time multiplayer apps (no server-side runtime).
- High-volume CDN-style static (use a real CDN + beacon to your machine).
- Anything that wants to read
zeq.devcookies (you can't — separate origin). - Anything that wants to call random external APIs (you can't — strict
connect-src).
Next
- Embed + hosting threat model — full security analysis.
- Embed snippet — pair the snippet with hosting for end-to-end audited apps.
- State machines — the binding.