5.9 KiB
Extension Host
Status: signed-release Host and local Edge routing integration implemented.
The Edge supports in-process, prefer-host, and host-only execution modes.
The production default remains in-process; the Host is not installed or
enabled on production host.
Process Model
One host process:
- receives an explicit verified release directory;
- re-verifies installed release content, record, and publisher signature;
- checks the publisher against an explicit OpenSSH allowed-signers file;
- re-validates manifest and source policy;
- loads one plugin entrypoint;
- freezes declared command registration;
- reads one bounded JSON request from stdin;
- invokes one command;
- exchanges bounded service requests with the Kernel when declared;
- writes one final JSON response to stdout;
- exits.
Plugin stdout is redirected away from the protocol stream. Plugin stdout and stderr are bounded before reaching the parent pipe.
Request
{
"op": "invoke_command",
"command": "/hello",
"arguments": "world",
"event": {
"version": 1,
"event_id": "event-1",
"observed_at": "2026-10-14T10:00:00+08:00",
"sender": "JWeBot",
"text": "@JWeBot /hello world",
"confidence": 1.0,
"sender_confidence": 1.0,
"body_confidence": 1.0,
"side": "incoming",
"mention_bot": true,
"group_fingerprint": "opaque-fingerprint",
"source": "rapidocr-local"
}
}
The request contains no delivery target. The group fingerprint remains an opaque safety binding for the Kernel.
Response
{
"status": "ok",
"api_version": "1",
"plugin_id": "hello-command",
"result": {
"api_version": "1",
"reply": {"kind": "text", "text": "hello JWeBot world"},
"facts": [],
"metadata": {}
}
}
Errors return a short error class and do not include plugin exception text.
Declared service calls use an intermediate JSON-line exchange:
{
"status": "service_request",
"call_id": "1",
"profile": "coin",
"operation": "execute",
"payload": {"command": "/翻硬币"}
}
The parent validates the profile, performs the Kernel-owned operation, and
returns a matching service_result. Calls, message sizes, and total wall time
are bounded. An undeclared profile, mismatched call ID, malformed result, or
unavailable broker fails closed.
Context Capabilities
The prototype provides:
- manifest-bound command registration;
- declared, bounded Kernel service profiles;
- bounded fact collection;
- hashed logger messages with field names only.
The following brokers intentionally fail closed until implemented:
- storage;
- approved HTTP;
- scheduler.
No GUI sender, group selector, raw ledger, environment, shell, SSH, Docker, or arbitrary network client is supplied.
Environment Handling
Before loading extension code, the CLI removes environment variables except
LANG, LC_ALL, and TZ.
This reduces accidental secret exposure but is not a security sandbox.
The parent interpreter also sets PYTHONDONTWRITEBYTECODE=1, and the loader
disables bytecode writes while importing a plugin, so verified release
directories cannot acquire unrecorded __pycache__ files.
Required Production Isolation
Before Edge integration, the host still needs an OS-enforced launcher with:
- dedicated dynamic or unprivileged identity;
- read-only verified release directory;
- private writable state directory;
- no inherited secrets;
- no network by default;
- restricted address families;
- no new privileges and empty capability set;
- process, memory, CPU, and wall-time limits;
- parent-enforced timeout and kill;
- fixed Unix-socket or pipe protocol.
Static source checks and Python-level context restrictions do not replace these controls.
Repository templates:
deploy/systemd/system/jwebot-extension-host@.service
deploy/systemd/system/jwebot-extension-host@.socket
They specify a fixed unprivileged jwebot-ext identity, private AF_UNIX-only
network namespace, empty capabilities, read-only system and release paths,
private devices/tmp, syscall filtering, and CPU/memory/process/runtime limits.
The service refuses to start unless
/etc/jwebot-extension-host/allowed_signers is readable and passes that trust
file to the Host. No private signing key belongs on the target.
The templates are not installed or enabled. A later provisioning phase must
create the service identity, install the Host virtual environment, prepare
release ownership and public-key trust file, run systemd-analyze verify, and
test the socket protocol on the target Debian version.
Parent Client
jwebot_kernel.ExtensionHostClient starts one Host in a new process group with:
- an explicit verified release directory;
python -Iby default;- only
LANG,LC_ALL, andTZinherited; - closed unrelated file descriptors;
- bounded request/response/stderr sizes;
- a parent-enforced wall-time timeout;
- process-group kill on timeout.
The parent discards Host stderr from protocol results and returns generic transport errors. It services only reviewed broker callbacks supplied by the Kernel.
At Edge startup, signed manifests are loaded from <state-root>/<plugin-id>/current,
checked against the fixed ownership inventory, registered into one catalog,
and frozen. Host health responses must exactly match the signed author,
commands, service profiles, plugin ID, and API version.
prefer-host may use the in-process adapter only when a signed release is
missing or its Host fails startup health. Once a Host invocation starts, the
Kernel never replays that command through another path because the broker may
already have committed state. host-only requires every reviewed plugin to be
signed, installed, and healthy before Edge startup succeeds.
jwebot_kernel.UnixExtensionHostClient matches the systemd socket template. It
uses the same bounded JSON-line service exchange and applies one end-to-end
socket deadline. Socket absence, timeout, malformed JSON, service protocol
violations, and Host error status all fail closed.