@lifeaitools/clauth 1.30.8 → 1.30.9

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.
@@ -572,16 +572,21 @@ function makeWriteToken() {
572
572
  };
573
573
  }
574
574
 
575
- function validateWriteToken(req, writeSession) {
576
- if (!writeSession?.token || Date.now() > writeSession.expiresAt) return false;
577
- const header = req.headers["x-clauth-write-token"] || req.headers.authorization;
578
- const token = Array.isArray(header) ? header[0] : header;
579
- if (!token) return false;
575
+ function validateWriteToken(req, writeSession) {
576
+ if (!writeSession?.token || Date.now() > writeSession.expiresAt) return false;
577
+ const header = req.headers["x-clauth-write-token"] || req.headers.authorization;
578
+ const token = Array.isArray(header) ? header[0] : header;
579
+ if (!token) return false;
580
580
  const supplied = String(token).startsWith("Bearer ") ? String(token).slice(7) : String(token);
581
581
  const a = Buffer.from(supplied);
582
- const b = Buffer.from(writeSession.token);
583
- return a.length === b.length && crypto.timingSafeEqual(a, b);
584
- }
582
+ const b = Buffer.from(writeSession.token);
583
+ return a.length === b.length && crypto.timingSafeEqual(a, b);
584
+ }
585
+
586
+ export function supervisorRequiresWriteToken(port = getSupervisorPort(), env = process.env) {
587
+ if (port !== getSupervisorPort()) return true;
588
+ return env.CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN === "1";
589
+ }
585
590
 
586
591
  // ── PID helpers ──────────────────────────────────────────────
587
592
  function readPid() {
@@ -3177,14 +3182,15 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3177
3182
 
3178
3183
  function hasSupervisorWrite(req) {
3179
3184
  if (validateWriteToken(req, writeSession)) return true;
3180
- return isSupervisorPort && supervisorTestNoToken;
3185
+ if (!isSupervisorPort) return false;
3186
+ return supervisorTestNoToken || !supervisorRequiresWriteToken(port);
3181
3187
  }
3182
3188
 
3183
3189
  function rejectSupervisorWrite(res) {
3184
3190
  res.writeHead(403, { "Content-Type": "application/json", ...CORS });
3185
3191
  return res.end(JSON.stringify({
3186
3192
  error: "write_token_required",
3187
- hint: "Unlock clauth writes, or set CLAUTH_SUPERVISOR_TEST_NO_TOKEN=1 for temporary localhost-only supervisor tests.",
3193
+ hint: "Unlock clauth writes, unset CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN for localhost supervisor operations, or set CLAUTH_SUPERVISOR_TEST_NO_TOKEN=1 for temporary localhost-only supervisor tests.",
3188
3194
  }));
3189
3195
  }
3190
3196
 
@@ -372,10 +372,22 @@ export function runSurfaceAction(id, action, actor = "localhost") {
372
372
  const port = surface.port === "auto" || !surface.port ? 0 : surface.port;
373
373
  return operation(action, { surface_id: id }, surface, { ok: true, state: "candidate_testing", port, private: true, public_route: false, evidence: ["candidate surfaces bind localhost only"] }, actor);
374
374
  }
375
- const command = action === "stop" ? surface.stop : action === "start" ? surface.start : surface.restart;
376
375
  if (surface.lifecycle_owner === "plugin") {
377
376
  return operation(action, { surface_id: id }, surface, { ok: true, state: "delegated_to_plugin_adapter", evidence: ["plugin lifecycle owner retained"] }, actor);
378
377
  }
378
+ if (action === "promote" || action === "rollback") {
379
+ return operation(action, { surface_id: id }, surface, {
380
+ ok: false,
381
+ state: "unsupported_surface_action",
382
+ reason: `${action}_is_plugin_candidate_lifecycle`,
383
+ evidence: ["surface action did not execute a process command"],
384
+ }, actor);
385
+ }
386
+ let command = null;
387
+ if (action === "stop") command = surface.stop;
388
+ else if (action === "start") command = surface.start;
389
+ else if (action === "restart") command = surface.restart;
390
+ else if (action === "reconcile") command = surface.enabled === false ? surface.stop : (surface.restart || surface.start);
379
391
  if (!command || command.length === 0) {
380
392
  return operation(action, { surface_id: id }, surface, { ok: false, state: "command_missing" }, actor);
381
393
  }
@@ -17,6 +17,7 @@ import {
17
17
  supervisorHealth,
18
18
  validatePluginManifest,
19
19
  } from "./supervisor-registry.js";
20
+ import { supervisorRequiresWriteToken } from "./commands/serve.js";
20
21
 
21
22
  function withTempSupervisor(fn) {
22
23
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-"));
@@ -154,6 +155,34 @@ test("surface actions use dedicated clauth PM2 home and keep CodeFlow observe-on
154
155
  assert.equal(supervisorHealth().surfaces, 2);
155
156
  }));
156
157
 
158
+ test("surface promote and rollback never fall through to restart commands", () => withTempSupervisor((root) => {
159
+ const managed = path.join(root, "managed");
160
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
161
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
162
+ writePlugin(root, "managed", "demo", baseManifest("demo", {
163
+ surfaces: [{
164
+ id: "demo-surface",
165
+ name: "Demo surface",
166
+ health: "http://127.0.0.1:3333/health",
167
+ restart: [process.execPath, "--version"],
168
+ }],
169
+ }));
170
+ discoverPlugins();
171
+
172
+ for (const action of ["promote", "rollback"]) {
173
+ const receipt = runSurfaceAction("demo:demo-surface", action);
174
+ assert.equal(receipt.resulting_state.ok, false);
175
+ assert.equal(receipt.resulting_state.state, "unsupported_surface_action");
176
+ assert.equal(receipt.resulting_state.evidence[0], "surface action did not execute a process command");
177
+ }
178
+ }));
179
+
180
+ test("supervisor write-token policy is temporarily relaxed only for the localhost supervisor port", () => {
181
+ assert.equal(supervisorRequiresWriteToken(52439, {}), false);
182
+ assert.equal(supervisorRequiresWriteToken(52439, { CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN: "1" }), true);
183
+ assert.equal(supervisorRequiresWriteToken(52437, {}), true);
184
+ });
185
+
157
186
  test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
158
187
  process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
159
188
  process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.30.8",
3
+ "version": "1.30.9",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
Binary file
Binary file