@lifeaitools/clauth 1.30.8 → 1.30.10
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.
package/cli/commands/serve.js
CHANGED
|
@@ -572,16 +572,25 @@ 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 isLoopbackAddress(remote) {
|
|
587
|
+
return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function supervisorRequiresWriteToken(port = getSupervisorPort(), env = process.env) {
|
|
591
|
+
if (port !== getSupervisorPort()) return true;
|
|
592
|
+
return env.CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN === "1";
|
|
593
|
+
}
|
|
585
594
|
|
|
586
595
|
// ── PID helpers ──────────────────────────────────────────────
|
|
587
596
|
function readPid() {
|
|
@@ -3177,14 +3186,16 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3177
3186
|
|
|
3178
3187
|
function hasSupervisorWrite(req) {
|
|
3179
3188
|
if (validateWriteToken(req, writeSession)) return true;
|
|
3180
|
-
|
|
3189
|
+
if (!isSupervisorPort) return false;
|
|
3190
|
+
if (!isLoopbackAddress(req.socket?.remoteAddress)) return false;
|
|
3191
|
+
return supervisorTestNoToken || !supervisorRequiresWriteToken(port);
|
|
3181
3192
|
}
|
|
3182
3193
|
|
|
3183
3194
|
function rejectSupervisorWrite(res) {
|
|
3184
3195
|
res.writeHead(403, { "Content-Type": "application/json", ...CORS });
|
|
3185
3196
|
return res.end(JSON.stringify({
|
|
3186
3197
|
error: "write_token_required",
|
|
3187
|
-
hint: "Unlock clauth writes, or set CLAUTH_SUPERVISOR_TEST_NO_TOKEN=1 for temporary localhost-only supervisor tests.",
|
|
3198
|
+
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
3199
|
}));
|
|
3189
3200
|
}
|
|
3190
3201
|
|
|
@@ -3605,9 +3616,9 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3605
3616
|
}
|
|
3606
3617
|
}
|
|
3607
3618
|
|
|
3608
|
-
const server = http.createServer(async (req, res) => {
|
|
3609
|
-
const remote = req.socket.remoteAddress;
|
|
3610
|
-
const isLocal = remote
|
|
3619
|
+
const server = http.createServer(async (req, res) => {
|
|
3620
|
+
const remote = req.socket.remoteAddress;
|
|
3621
|
+
const isLocal = isLoopbackAddress(remote);
|
|
3611
3622
|
|
|
3612
3623
|
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
3613
3624
|
const reqPath = url.pathname;
|
|
@@ -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 { isLoopbackAddress, 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,38 @@ 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
|
+
assert.equal(isLoopbackAddress("127.0.0.1"), true);
|
|
185
|
+
assert.equal(isLoopbackAddress("::1"), true);
|
|
186
|
+
assert.equal(isLoopbackAddress("::ffff:127.0.0.1"), true);
|
|
187
|
+
assert.equal(isLoopbackAddress("192.168.1.25"), false);
|
|
188
|
+
});
|
|
189
|
+
|
|
157
190
|
test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
|
|
158
191
|
process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
|
|
159
192
|
process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|