Plugins Capability-gated plugins, signing, WASM, and Sideband Python. Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time. Manage them from Settings -> Plugins. Disable every packaged plugin at startup with --disable-plugins or MESHCHAT_DISABLE_PLUGINS=true. What plugins can do# Add a row on the Tools page Add an item in the main Navigation sidebar React to mesh events (announces, RNS link traffic) Call narrowly declared backend managers (path table, debug log, bug reports, RNS links) Keep a private key-value store (storage: isolated) Optionally fetch clearnet HTTP (network: fetch), still subject to Privacy mode Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below). Runtimes# Runtime Where it runs Trust level Frontend JS Browser Web Worker Medium. Sandboxed worker, capability grants Backend WASM wasmtime on the server Medium. Fuel-metered, capability-gated host Backend Python In-process with MeshChatX High. Permission-checked in-process host Sideband *.py In-process, flat files Highest. Opt-in danger switch, full host access A packaged plugin can ship frontend only, backend only, or both. Install flow# Pick ZIP or .wasm file in Settings -> Plugins | --> Preview (permissions, URLs, signature, findings) | --> You grant or deny each capability | --> Optional: trust a valid signer | --> Install + integrity hash stored | --> Enable | --> Frontend Worker loads (if present) --> Backend WASM / Python activates (if present) Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not. After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered. Bundled example: Bug Reports# com.meshchatx.mcx-bugs ships with MeshChatX. It adds a Bug Reports tool for sending redacted debug logs to an mcx-bugs-v1 collector, or running a collector yourself. Layout: mcx-bugs/ plugin.json frontend/main.js backend/main.py locales/en.json Use it as the reference package when building your own. Manifest (plugin.json)# Every packaged plugin needs a root plugin.json. { "id": "com.example.my-plugin", "version": "1.0.0", "apiVersion": 1, "name": "My Plugin", "description": "Adds a custom tool.", "frontend": { "entry": "frontend/main.js", "type": "js" }, "backend": { "entry": "backend/main.py", "type": "python" }, "i18n": { "directory": "locales", "defaultLocale": "en" }, "contributes": { "navItems": [ { "id": "my-plugin", "route": { "name": "plugin-my-plugin" }, "icon": "puzzle", "labelKey": "nav" } ], "toolsPageEntries": [ { "name": "my-plugin", "route": { "name": "plugin-my-plugin" }, "icon": "puzzle", "titleKey": "title", "descriptionKey": "description" } ] }, "permissions": { "hooks": ["announce.received"], "managers": ["destinationPath.read"], "storage": "isolated", "network": "none" } } Notes: id is reverse-DNS style and must stay stable across versions apiVersion is currently 1 Plugin strings live in the plugin bundle (locales/{locale}.json), not core en.json contributes wires UI slots through the frontend registries Permissions# Nothing is available unless it is declared in the manifest and granted in the install dialog. Hooks# Hook When it fires announce.received A Reticulum announce arrives rns.link.event Generic RNS Link traffic (packet_received, link_closed) Hook events reach the UI as WebSocket plugin.event frames, then into the plugin Worker. Managers# Manager Purpose destinationPath.read Read the Reticulum path table debugLog.read Read redacted debug logs bugReport.* Bug report / collector APIs rnsLink.open Open or reuse an RNS link rnsLink.identify Identify on a cached link rnsLink.request Request/response on a link rnsLink.send Send a raw link packet rnsLink.close Tear down a cached link Call managers from a plugin with POST /api/v1/plugins/{id}/invoke and method: "callManager". Details for the link transport are in RNS Link API. Storage and network# Permission Effect storage: isolated Private key-value store in the MeshChatX database storage: none No plugin storage network: fetch Outbound HTTP allowed (still blocked by Privacy mode when that is on) network: none No clearnet fetch Install preview also scans plugin files for external http:// / https:// URLs and shows them before you grant network access. How a frontend plugin runs# Settings enable plugin | --> PluginHost loads /api/v1/plugins | --> Fetch frontend entry as text | --> Spawn pluginWorker.js (module Worker) | --> Register nav / tools contributions | --> Subscribe to plugin.event on /ws (if hooks granted) | --> Worker may invoke backend via /api/v1/plugins/{id}/invoke The Worker talks to the host with typed messages (init, event, request). The host never gives the Worker a raw privileged API. How a backend plugin runs# Enable plugin | +--> type: wasm --> load into wasmtime, fuel + host caps | +--> type: python --> import entry, call activate(host) | --> Hooks fan out from PluginManager | --> invoke(method, args) for RPC from the UI Worker Python host surface (permission-checked): host.log(message) host.call_manager(capability, args) host.storage_get(key) / host.storage_set(key, value) host.network_fetch_allowed() Packaging and signing# Distribute as: ZIP with plugin.json and assets WASM bundle (single .wasm with embedded manifest / files / optional signature) Signature file for ZIP/dir packages: meshchatx.plugin.rsg WASM custom sections: meshchatx.plugin --> embedded plugin.json meshchatx.files --> embedded text assets meshchatx.signature --> RSG over payload without this section Canonical ZIP signing uses sorted paths and fixed 1980-01-01 mtimes. The signature file itself is excluded from the signed payload. Sign and verify with: python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity <rnid> python3 scripts/sign-plugin.py verify-dir ./my-plugin python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity <rnid> python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity <rnid> python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity <rnid> Trust status in the UI: No .rsg present --> Unsigned (install allowed) Valid .rsg, signer unknown --> Signed (you can add to Trusted Publishers) Valid .rsg, signer in Trusted Publishers --> Trusted Broken / mismatched .rsg --> Invalid (install blocked) Sideband-compatible plugins# Legacy Sideband-style flat *.py files are separate from packaged ZIP/WASM plugins. Settings -> Plugins -> Sideband | --> Confirm danger prompt | --> Set directory of *.py files | --> Optional filename.py.rsg next to each script | --> Reload These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory. Operator tips# Prefer signed packages from publishers you added yourself Deny network: fetch unless the plugin needs clearnet HTTP Prefer WASM backends over Python when you can Use --disable-plugins when diagnosing weird UI or backend behaviour Treat Sideband plugins like running arbitrary local scripts See also# Tools and utilities for the Tools page and contribution overview RNS Link API for rnsLink.* and rns.link.event Architecture and design for the plugin runtime overview Identities, privacy, and security for signing and Privacy mode