Trust modelalpha
What does each of the four front ends require you to trust?
The trust model is the list of parties who could change your result without you noticing. It is different for each of the four front ends, and only one of them is trustless.
The constraint that shapes all four: there are no certificates off-chain. A certificate is a subnet threshold signature, and there is no subnet in moxzid or in a browser tab. Faking one would be strictly worse than omitting it, because client code would verify the forgery and believe it. So ic0.data_certificate_present reports 0, and @dfinity/agent's Actor — which polls for a certificate — is unsupported. The generated idlFactory path is the supported surface, and the argument bytes it produces are the real encoder's.
The four front ends#
moxzi CLI | moxzid | moxzi-web | On-chain builder | |
|---|---|---|---|---|
| Runs where | your machine | one server | one browser tab | an IC subnet |
| You must trust | your machine and the compiler binary | the server operator | the page origin | subnet consensus and the deployed module |
| Auth | none — local process | one shared bearer token | none — same-origin JS | IC caller principal, per-job owner checks |
| Instruction metering | wasmtime fuel | wasmtime fuel | opt-in instrumentation (meter: true) — V8 has no fuel, so the module counts its own | the replica |
| Preemption | fuel exhaustion | fuel exhaustion | worker termination, wall-clock | the replica |
| Certificates | no | no | no | yes |
| Who can read your source | you | the operator | the page | the canister's operators, unless you build from a mops tag |
| Trustless | no | no | no | yes |
The CLI trusts your machine#
moxzi build runs compiler.wasm under wasmtime in your own process. Anything on your machine that can modify that file, the linker, or the CLI binary can change your artifact, and nothing in the design prevents it. What the CLI does give you is a name for what it used: every build prints a provenance line naming the compiler by SHA-256, and moxzi info reports the loaded compiler and linker with their hashes. Compare those against the release notes; that comparison is the only integrity check available locally.
--remote changes the picture: the compile happens on-chain and the CLI downloads the artifact, so the local machine is reduced to a transport — but the link still happens against your local linker unless you use the on-chain linker, so check what your build actually did.
moxzid trusts the operator#
moxzid is an actor host, not a sandbox for you against its operator. The operator can read every actor's state, replace its code, and read every message.
| Property | Detail |
|---|---|
| Auth | a single shared bearer token (--auth-token / MOXZID_TOKEN), constant-time compared, on every endpoint except /health, /health/ready and /version |
| User model | none. One token, all powers. This is stated as alpha scope rather than half-implemented as RBAC |
| TLS | not in the binary. Terminate it in a reverse proxy |
| Dangerous surface | POST /install/<name> runs arbitrary wasm. That is why moxzid refuses to bind a non-loopback address without a token unless you pass --insecure |
| Scale | single-node. One process, one queue per actor, round-robin fairness. More moxzids do not coordinate |
| Inspector | the dashboard at / is static and open; every data call it makes still needs the token |
Rate limits, IP allowlists and origin restrictions are deliberately absent from the binary and belong in the proxy. If you are exposing moxzid, the token is the entire access control system — treat it accordingly.
The browser trusts the page#
Actors in a tab run inside the page's own JavaScript context. Any script the page loads can call them, read their state, and rewrite their storage; IndexedDB persistence is origin-scoped and no more private than the origin is. There is no boundary between your actor and the page — the page is the host.
Two consequences that are easy to get wrong:
- No per-instruction metering. V8 offers no fuel, and nothing is faked. Creation and message fees are charged everywhere, but instruction limits are enforced only in moxzid (wasmtime fuel) and on the IC. In a tab the only enforcement is wall-clock, and only in worker mode: a browser cannot interrupt a running wasm call, so the runtime terminates the worker.
- Preemption is coarse. A terminated worker cannot report where it got to, so the rewind is to the last completed ingress, not the trapping message. On the IC a trap discards exactly its own writes; here it discards everything since the last commit point. Autosave narrows that window to one message.
Outcalls need cross-origin isolation (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp) because the sync-to-async bridge uses SharedArrayBuffer. Requesting deadlineMs or http without worker: true fails at start rather than half-working.
The on-chain builder is the trustless one#
This is the front end the rest exist to be compared against. An artifact whose bytes were produced by a canister, from sources anyone can hash, by a compiler anyone can audit, is evidence — and it is evidence precisely because the local build reproduces it byte for byte.
What you still trust, stated plainly:
| You trust | Because |
|---|---|
| The subnet's consensus | It executes the canister. This is the IC's own trust model, not moxzi's. |
| The deployed module hash, not the canister id | A canister with controllers can be upgraded. Verify the compiler and linker SHA-256s published in the release notes against the deployment before treating a build as attested. |
| The operator set | syncOperators() copies the controller list into the operator set, and a fresh deployment is locked until it runs. Operators arm payment and can steer jobs. |
And what the operator can see: uploaded source sits in the canister's VFS and is readable by operators until removed or evicted. This is disclosed rather than mitigated. Builds from registered mops tags upload nothing — the canister materialises content-addressed blobs it already holds — so closed-source projects should prefer tag builds. Tags are not taken on trust either: useTags accepts only tags that passed verifyTagAgainstMops, and fails closed when no registry is configured, because content-addressing proves a client did not lie about content while only mops can say the content is really that package version.
Between customers, the isolation is real and tested: every job method checks owner-or-operator, and refusals are silent (null/0/false) so probing job ids teaches nothing. test/pic-js/test/job-auth.test.ts pins it. Uploads are quota-bounded at 64 MiB per caller and 1 GiB total.
On money: escrow is pulled before compute, settlement charges 3× measured actual and refunds the rest, startEscrowed refuses up front what the deployment cannot afford to finish, and a customer who walks away can be settled by abandonSettlement. A fresh deployment with payment unconfigured compiles for free and says so.
Things you cannot do#
| Not possible | Why |
|---|---|
| Verify a certified query off-chain | No subnet, no threshold key. Certified-data patterns need the real IC. |
Use @dfinity/agent's Actor against moxzid or a tab | It polls for a certificate. Use the generated idlFactory. |
| Enforce an instruction limit in a browser for free | V8 has no fuel, so the module has to be instrumented to count its own instructions. meter: true does that and enforces a real limit; it costs about a fifth in size and half again in speed. Unmetered, the only lever is a wall-clock worker deadline. |
Get per-user isolation from moxzid | One shared token; no user model in the alpha. |
| Hide source from an on-chain build operator by uploading it | The VFS is canister state. Build from a mops tag, or accept the disclosure. |
| Treat a canister id as an attestation | Verify the deployed module hash. An upgradeable canister is only as trustworthy as its controllers. |
Next#
- Byte-identical output — why reproducing an on-chain build locally is what makes it evidence.
- Self-hosting and fixed points — why the compiler itself is auditable end to end.
- Architecture — which process runs your code in each of the four cases.