Files
TREK/wiki/Plugin-Publishing.md
T
Maurice 711f6e9881 feat(plugins): sandbox hardening — OS permission model, egress choke point, bound acting user, author signatures
Closes the four gaps the security review surfaced:

- OS permission model on the prod plugin child (Node --permission with
  fs-read scoped to the compiled server dir + the plugin's own code dir, no
  fs-write/child_process/worker/native). A plugin can no longer read trek.db
  or the .jwt_secret/.encryption_key files, nor shell out — the direct-fs and
  RCE escapes that bypassed the RPC layer. Opt-out via TREK_PLUGIN_PERMISSIONS=off.
- Egress guard extended from fetch to the net.Socket connect choke point, so
  node:http/https/net/tls obey the declared-egress allowlist too (no declared
  egress = no outbound). Under the permission model there is no clean escape to
  an unwrapped runtime. Kernel/network-namespace containment remains the
  container step.
- Trip reads are membership-checked against the acting user the HOST binds from
  the authenticated invocation, not an asUserId the plugin supplies; a job/onLoad
  (no user) can't read user-scoped trips.
- Optional minisign (Ed25519) author signatures verified offline, TOFU-pinned
  (migration 158). Unsigned plugins install on sha256 alone; a signed plugin
  can't silently drop its signature or swap its author key.

Server suite green (permission-model activation verified against the Koffi
reference plugin on dev1).
2026-07-04 00:31:01 +02:00

133 lines
4.6 KiB
Markdown

# Publishing a Plugin
Plugins are distributed from a static registry — the
[TREK-Plugins](https://github.com/mauriceboe/TREK-Plugins) GitHub repo. There is
no upload server and no account: you host your plugin in your own GitHub repo and
list it with a pull request.
## 1. Host your plugin
Put your plugin in a **public GitHub repo** (convention: `trek-plugin-<id>`). It
must contain a `trek-plugin.json` at the root, a built `server/index.js`, a
filled-in `README.md`, and (page/widget) a built `client/`.
## 2. Tag a release
Tag `vX.Y.Z` where `X.Y.Z` **equals** `version` in your manifest. Either attach a
`plugin.zip` release asset, or rely on the GitHub source archive at that commit.
## 3. Validate locally
```bash
npx trek-plugin validate .
```
This runs the exact checks the registry CI runs — a local pass predicts a CI
pass.
## 4. Fork the registry and open a PR
You don't have write access to the registry, so **fork
[TREK-Plugins](https://github.com/mauriceboe/TREK-Plugins)** to your own account,
add your entry there, and open a pull request from your fork back to the
registry's `main` branch.
Add one file, `registry/plugins/<id>.json`:
```jsonc
{
"id": "flight-tracker",
"name": "Flight Tracker",
"author": "Your Name",
"description": "Live flight status widget.",
"repo": "you/trek-plugin-flight-tracker",
"type": "widget",
"versions": [{
"version": "1.0.0",
"gitTag": "v1.0.0",
"commitSha": "<40-hex commit the tag points at>",
"downloadUrl": "https://github.com/you/trek-plugin-flight-tracker/releases/download/v1.0.0/plugin.zip",
"sha256": "<sha256 of that artifact>",
"minTrekVersion": "3.2.0",
"apiVersion": 1,
"nativeModules": false
}]
}
```
CI validates the entry and the artifact, and the maintainer reviews the source at
the pinned commit before merging.
## What CI enforces
**Entry:** valid schema · `id` matches the filename and is a valid slug ·
reserved namespaces (`trek-`, `official-`) blocked · your `id` is bound to your
GitHub owner on first registration (nobody can repoint it later) · homoglyph
check · the release tag exists and points at the pinned commit · manifest parity
· **the artifact's SHA-256 matches the pin** · **no native binaries** ·
`egress[]` present when `http:outbound` is declared.
**README:** must exist, have the required sections (*What it does / Screenshots /
Permissions / Setup*), contain **at least one screenshot that actually resolves**,
have real content (not the unfilled template), and explain every declared
permission.
## Provenance & integrity
- `commitSha` pins the exact source the maintainer reviewed (git tags are movable).
- `sha256` pins the exact artifact bytes TREK will run (release assets are mutable).
TREK verifies the downloaded bytes against `sha256` and refuses to install on a
mismatch. A `reviewedAt` date on your entry means a maintainer looked at that
exact commit — it is **not** an ongoing guarantee.
## Signing your releases (optional, recommended)
`sha256` proves the bytes are the ones the *registry* vouches for. An author
signature additionally proves the bytes were signed by **you** — so a compromised
registry cannot ship attacker code under your name. TREK verifies the signature
offline (Ed25519, no external service), and pins your key on first install
(trust-on-first-use): a later release signed with a different key is refused until
an admin re-trusts it.
**One-time — create a key:**
```
minisign -G # writes minisign.key (keep secret) + minisign.pub
```
Put the public key in your registry entry as `authorPublicKey` (the base64 payload
line from `minisign.pub`). It is stable across versions.
**Each release — sign the artifact:**
```
minisign -Sm plugin.zip # writes plugin.zip.minisig
```
Add the base64 signature line from `plugin.zip.minisig` to that version as
`signature`, next to its `sha256`. Example entry:
```jsonc
{
"id": "flight-tracker",
"authorPublicKey": "RWQ…base64 minisign public key…",
"versions": [{
"version": "1.2.0",
"sha256": "3b2a…",
"signature": "RUR…base64 .minisig payload…"
}]
}
```
Signing is **opt-in**: an entry without `authorPublicKey`/`signature` installs on
`sha256` alone, exactly as before. But once a plugin has shipped signed, an
*unsigned* update for it is refused — don't drop the signature between versions.
## Updating
Add a new version entry (new `version`/`gitTag`/`commitSha`/`sha256`) to your
plugin file and PR it. Instances see the update on their next registry poll;
updating is always an explicit admin action, and if a new version requests **more**
permissions the admin must re-approve.