Ixchel is the part of Mawu that makes "creator republic" more than a slogan: it is the runtime that lets a community author executable content — entity scripts, behaviours, economy rules, whole world genomes — and run it on the authoritative realm server without that code being able to harm the realm, the players, or the platform. This is the differentiator V7 stakes its identity on. Roblox lets you run code but owns every server; FiveM lets you own the server but a malicious resource has near-total power over its host and the highest-risk path to players. V7's answer is to let creators ship real code and run it only as capability-sandboxed WebAssembly behind a hardened client the operator cannot patch. The whole bet rests on one claim being literally, mechanically true: untrusted creator WASM cannot reach a file, a socket, a process, another plugin's memory, the platform economy, or the realm clock unless a tier and a manifest explicitly grant it — and even then it traps at a deterministic fuel budget rather than hanging the world.
Ixchel productizes the Maya forge-* modding framework into the V7 realm
runtime, and it is three things wearing one name: a resource model and
lifecycle (load, hot-reload, fault-isolate creator code), a WASM sandbox
safety model (tiers, capabilities, fuel/epoch budgets, store limits, host
hardening), and a dependency-resolution + content-addressed storage layer (a
PubGrub solver and a Nix-style content store that pins exactly which bytes a
realm runs). This page is the deep companion to the "Ixchel — The Modding
Runtime", "The WASM Sandbox", and "Dependency Resolution and Content-Addressed
Storage" sections of the orientation hub
../V7_ARCHITECTURE.md.
What ships, honestly#
The sandbox is real, not a contract — and that is the distinction this page exists to be precise about. There are two Ixchel codebases in the tree, and they play different roles:
-
The real runtime ships in
apps/v7/moremi-realm-server(a single ~14,900-line Rust crate) on a genuine Wasmtime 45.0.0 with the Component Model, Cranelift, fuel, epoch, and async features enabled (Cargo.toml:17), plus PubGrub 0.3.0 and SHA-256. It carries eleven Ixchel/forge eval functions and 69 tests; the fifteenixchel_*sandbox tests compile and pass (cargo test … ixchel→15 passed), exercising real WAT modules against the real engine — runaway loops that actually trap on fuel exhaustion, oversized memories that are actually refused at instantiation, hostile capability requests actually denied before a component instantiates. This is wired to CI:scripts/v7/verify-adversarial-eval-gates.mjsregisters asandbox-escapegate whose workflow command iscargo test … -p moremi-realm-server ixchel_sandbox_escape_evaland whose evidence needle is literallyescaped_fixtures == 0. -
The
libs/maya/forge-*crates are framework scaffolds, deliberately API-first.forge-sandboxdefines the tier/manifest/budget contract that upload and scanner code compile against, and says so in its own doc comment — "the full implementation will host Wasmtime component-model modules" (forge-sandbox/src/lib.rs:5); it has no Wasmtime dependency.forge- resolveris a deterministic exact-manifest resolver explicitly preceding the "full V7 resolver [that] will use PubGrub" (forge-resolver/src/lib.rs:6).moremi-realm-serverdoes not import these crates — it reimplements their surface natively against the real engine. Two of the four are nonetheless real working code today:forge-compositor(835 lines) genuinely composes layers with per-field provenance and implements the Maya Loom genome/runtime- expression system, and@maya/forge-assist(TypeScript) is a real AI co-creator that enforces the trust boundary in code before any artifact reaches the sandbox. -
The honest seam is the live intake-and-serve daemon. The sandbox, the resolver, and the content store ship as verified libraries and eval gates, not as an assembled service that today accepts an uploaded creator module over the network, scans it, and streams it to clients at scale. The hostile-module corpus is hand-built WAT fixtures, not a stream of real uploads; the upload → malware-scan → AOT → CDN pipeline is described in the monolith and partially realised (the AOT step is real; the scanner and CDN are the seam). Treat the byte-level mechanics below as proven and the production wiring as the integration target.
The modding runtime: resources, tiers, lifecycle#
A realm's behaviour is built from resources — the FiveM unit, hardened. A
resource is an Ixchel layer that bundles assets, data blobs, and WASM scripts,
declares its dependencies and the sandbox tier it needs, and names the events
and callbacks it exports and consumes. That is exactly the shape of
MoremiResourceManifest (moremi-realm-server/src/lib.rs:741): a
resource_id, version, kind (BaseFramework or Content), a
sandbox_tier, a hot_reload flag, and vectors of dependencies, assets,
data_blobs, wasm_scripts, exported_events, exported_callbacks,
consumed_exports, and capability_grants. Assets and data blobs carry a
content_hash (:660, :680); a WASM script carries a module_hash and an
entry_callback (:695). The reference RP framework is itself a
BaseFramework resource that exports inventory/job/economy primitives, and
content resources depend on it — the manifest's
with_dependency/consuming_callback builders model precisely that.
The six tiers are a total order (MoremiResourceSandboxTier, :583):
DataOnly < Scripted < Extended < System < Native < Trusted. The lattice is not
decorative — allows_code_execution is tier > DataOnly (:617), and
grants_capability is tier >= capability.minimum_sandbox_tier() (:621).
Capabilities are typed (MoremiIxchelWitCapability, :542) and each names a
WIT interface and the minimum tier that may hold it (:567): RealmEmitEvent
needs Extended, EconomyGrantCurrency needs System, and
PlatformSecretRead needs Trusted. A realm declares a maximum tier it will
host; the tier-gate eval (run_moremi_ixchel_tier_gate_eval, :7893) proves
that a System-tier resource loaded into an Extended-max realm is refused with a
precise SandboxTierExceeded { required: System, allowed: Extended } error,
while a lower-tier guest is never even handed the host import it didn't earn.
The lifecycle manager (MoremiIxchelLifecycleManager, :7599) hot-loads
and unloads resources without restarting the realm. hot_reload_resource
(:7673) swaps a resource generation while preserving the prior generation's
state hash and emitting a cleanup receipt that releases the old callbacks and
state handles. The decisive safety property is fault isolation:
isolate_faulting_resource (:7702) sets a faulting resource to Disabled,
records the fault reason, and — critically — leaves realm_running true and the
connected-player count unchanged. The lifecycle smoke test
(run_moremi_ixchel_lifecycle_smoke, :7757) asserts that a deliberately
faulting resource is disabled while healthy resources stay active, no player is
disconnected, and the realm does not crash. A bad mod takes itself down, not the
world.
The WASM sandbox safety model#
The sandbox is the security keystone, and it is enforced at four layers that compose into one gate.
Capability-typed host interface (Component Model + WIT)#
A plugin receives only the host imports its tier and manifest grant — no ambient
authority. The capability-deny eval (run_moremi_ixchel_capability_deny_eval,
:8660) builds a guest that declares RealmEmitEvent, instantiates it as a
real Wasmtime component, and confirms it can make exactly one host call. It then
runs a hostile corpus — a guest reaching for EconomyGrantCurrency and one
reaching for PlatformSecretRead without declaring them — and asserts both
are denied before instantiation (denied_hostile_attempts == hostile.len()),
never instantiated, never called, with zero host calls recorded. This is the
UEFN-Verse "no filesystem, no sockets, no system resources" guarantee made
language-agnostic: capability absence is structural, expressed through which WIT
imports the linker actually provides.
CPU budgeting: fuel for gameplay, epoch for cosmetics#
Gameplay-critical plugins run under fuel — deterministic,
instruction-counted metering — so they trap at the same instruction on every
replay; cosmetic/UI work runs under cheaper, non-deterministic epoch
interruption. The budget eval (run_moremi_ixchel_budget_eval, :7958)
compiles two real runaway-loop WAT modules and runs them. For the deterministic
case it sets config.consume_fuel(true) (:8030), store.set_fuel(budget)
(:8047), calls the export, and observes a real trap with get_fuel() == 0,
classified as FuelExhausted (:8076). For the cosmetic case it sets
config.epoch_interruption(true) (:8033), set_epoch_deadline +
increment_epoch (:8052), and observes an EpochDeadline trap. Either way
the attempt is recorded as trapped, throttled, and flagged — overrun is
contained and surfaced, never allowed to hang the realm tick.
Memory and stack limits via StoreLimits#
The Wasmtime Config caps the WASM stack, sizes and zeroes the async stack,
and installs memory guard pages (moremi_ixchel_budget_wasmtime_config,
:8151); the per-store StoreLimitsBuilder caps linear-memory size, table
elements, and instance count and sets trap_on_grow_failure(true) (:8165).
The probe proves three things against the real engine: an oversized
linear-memory module is refused at instantiation (:8177), an unbounded
recursive-stack module traps (:8190), and — the data-leak guard — two
instances of the same module are isolated, so the second instance reads 0 for
a secret the first wrote (173), giving no_cross_instance_memory_leak
(:8146). Async-stack zeroing exists precisely so a freed stack can't leak one
plugin's data into the next.
Host hardening and AOT#
Borrowing Luau's posture, the host surface is frozen. The host-tamper eval
(run_moremi_ixchel_host_tamper_eval, :8517) runs five attacks — mutate a
host global, mutate a host metatable, write another plugin's environment,
disable the interrupt callback, and load untrusted bytecode — under a
luau_hardened_default policy, and asserts all five are blocked and the
shared host-state hash is byte-identical before and after (:8557). Separately,
vetted plugins are AOT-compiled at upload:
run_moremi_ixchel_aot_audit_eval (:8278) calls
engine.precompile_component() to emit .cwasm bytes, confirms them with
WasmtimeEngine::detect_precompiled (:8380), and runs them on a production
engine built with the compiler disabled (:8384) — the same speed-and-attack-
surface reduction the monolith specifies.
The escape gate#
All of this rolls up into one adversarial gate.
run_moremi_ixchel_sandbox_escape_eval (:8726) aggregates the capability,
budget, and host-tamper evidence into a single fixture corpus —
undeclared-economy, undeclared-platform-secret, runaway- fuel-loop,
oversized-linear-memory, frozen-host-global-tamper, untrusted-bytecode- load —
marking each blocked / detected / escaped. The report passed() (:7559)
is true only when the corpus is non-empty, every fixture is blocked and
detected, and escaped_fixtures == 0. The pinned test
ixchel_sandbox_escape_eval_blocks_all_hostile_modules (:14717) asserts
report.passed() over ≥ 6 fixtures. That zero-escape bar is the launch gate
the monolith names, and it is green today.
Dependency resolution and content-addressed storage#
A realm is reproducible only if "which exact bytes run" is pinned, and that is two subsystems.
Resolution is PubGrub. run_moremi_forge_resolver_eval (:9934) drives a
real pubgrub::resolve over a package registry, with SemVer ranges parsed by
the semver crate (VersionReq::parse / Version::parse, :10088),
optional dependencies, feature gates (moremi_forge_dependency_active,
:10072), and capability alternatives that expand to a cartesian product of
providers satisfying an abstract capability id
(moremi_forge_expand_capability_alternatives, :10019). The eval proves three
behaviours: a satisfiable graph locks the expected versions and resolves
render.surface to mesh-surface-pack over two decoy providers; disabling the
weather feature drops the optional weather-pack from the lock; and an
unsatisfiable graph fails with a precise root cause, not silently — the
MoremiForgeFailureReport carries plain_english, root_causes, and a
pubgrub_report from PubGrub's DefaultStringReporter. The solution freezes to
a MoremiForgeLockFile (:1590) carrying the resolver id, enabled features,
locked packages, capability selections, and a lock_hash.
Storage is content-addressed, Nix-model.
run_moremi_forge_content_store_eval (:11145) builds a content manifest where
every artifact's id is a SHA-256 hash of its content and its full dependency
closure. moremi_forge_content_artifact_for (:11249) recurses dependencies,
computes a content_hash over the package's blobs (:11342, Sha256), a
closure_hash over the sorted dependency artifact hashes (:11359), and
combines them into the artifact_hash (:11375) that becomes the store path.
This buys, for free, the four properties the monolith claims: deduplication
(identical content + closure ⇒ identical path), tamper-evidence (the eval
swaps one dependency and the artifact hash changes), atomic install/rollback
(MoremiForgeContentStore, :2078, keeps prior generations — a failed
activation preserves the previous active state hash via
try_install_manifest_with_activation_fault, :2126, and rollback_to,
:2134, restores it), and side-by-side versions. The lock's hash is the
supply-chain integrity primitive and the AOT .cwasm cache key.
The pinned lock is what crosses to the client.
V7/ue/Config/RealmLocks/dedicated-smoke.lock.json is a real realm lock whose
artifactClosure lists each artifact with a sha256: contentHash, its
dependencyRefs, and a payloadHash; the UE module MawuRealmLockTypes.cpp
parses and validates it, and AMawuComposedRealmActor::ApplyLockFile composes
the realm geometry from that content-addressed closure. Server resolves and
pins; client renders exactly the pinned composition.
Composition and the Loom#
Once resolved, layers compose in fixed priority. forge-compositor
(forge-compositor/src/lib.rs:271) applies layers in the band order
Engine < BaseGame < ContentPack < ServerRealm < CommunityVariant < PersonalOverride,
breaking ties by layer id, and records the source_layer_id for every composed
field — so a realm operator can see which layer supplied each value. On top of
that sits the Maya Loom: compile_world_genome_layer (:300) turns a
shareable world genome (physics constants, biome/dimension/narrative rules) into
a deterministic Ixchel layer, and express_world_genome (:424) applies it as
a non-destructive layer — fork_created: false — reporting only the changed
paths, the §15 "Variants without forking" capability.
compose_runtime_expression_view (:480) then layers per-player runtime
expressions with visibility scopes, its test proving one player's "eternal
winter" does not bleed into another's view and that a realm-wide expression
requires explicit owner consent. (Loom's signatures use an FNV-1a digest for
change-detection, distinct from the SHA-256 the content store uses for integrity
— a deliberate, documented split.)
The pipeline end to end#
Edge cases and malicious UGC#
- A mod reaches for the economy. A guest invoking
EconomyGrantCurrencywithout the System tier and a declared grant is denied before instantiation — no host call ever happens (:8660). Currency lives in Aje behind the trust boundary, never in realm WASM, so even a granted realm script mutates only realm-local, non-fungible play-currency. - A mod tries to read a platform secret.
PlatformSecretReadis Trusted-tier, first-party only; an uploaded community resource cannot declare it, and the capability is refused at link time. Thesubstrate-bridgeV7IdentityFirewallindependently guarantees realm code only ever sees an opaque per-realm handle, never a platform account id. - An infinite loop. A runaway gameplay plugin exhausts its fuel and traps at
a deterministic instruction (
FuelExhausted,:8076); it is throttled and flagged, the tick proceeds, and a golden replay still reproduces the exact state hash because the trap is deterministic. - A memory bomb / data exfiltration. Oversized linear memory is refused at
instantiation and a recursive stack traps (
:8177,:8190); async-stack zeroing plus per-instance isolation mean a freed plugin can't leak bytes into the next (:8146). - Tampering with the host or another plugin. Frozen globals/metatables, a
per-plugin environment boundary, a non-disableable interrupt callback, and a
refusal to load untrusted bytecode block all five host-tamper vectors with the
shared host hash unchanged (
:8517). - A swapped dependency. Because an artifact id hashes its content and its dependency closure, substituting a dependency changes the artifact hash; the lock no longer verifies, and the realm refuses to compose the altered closure.
- A forged client event. Orthogonal but adjacent: the realm protocol treats
every cross-trust message as hostile —
rejects_forged_payload_even_with_valid_tokenandrejects_forged_security_token_even_when_envelope_is_resigned(realm-protocol/src/lib.rs:3788,:3799) prove a tampered intent is dropped before it can mutate authoritative state. - A malicious proposal from the AI co-creator.
@maya/forge-assist'sCapabilityPolicy(capability-policy.ts:75) scans generated source foroshun.invoke("…")and rejects any artifact that touches aplatform.*namespace or invokes a capability it didn't declare — there is "no declaration that makes a realm script mint currency", and the assistant has no path around the check.
Related#
- Moremi: Realm Server & Netcode — the authoritative Rust server that hosts this sandbox, validates client intent, and replicates the composed realm.
- Thesis, Trust Boundary, and Topology — why a realm is assumed hostile and exactly which concerns are never delegated to one (the boundary the sandbox enforces).
- Sekhmet: Safety & Anti-Cheat — the platform-central malware scan, integrity, and child-safety floor that gate an artifact before Ixchel ever resolves or runs it (planned sibling).
- The orientation hub ../V7_ARCHITECTURE.md.