From 5cd6e8f572d61680e4bbe146d507478ff9d75f81 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 19 Jul 2026 22:25:33 +0200 Subject: [PATCH] docs: reflect the plugin-runner security tiers - plugins: the Windows runner task runs as LocalService now, and public-registry names need --allow-public-registry - automation + SDK README: connect()'s zero-config credential is the scoped plugin token; pairing administration and hook registration need an explicit PUNKTFUNK_MGMT_TOKEN opt-in Co-Authored-By: Claude Fable 5 --- docs-site/content/docs/automation.md | 7 +++++++ docs-site/content/docs/plugins.mdx | 15 +++++++++------ sdk/README.md | 14 ++++++++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/docs-site/content/docs/automation.md b/docs-site/content/docs/automation.md index 2fe7cc49..8e87c6b8 100644 --- a/docs-site/content/docs/automation.md +++ b/docs-site/content/docs/automation.md @@ -157,6 +157,13 @@ The canonical "decide, don't just observe" pattern — approve pairing from your `POST /api/v1/native/pending/{id}/approve` when you tap yes. The full API is documented at [`/api/docs`](/api) on your host. +> A unit under the runner auto-connects with the host's **scoped plugin token**, which covers +> the everyday surface (status, library, sessions, events) but deliberately not **hook +> registration** or **pairing administration** — so a plugin defect can't admit new devices or +> install commands. A script that should administer pairing (like the approval pattern above) +> opts into the full-admin credential explicitly: set `PUNKTFUNK_MGMT_TOKEN` on the unit (e.g. +> a `systemctl --user edit punktfunk-scripting` drop-in) or pass `{ token }` to `connect()`. + ## Recipe: full controller passthrough (VirtualHere) To get a controller's *native* features on the host — DualSense gyro, touchpad, adaptive diff --git a/docs-site/content/docs/plugins.mdx b/docs-site/content/docs/plugins.mdx index af8fd616..b5e3fdce 100644 --- a/docs-site/content/docs/plugins.mdx +++ b/docs-site/content/docs/plugins.mdx @@ -31,8 +31,9 @@ punktfunk-host plugins enable # turn the runner on (once) Run these from an **elevated** PowerShell — right-click **PowerShell** → **Run as administrator**. -The plugins directory lives under `%ProgramData%\punktfunk`, which is admin-owned, and the runner -task runs as SYSTEM. +The plugins directory lives under `%ProgramData%\punktfunk`, which is admin-owned. The runner task +itself runs as the low-privilege `NT AUTHORITY\LocalService` account — `plugins enable` sets that +up (including read access to the runner's scoped API token). ```powershell punktfunk-host plugins add playnite # or: rom-manager @@ -62,11 +63,13 @@ The runner is **opt-in**: `plugins add` installs, `plugins enable` turns it on. | `punktfunk-host plugins disable` | Stop + disable the runner. | | `punktfunk-host plugins status` | Is the runner enabled and running? | -A bare name resolves to the first-party package — `playnite` installs `@punktfunk/plugin-playnite`. -A scoped (`@scope/pkg`) or `punktfunk-plugin-*` name is used verbatim, so third-party plugins work -the same way. +A bare name resolves to the first-party package — `playnite` installs `@punktfunk/plugin-playnite`, +always from Punktfunk's own package registry. Any other name (`punktfunk-plugin-*`, a foreign +`@scope/pkg`) would install from the **public npm registry** and is refused unless you add +`--allow-public-registry` — a guard against typos and look-alike packages pulling untrusted code +onto your host. -> Plugins are operator-installed code that runs as the host user — they can launch games and run +> Plugins are operator-installed code with operator privileges — they can launch games and run > commands. Install only plugins you trust, from a registry you control. ## ROM Manager diff --git a/sdk/README.md b/sdk/README.md index c6d1727e..482b650b 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -106,7 +106,7 @@ Plus a real-world recipe: | What | Source | |---|---| | URL | `{ url }` → `PUNKTFUNK_MGMT_URL` → `https://127.0.0.1:47990` | -| Token | `{ token }` → `PUNKTFUNK_MGMT_TOKEN` → `/mgmt-token` | +| Token | `{ token }` → `PUNKTFUNK_MGMT_TOKEN` → `PUNKTFUNK_PLUGIN_TOKEN` → `/plugin-token` → `/mgmt-token` | | TLS pin | `{ ca }` → `PUNKTFUNK_MGMT_CA` (path) → `/cert.pem` | `` is `~/.config/punktfunk` (Linux/macOS) or `%ProgramData%\punktfunk` (Windows) — @@ -115,8 +115,13 @@ the host's self-signed identity cert (chain-verified; the hostname check is waiv is deliberately CN-only, native clients pin its fingerprint). Bun and Node are first-class; other runtimes fall back to system trust (point your runtime's CA option at `cert.pem`). -The bearer token is the host's **admin** credential and is honored from loopback only — run -scripts on the host box (or through an SSH tunnel). +The zero-config default is the host's **scoped plugin token** (`plugin-token`): the everyday +surface — status, library, sessions, events, the plugin UI lease — but deliberately **not** hook +registration or pairing administration, so a plugin defect can't install commands or admit +devices. A script that needs the full admin surface opts in explicitly with +`PUNKTFUNK_MGMT_TOKEN` or `{ token }` (`mgmt-token` remains the fallback on hosts that predate +the plugin token). Both tokens are honored from loopback only — run scripts on the host box (or +through an SSH tunnel). ## Events @@ -260,7 +265,8 @@ WantedBy=default.target Windows Task Scheduler: a task triggered *At log on* running `bun C:\Users\me\punktfunk-scripts\myscript.ts` (the SDK reads -`%ProgramData%\punktfunk\mgmt-token` — run the task as an account that can). +`%ProgramData%\punktfunk\plugin-token` — run the task as an account that can; the managed +runner's `plugins enable` grants its LocalService principal exactly that read). ## Compatibility