Skip to main content

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 like https://*.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.dev credentials 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

MethodPathAuthNotes
PUT/api/chain/:slug/site/:pathBearer (operator+)Upload a byte stream at :path. Hash committed on the entangled state.
GET/api/chain/:slug/site/:pathoptionalRead back the byte stream (paid tier) or the hash (free tier).
DELETE/api/chain/:slug/site/:pathBearer (admin+)Remove. Records a deletion row on the entangled state.
GET/s/:slug/* (or https://:slug.zeqsite.app/*)nonePublic serve. Strict CSP. MIME-forced.
GET/api/chain/:slug/site/manifestBearer (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, upload index.html + style.css, share https://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/sse via the same-origin gate. Public dashboards for is_public machines work without auth.
  • Companion docs for a contract. Upload a static page describing your deployed contract; link from the contract definition's description field.
  • Static API stub. Upload a JSON file at /manifest.json, have a partner read it via GET /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.dev cookies (you can't — separate origin).
  • Anything that wants to call random external APIs (you can't — strict connect-src).

Next