@xenosystem/runtime-packages
v0.9.0
Published
Load a **signed XENO package at runtime**, in any host.
Readme
@xenosystem/runtime-packages
Load a signed XENO package at runtime, in any host.
XENO already delivers signed packages this way — tool@1, image@1, video@1 and now panel@1
reach users through the registry at updates.xenostudio.ai/apps/tools/, verified before a byte is
served. Bumping the version IS the delivery: no application release is needed to ship a fix.
That machinery used to live inside xeno-hub, which meant every other host that wanted it —
xeno-apps, Pixel, Motion, Canvas — had to reimplement it. A second copy of a signature-checking
loader is not something this ecosystem should own twice, so it lives here.
The two halves, and the boundary between them
| half | runs in | export |
|---|---|---|
| verification — is this package from us, and is it intact? | the host's privileged process | verifyToolPackage |
| loading — cache, contract check, module linking, style injection, cache-busting | the renderer | createRuntimePackageLoader |
🔴 The loader deliberately does not verify. A renderer-side signature check is a suggestion:
whatever the renderer concludes can be re-concluded by whoever is looking at it. The host resolves
an id to a URL only after verifyToolPackage has run on the privileged side, and a refusal comes
back as a refusal. Moving verification into the loader would look like more capability and be less
security.
Verification
🔴 Imported from a SUBPATH, and only from privileged code. The root entry is browser-safe; the
verifier imports node:crypto, so re-exporting it from the root dragged Node's crypto into every
renderer bundle — a hard build failure in one consumer, and a silently larger bundle everywhere
else. Since 0.4.0 the module graph enforces the separation the docs always claimed, and a gate
pins it.
import { verifyToolPackage } from '@xenosystem/runtime-packages/verifier'
const result = verifyToolPackage({ manifestBytes, signatureBytes, files, trustedKeys })
if (!result.ok) return refuse(result.reason)Ed25519 over the canonical tool.json; tool.json then carries a SHA-256 for every other file,
so the signature proves the manifest and the manifest proves the bundle. It refuses an unsigned
package, an untrusted key, a swapped file, a smuggled undeclared file, an unknown contract,
and an isolation this build cannot actually provide — refusing rather than running it in-process
and calling that success. Every one of those refusals is a test, and every one is mutation-checked.
Loading
const loader = createRuntimePackageLoader({
contract: 'panel@1',
resolve: (id) => host.resolveVerifiedPackage(id),
hostModules: { react: React, '@xenosystem/panel-sdk': panelSdk },
})⚠️ hostModules is the capability boundary. Everything in it is reachable by any package this
loader loads, and nothing else is. It differs per contract on purpose: a tool@1 package is handed
the tool kit, a panel@1 package the panel SDK.
Host modules — how a loaded package resolves react
A package externalises its bare imports so React stays a single copy (two copies make every hook
throw Invalid hook call). At BUILD time a bundler resolves those. At RUNTIME there is no bundler,
so the browser must resolve react itself — and with no import map there is nothing to resolve it
to.
🔴 This is how a delivery chain is green as far as INSTALL and has never rendered anything.
Signed, fetched, verified, written to disk, then Failed to resolve module specifier.
The loader publishes hostModules onto a shared global; the host serves a generated shim per
specifier and carries an inline import map:
const loader = createRuntimePackageLoader({ contract: 'panel@1', resolve, hostModules })
// in the host's privileged serving code, for <scheme>://__host/<file>
const specifier = hostSpecifierFromFileName(fileName, hostModules)
return hostModuleShimSource(specifier, loader.hostModuleExportNames[specifier])
// generated at build time into the document (it must be INLINE and before the first module load)
hostImportMap(Object.keys(hostModules), 'xeno-panel-pkg')⚠️ The global key is deliberately the same one xeno-hub already uses, so a package built for Hub
resolves in any host adopting this. Keep the shared set small — only modules that must be a single
instance. Everything else belongs vendored in the package's own bundle, which is what keeps a
package release independent of a host release.
Version arbitration
chooseRuntimePackageVersion(bundled, installed) — highest version wins, with the bundled copy as
a floor. Both simpler rules are wrong: bundled always wins was a shipped defect that left
packages on the registry nothing could ever run, and runtime always wins makes a downgrade an
attack, because a signature proves ORIGIN and never FRESHNESS.
Licence
UNLICENSED — proprietary, © XENO Corporation.
