Commit Graph

4 Commits

Author SHA1 Message Date
enricobuehler e9a4c4a601 feat(security): add a user-writable plugin ingest inbox for cross-account data
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 1m1s
apple / swift (push) Successful in 1m18s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
ci / bench (push) Successful in 7m12s
apple / screenshots (push) Successful in 6m21s
deb / build-publish (push) Successful in 11m27s
deb / build-publish-host (push) Successful in 12m0s
arch / build-publish (push) Successful in 13m2s
android / android (push) Successful in 15m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m38s
ci / rust (push) Successful in 20m35s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m2s
docker / deploy-docs (push) Successful in 27s
windows-host / package (push) Successful in 15m19s
The LocalService runner can't traverse the interactive user's profile the way
the old SYSTEM runner could — so a plugin can no longer read a file an app
running as *you* produced (the acute case: the Playnite exporter's library
JSON under %APPDATA%). Confirmed on-glass: as LocalService the glob finds
nothing and the profile is un-traversable.

Add the inverse of plugin-state: <config_dir>\ingest, granted BUILTIN\Users
Modify by plugins enable (disable reverts to inherited Users:RX). An
interactive-user app drops ingest\<plugin>\… and the de-privileged runner
reads it there — the one Users-writable carve-out in the otherwise
Users-read-only tree. SDK exports pluginIngestDir(name) to resolve it; on
Linux the systemd --user runner owns the config dir so same-user producers
write there with no grant.

Accepted tradeoff: the inbox is writable by any local user (trusted-single-
user model; it feeds only a LocalService runner). Consumers must treat ingest
data as lower trust than their own state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:45:27 +02:00
enricobuehler 73ec6ed9ef feat(security): give the de-privileged runner a writable plugin-state dir
The LocalService runner cannot write anywhere under %ProgramData%\punktfunk
(the config dir is Users-read-only), so a state-writing plugin's saveCache /
config-edit / first-run mkdir all fail EPERM — proven on-glass (rom-manager
only looked fine because its state dir was pre-created by an admin run and a
0-title reconcile skipped the write).

Add the one writable grant the model was missing, keeping the split crisp —
code dirs RX+WA, secrets R, and now a dedicated state root RW:

- plugins enable / build-scripting.ps1: create %ProgramData%\punktfunk  plugin-state and grant LocalService (OI)(CI)(M); disable revokes. Users stay
  read-only, so another non-admin still can't tamper with a plugin's launch
  templates.
- SDK: export pluginStateDir(name) -> <config_dir>/plugin-state/<name>. Same
  path on Linux (the systemd --user runner owns the config dir, writable with
  no grant), so plugins use one branch-free helper.

Plugins must persist under pluginStateDir(), not straight under the config dir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:45:27 +02:00
enricobuehler 84c47cd0a7 feat(security): run the Windows plugin runner as LocalService, not SYSTEM
The PunktfunkScripting scheduled task ran operator-installed plugin code
as SYSTEM with -RunLevel Highest — any plugin defect was a full
compromise of the box. The principal is now NT AUTHORITY\LocalService
(minimal privileges, no password to manage, exists at boot, loopback
networking works), registered without RunLevel:

- installer: New-ScheduledTaskPrincipal -UserId 'LocalService'
- plugins enable: converges the principal idempotently (migrating tasks
  an older installer or a dev box registered as SYSTEM) BEFORE starting,
  then grants LocalService read — via icacls, full-System32-path — on
  exactly the two SYSTEM/Admins-DACL'd files the runner's connect()
  needs: the scoped plugin-token and the TLS-pin cert.pem. Never
  mgmt-token. plugins disable revokes the grants; plugins status now
  prints the task principal so the migration is verifiable.
- build-scripting.ps1 mirrors the convergence + grants on dev deploys.

The usage text also mentions the new --allow-public-registry gate that
lands with the supply-chain commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:45:27 +02:00
enricobuehler ecec7cc062 feat(deploy/windows): always deploy the plugin/script runner
`punktfunk-host plugins add/remove/list` forward to the bun runner, which on
Windows is resolved relative to the running exe (<exe-dir>\bun\bun.exe +
<exe-dir>\scripting\runner-cli.js). Only the installer ever laid that payload
down, so on a deploy-host.ps1 dev box — where the service runs out of
target\release — both paths are absent and the CLI bails with "the plugin
runner isn't installed". deploy-all.ps1 was host + web console only.

Add build-scripting.ps1: it mirrors CI (bun install --frozen-lockfile
--ignore-scripts + bun build src/runner-cli.ts --target=bun, gated on the same
`attempt=` sentinel that proves the dynamic plugin import stayed a runtime
import), then lays the bundle + scripting-run.cmd + the shared bun next to every
host exe it finds — the built one and whatever the PunktfunkHost service runs —
so it is correct on a dev checkout or an installed {app}. It stops a running
PunktfunkScripting first (a live runner holds bun.exe open) and does not
silently enable the opt-in task (-EnableTask to do so).

deploy-all.ps1 is now host -> web console -> runner, always, so the host binary
and the runner bundle never drift apart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:45:27 +02:00