@lifeaitools/clauth 1.30.26 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,10 +11,12 @@ import {
11
11
  listPlugins,
12
12
  listSurfaces,
13
13
  reconcileSurfaceHealth,
14
+ registerPlugin,
14
15
  runPluginAction,
15
16
  runSurfaceAction,
16
17
  removeTunnelRoute,
17
18
  setPluginEnabled,
19
+ shellQuote,
18
20
  supervisorHealth,
19
21
  validatePluginManifest,
20
22
  } from "./supervisor-registry.js";
@@ -50,6 +52,15 @@ function writePlugin(root, source, id, manifest) {
50
52
  return dir;
51
53
  }
52
54
 
55
+ // A scoped npm package installs at <root>/@scope/pkg-name/clauth-plugin.json —
56
+ // two levels deep, mirroring node_modules/@lifeaitools/<pkg>/.
57
+ function writeScopedPlugin(root, source, scope, id, manifest) {
58
+ const dir = path.join(root, source, scope, id);
59
+ fs.mkdirSync(dir, { recursive: true });
60
+ fs.writeFileSync(path.join(dir, "clauth-plugin.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
61
+ return dir;
62
+ }
63
+
53
64
  function baseManifest(id, overrides = {}) {
54
65
  return {
55
66
  schema: "lifeai.plugin.v1",
@@ -94,6 +105,32 @@ test("validatePluginManifest accepts empty test command arrays from the v1 templ
94
105
  assert.equal(plugin.test.port, "auto");
95
106
  });
96
107
 
108
+ test("shellQuote quotes a whitespace-bearing value only when shell is active, and is idempotent", () => {
109
+ assert.equal(shellQuote("pm2", true), "pm2");
110
+ assert.equal(shellQuote("C:/Program Files/nodejs/node.exe", true), '"C:/Program Files/nodejs/node.exe"');
111
+ assert.equal(shellQuote("C:/Program Files/nodejs/node.exe", false), "C:/Program Files/nodejs/node.exe");
112
+ assert.equal(shellQuote("one two", false), "one two");
113
+ assert.equal(shellQuote('"already quoted value"', true), '"already quoted value"');
114
+ });
115
+
116
+ test("validatePluginManifest rejects shell metacharacters in command ARGS, not just command[0]", () => {
117
+ // The [;&|<>] guard on command[0] alone is defeated by moving the payload
118
+ // one index right — args must be validated the same way (verified live
119
+ // during code review: spawnSync(['cmd','/c','echo','A & echo INJECTED'],
120
+ // {shell:true}) executed a second command from inside one arg string).
121
+ assert.throws(() => validatePluginManifest(baseManifest("inject", {
122
+ surfaces: [{ id: "primary", lifecycle_owner: "clauth", restart: ["pm2", "start svc & calc.exe"] }],
123
+ })), /shell syntax/);
124
+ assert.throws(() => validatePluginManifest(baseManifest("inject2", {
125
+ surfaces: [{ id: "primary", lifecycle_owner: "clauth", restart: ["pm2", "restart", "name; rm -rf /"] }],
126
+ })), /shell syntax/);
127
+ // A legitimate arg with no shell metacharacters still passes.
128
+ const ok = validatePluginManifest(baseManifest("legit", {
129
+ surfaces: [{ id: "primary", lifecycle_owner: "clauth", restart: ["pm2", "restart", "my-app-name"] }],
130
+ }), "clauth-plugin.json");
131
+ assert.deepEqual(ok.surfaces[0].restart, ["pm2", "restart", "my-app-name"]);
132
+ });
133
+
97
134
  test("validatePluginManifest rejects invalid manifests and non-local health URLs", () => {
98
135
  assert.throws(() => validatePluginManifest({ ...baseManifest("bad"), schema: "bad" }), /schema/);
99
136
  assert.throws(() => validatePluginManifest(baseManifest("bad", {
@@ -126,6 +163,50 @@ test("discovery merges managed and user roots without silent managed-id shadowin
126
163
  assert.match(invalid.error, /shadow/);
127
164
  }));
128
165
 
166
+ test("discovery finds both a scoped npm-package layout and an unscoped layout from one root", () => withTempSupervisor((root) => {
167
+ const managed = path.join(root, "managed");
168
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
169
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
170
+ // Unscoped: managed/plain-plugin/clauth-plugin.json (one level — existing behavior)
171
+ writePlugin(root, "managed", "plain-plugin", baseManifest("plain-plugin"));
172
+ // Scoped: managed/@lifeaitools/fs-mcp/clauth-plugin.json (two levels — the WP-1 fix)
173
+ writeScopedPlugin(root, "managed", "@lifeaitools", "fs-mcp", baseManifest("fs-mcp"));
174
+
175
+ const result = discoverPlugins();
176
+ const plain = result.plugins.find((plugin) => plugin.id === "plain-plugin");
177
+ const scoped = result.plugins.find((plugin) => plugin.id === "fs-mcp");
178
+ assert.ok(plain, "unscoped layout must still be discovered");
179
+ assert.equal(plain.state, "awaiting_enable");
180
+ assert.ok(scoped, "scoped @scope/pkg layout must now be discovered");
181
+ assert.equal(scoped.state, "awaiting_enable");
182
+ assert.equal(scoped.manifest_hash?.length > 0, true);
183
+ }));
184
+
185
+ test("discovery skips a malformed manifest without aborting the rest of the scan", () => withTempSupervisor((root) => {
186
+ const managed = path.join(root, "managed");
187
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
188
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
189
+ writePlugin(root, "managed", "good-plugin", baseManifest("good-plugin"));
190
+ const brokenDir = path.join(managed, "broken-plugin");
191
+ fs.mkdirSync(brokenDir, { recursive: true });
192
+ fs.writeFileSync(path.join(brokenDir, "clauth-plugin.json"), "{ this is not valid json", "utf8");
193
+ // Also prove a malformed SCOPED manifest doesn't abort the scoped-dir descent either.
194
+ const brokenScopedDir = path.join(managed, "@lifeaitools", "broken-scoped");
195
+ fs.mkdirSync(brokenScopedDir, { recursive: true });
196
+ fs.writeFileSync(path.join(brokenScopedDir, "clauth-plugin.json"), "{ also not valid json", "utf8");
197
+ writeScopedPlugin(root, "managed", "@lifeaitools", "good-scoped", baseManifest("good-scoped"));
198
+
199
+ const result = discoverPlugins();
200
+ const good = result.plugins.find((plugin) => plugin.id === "good-plugin");
201
+ const broken = result.plugins.find((plugin) => plugin.id === "broken-plugin");
202
+ const goodScoped = result.plugins.find((plugin) => plugin.id === "good-scoped");
203
+ const brokenScoped = result.plugins.find((plugin) => plugin.id === "broken-scoped");
204
+ assert.equal(good.state, "awaiting_enable");
205
+ assert.equal(broken.state, "manifest_invalid");
206
+ assert.equal(goodScoped.state, "awaiting_enable");
207
+ assert.equal(brokenScoped.state, "manifest_invalid");
208
+ }));
209
+
129
210
  test("trusted managed core plugins auto-enable while user plugins remain opt-in", () => withTempSupervisor((root) => {
130
211
  const managed = path.join(root, "managed");
131
212
  const user = path.join(root, "user");
@@ -378,6 +459,76 @@ test("supervisor log DTO strips raw operation payloads", () => {
378
459
  assert.equal(json.includes("--token"), false);
379
460
  });
380
461
 
462
+ test("registerPlugin validates, writes into the managed root, and discovers the plugin — idempotent on re-register", () => withTempSupervisor((root) => {
463
+ const managed = path.join(root, "managed");
464
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
465
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
466
+ const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-register-source-"));
467
+ const manifestPath = path.join(sourceDir, "clauth-plugin.json");
468
+ fs.writeFileSync(manifestPath, JSON.stringify(baseManifest("registered-demo", { core: true, enable_default: true })), "utf8");
469
+
470
+ const first = registerPlugin(manifestPath, "test");
471
+ assert.equal(first.resulting_state.ok, true);
472
+ assert.equal(first.resulting_state.state, "registered");
473
+ assert.equal(first.resulting_state.plugin_state, "current");
474
+ const written = fs.existsSync(path.join(managed, "registered-demo", "clauth-plugin.json"));
475
+ assert.equal(written, true);
476
+ const found = listPlugins().find((plugin) => plugin.id === "registered-demo");
477
+ assert.equal(found.enabled, true);
478
+
479
+ const second = registerPlugin(manifestPath, "test");
480
+ assert.equal(second.resulting_state.state, "unchanged", "re-registering identical content must be a no-op, not a rewrite");
481
+
482
+ fs.rmSync(sourceDir, { recursive: true, force: true });
483
+ }));
484
+
485
+ test("registerPlugin rejects a plugin id that would escape the managed-plugins root", () => withTempSupervisor((root) => {
486
+ // Code-review finding (confidence 95, live PoC): manifest.id of ".." passed
487
+ // the old id regex (dot is in the allowed character class with no
488
+ // exclusion of all-dots forms) and path.join(managedRoot, "..") wrote
489
+ // clauth-plugin.json one level ABOVE the managed root. Two independent
490
+ // fixes now close this: the id regex rejects all-dots ids, and
491
+ // registerPlugin asserts containment at the write site so a future regex
492
+ // relaxation can't reopen it.
493
+ const managed = path.join(root, "managed");
494
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
495
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
496
+ const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-register-traversal-"));
497
+ const manifestPath = path.join(sourceDir, "clauth-plugin.json");
498
+ fs.writeFileSync(manifestPath, JSON.stringify({ schema: "lifeai.plugin.v1", id: "..", version: "1.0.0", publisher: "t", documentation: { architecture: "a.md" }, surfaces: [] }), "utf8");
499
+
500
+ const receipt = registerPlugin(manifestPath, "test");
501
+ assert.equal(receipt.resulting_state.ok, false);
502
+ assert.equal(receipt.resulting_state.state, "manifest_invalid");
503
+ const escapedPath = path.join(managed, "..", "clauth-plugin.json");
504
+ assert.equal(fs.existsSync(escapedPath), false, "must never write outside the managed-plugins root");
505
+
506
+ fs.rmSync(sourceDir, { recursive: true, force: true });
507
+ }));
508
+
509
+ test("validatePluginManifest rejects an all-dots plugin or surface id", () => {
510
+ assert.throws(() => validatePluginManifest(baseManifest("..", {}), "clauth-plugin.json"), /may not be all dots/);
511
+ assert.throws(() => validatePluginManifest(baseManifest("valid-id", {
512
+ surfaces: [{ id: ".", lifecycle_owner: "clauth" }],
513
+ }), "clauth-plugin.json"), /may not be all dots/);
514
+ });
515
+
516
+ test("registerPlugin rejects an invalid manifest without writing anything", () => withTempSupervisor((root) => {
517
+ const managed = path.join(root, "managed");
518
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
519
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
520
+ const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-register-bad-"));
521
+ const manifestPath = path.join(sourceDir, "clauth-plugin.json");
522
+ fs.writeFileSync(manifestPath, JSON.stringify({ schema: "wrong", id: "bad" }), "utf8");
523
+
524
+ const receipt = registerPlugin(manifestPath, "test");
525
+ assert.equal(receipt.resulting_state.ok, false);
526
+ assert.equal(receipt.resulting_state.state, "manifest_invalid");
527
+ assert.equal(fs.existsSync(path.join(managed, "bad")), false);
528
+
529
+ fs.rmSync(sourceDir, { recursive: true, force: true });
530
+ }));
531
+
381
532
  test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
382
533
  process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
383
534
  process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");