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>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
// Connection/config resolution helpers. `pluginStateDir` is the writable location a supervised
|
||||
// plugin persists into — the one dir the de-privileged Windows runner may write.
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import * as path from "node:path";
|
||||
import { pluginStateDir } from "../src/config.js";
|
||||
|
||||
describe("pluginStateDir", () => {
|
||||
let saved: string | undefined;
|
||||
beforeEach(() => {
|
||||
saved = process.env.PUNKTFUNK_CONFIG_DIR;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (saved === undefined) delete process.env.PUNKTFUNK_CONFIG_DIR;
|
||||
else process.env.PUNKTFUNK_CONFIG_DIR = saved;
|
||||
});
|
||||
|
||||
test("resolves <config_dir>/plugin-state[/name] and honors the config-dir override", () => {
|
||||
process.env.PUNKTFUNK_CONFIG_DIR = path.join("/tmp", "pf-cfg");
|
||||
expect(pluginStateDir()).toBe(path.join("/tmp", "pf-cfg", "plugin-state"));
|
||||
expect(pluginStateDir("rom-manager")).toBe(
|
||||
path.join("/tmp", "pf-cfg", "plugin-state", "rom-manager"),
|
||||
);
|
||||
});
|
||||
|
||||
test("the per-plugin dir is nested under the shared root", () => {
|
||||
process.env.PUNKTFUNK_CONFIG_DIR = path.join("/tmp", "pf-cfg2");
|
||||
expect(pluginStateDir("x").startsWith(pluginStateDir())).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user