@lifeaitools/clauth 2.0.1 → 2.0.3
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.
|
@@ -167,7 +167,14 @@ function normalizeCommand(command, field) {
|
|
|
167
167
|
const [cmd, ...args] = command;
|
|
168
168
|
if (typeof cmd !== "string" || !cmd.trim()) throw new Error(`${field}[0] is required`);
|
|
169
169
|
if (/[;&|<>]/.test(cmd)) throw new Error(`${field}[0] must be an executable path/name, not shell syntax`);
|
|
170
|
-
|
|
170
|
+
// Expand the same path tokens `cwd` already gets. Until now expandPathToken()
|
|
171
|
+
// was applied ONLY to cwd, so a manifest naming a root inside a command
|
|
172
|
+
// argument shipped the literal string to the shell — dev-center's
|
|
173
|
+
// "$LIFEAI_ENV/services/restart-dev-center.ps1" reached pwsh unexpanded and
|
|
174
|
+
// exited 64. Expanding here, AFTER the shell-syntax checks on cmd and BEFORE
|
|
175
|
+
// the per-arg checks below, keeps the injection guard authoritative over the
|
|
176
|
+
// final value rather than the pre-expansion one.
|
|
177
|
+
const normalizedArgs = args.map((a) => expandPathToken(String(a)) ?? String(a));
|
|
171
178
|
// shell:true (Windows-only, see execute() in runSurfaceAction) hands each
|
|
172
179
|
// arg to cmd.exe verbatim — a shell metacharacter in an arg is exactly as
|
|
173
180
|
// exploitable as one in cmd[0]. A legitimate CLI arg for the pm2/node
|
|
@@ -192,13 +199,24 @@ export function shellQuote(value, useShell) {
|
|
|
192
199
|
function expandPathToken(value) {
|
|
193
200
|
if (!value) return null;
|
|
194
201
|
const root = process.env.REGEN_ROOT || process.env.LIFEAI_REPO_ROOT || "C:/Dev/regen-root";
|
|
202
|
+
// LIFEAI_ENV is the environment-harness checkout. A manifest that drives a
|
|
203
|
+
// service through a harness script (dev-center calls
|
|
204
|
+
// $LIFEAI_ENV/services/restart-dev-center.ps1) had no way to name it, so the
|
|
205
|
+
// literal token reached the shell and pwsh answered
|
|
206
|
+
// "not recognized as the name of a script file" with exit 64 — a restart that
|
|
207
|
+
// fails while the service stays up, which reads as a flaky action rather than
|
|
208
|
+
// an unresolved path.
|
|
209
|
+
const envRoot = process.env.LIFEAI_ENV || "C:/Dev/lifeai-env";
|
|
195
210
|
return String(value)
|
|
196
211
|
.replace(/\$\{REGEN_ROOT\}/g, root)
|
|
197
212
|
.replace(/\$REGEN_ROOT/g, root)
|
|
198
213
|
.replace(/%REGEN_ROOT%/gi, root)
|
|
199
214
|
.replace(/\$\{LIFEAI_REPO_ROOT\}/g, root)
|
|
200
215
|
.replace(/\$LIFEAI_REPO_ROOT/g, root)
|
|
201
|
-
.replace(/%LIFEAI_REPO_ROOT%/gi, root)
|
|
216
|
+
.replace(/%LIFEAI_REPO_ROOT%/gi, root)
|
|
217
|
+
.replace(/\$\{LIFEAI_ENV\}/g, envRoot)
|
|
218
|
+
.replace(/\$LIFEAI_ENV/g, envRoot)
|
|
219
|
+
.replace(/%LIFEAI_ENV%/gi, envRoot);
|
|
202
220
|
}
|
|
203
221
|
|
|
204
222
|
function normalizeLifecycleOwner(owner) {
|
|
@@ -482,6 +500,23 @@ export function registerPlugin(manifestPath, actor = "localhost") {
|
|
|
482
500
|
ok: false, state: "manifest_unreadable", error: error instanceof Error ? error.message : String(error),
|
|
483
501
|
}, actor);
|
|
484
502
|
}
|
|
503
|
+
// ${PACKAGE_ROOT} — "wherever this manifest actually landed" — resolved HERE
|
|
504
|
+
// and baked into the stored copy, which is the only point at which the true
|
|
505
|
+
// origin is still known. registerPlugin copies the manifest into the managed
|
|
506
|
+
// root, so by the time discoverPlugins() re-reads it, sourcePath is the
|
|
507
|
+
// managed directory and the package's real install location is gone. An npm
|
|
508
|
+
// package installs at node_modules/@scope/name/, a path nothing can hardcode.
|
|
509
|
+
//
|
|
510
|
+
// Without this a shipped manifest had to name an absolute path: rdc-skills
|
|
511
|
+
// carried cwd "C:/Dev/rdc-skills" with core+enable_default, so `npm i -g` on
|
|
512
|
+
// any other machine auto-enabled a CORE plugin pointing at a directory that
|
|
513
|
+
// does not exist there. The token, not the package, was the defect — the
|
|
514
|
+
// correct value was simply inexpressible.
|
|
515
|
+
const packageRoot = path.dirname(path.resolve(manifestPath)).replaceAll("\\", "/");
|
|
516
|
+
raw = raw
|
|
517
|
+
.replaceAll("${PACKAGE_ROOT}", packageRoot)
|
|
518
|
+
.replaceAll("$PACKAGE_ROOT", packageRoot)
|
|
519
|
+
.replace(/%PACKAGE_ROOT%/gi, packageRoot);
|
|
485
520
|
let manifest;
|
|
486
521
|
try {
|
|
487
522
|
manifest = validatePluginManifest(JSON.parse(raw), manifestPath);
|
|
@@ -887,3 +887,78 @@ test("tunnel route add and remove produce reversible operation receipts", () =>
|
|
|
887
887
|
assert.equal(removed.resulting_state.ok, true);
|
|
888
888
|
assert.equal(removed.resulting_state.state, "route_removed");
|
|
889
889
|
}));
|
|
890
|
+
|
|
891
|
+
test("command arguments expand path tokens, not just cwd", () => {
|
|
892
|
+
// Regression: expandPathToken() was applied ONLY to `cwd`, so a manifest that
|
|
893
|
+
// named a root inside a COMMAND ARGUMENT shipped the literal string to the
|
|
894
|
+
// shell. dev-center's restart is
|
|
895
|
+
// ["pwsh","-NoProfile","-File","$LIFEAI_ENV/services/restart-dev-center.ps1"]
|
|
896
|
+
// and pwsh answered "not recognized as the name of a script file" with exit
|
|
897
|
+
// 64 -- a restart that fails while the service stays up, which reads as a
|
|
898
|
+
// flaky action rather than an unresolved path.
|
|
899
|
+
const oldEnv = process.env.LIFEAI_ENV;
|
|
900
|
+
const oldRoot = process.env.REGEN_ROOT;
|
|
901
|
+
process.env.LIFEAI_ENV = "C:/tmp/env-root";
|
|
902
|
+
process.env.REGEN_ROOT = "C:/tmp/regen-root";
|
|
903
|
+
try {
|
|
904
|
+
const plugin = validatePluginManifest(baseManifest("token-expansion", {
|
|
905
|
+
surfaces: [{
|
|
906
|
+
id: "primary",
|
|
907
|
+
destination: "local/clauth/pm2",
|
|
908
|
+
lifecycle_owner: "clauth",
|
|
909
|
+
port: 3003,
|
|
910
|
+
health: "/health",
|
|
911
|
+
restart: ["pwsh", "-NoProfile", "-File", "$LIFEAI_ENV/services/restart-dev-center.ps1"],
|
|
912
|
+
start: ["node", "${REGEN_ROOT}/scripts/start.mjs"],
|
|
913
|
+
}],
|
|
914
|
+
}), "clauth-plugin.json");
|
|
915
|
+
const s = plugin.surfaces[0];
|
|
916
|
+
assert.ok(!s.restart.some((a) => a.includes("$LIFEAI_ENV")),
|
|
917
|
+
`LIFEAI_ENV left unexpanded: ${JSON.stringify(s.restart)}`);
|
|
918
|
+
assert.ok(s.restart.some((a) => a.includes("C:/tmp/env-root")),
|
|
919
|
+
`LIFEAI_ENV did not expand to its value: ${JSON.stringify(s.restart)}`);
|
|
920
|
+
assert.ok(s.start.some((a) => a.includes("C:/tmp/regen-root")),
|
|
921
|
+
`REGEN_ROOT did not expand in a command arg: ${JSON.stringify(s.start)}`);
|
|
922
|
+
} finally {
|
|
923
|
+
if (oldEnv === undefined) delete process.env.LIFEAI_ENV; else process.env.LIFEAI_ENV = oldEnv;
|
|
924
|
+
if (oldRoot === undefined) delete process.env.REGEN_ROOT; else process.env.REGEN_ROOT = oldRoot;
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
test("registerPlugin resolves ${PACKAGE_ROOT} to the manifest's real origin", () => withTempSupervisor((root) => {
|
|
929
|
+
// An npm package installs at node_modules/@scope/name/ — a path no shipped
|
|
930
|
+
// manifest can hardcode. registerPlugin COPIES the manifest into the managed
|
|
931
|
+
// root, so by the time discoverPlugins() re-reads it the true origin is gone;
|
|
932
|
+
// the token therefore has to be resolved at registration and baked in.
|
|
933
|
+
//
|
|
934
|
+
// Without it, rdc-skills shipped cwd "C:/Dev/rdc-skills" with core +
|
|
935
|
+
// enable_default, so `npm i -g` on any other machine auto-enabled a CORE
|
|
936
|
+
// plugin pointing at a directory that does not exist there.
|
|
937
|
+
const origin = path.join(root, "pretend-node-modules", "@lifeaitools", "some-mcp");
|
|
938
|
+
fs.mkdirSync(origin, { recursive: true });
|
|
939
|
+
const manifestPath = path.join(origin, "clauth-plugin.json");
|
|
940
|
+
fs.writeFileSync(manifestPath, JSON.stringify(baseManifest("package-root-demo", {
|
|
941
|
+
surfaces: [{
|
|
942
|
+
id: "local",
|
|
943
|
+
destination: "local/clauth/pm2",
|
|
944
|
+
lifecycle_owner: "clauth",
|
|
945
|
+
port: 39114,
|
|
946
|
+
health: "/health",
|
|
947
|
+
cwd: "${PACKAGE_ROOT}",
|
|
948
|
+
start: ["node", "${PACKAGE_ROOT}/bin/server.mjs"],
|
|
949
|
+
}],
|
|
950
|
+
}), null, 2), "utf8");
|
|
951
|
+
|
|
952
|
+
const receipt = registerPlugin(manifestPath, "test");
|
|
953
|
+
assert.equal(receipt.resulting_state.ok, true, JSON.stringify(receipt.resulting_state));
|
|
954
|
+
|
|
955
|
+
const surface = listSurfaces().find((s) => s.plugin_id === "package-root-demo");
|
|
956
|
+
assert.ok(surface, "surface was not registered");
|
|
957
|
+
const expected = origin.replaceAll("\\", "/");
|
|
958
|
+
assert.equal(surface.cwd.replaceAll("\\", "/"), expected,
|
|
959
|
+
`cwd did not resolve to the manifest origin: ${surface.cwd}`);
|
|
960
|
+
assert.ok(surface.start.some((a) => a.replaceAll("\\", "/").includes(expected)),
|
|
961
|
+
`command arg did not resolve to the manifest origin: ${JSON.stringify(surface.start)}`);
|
|
962
|
+
assert.ok(!JSON.stringify(surface).includes("PACKAGE_ROOT"),
|
|
963
|
+
"an unexpanded PACKAGE_ROOT token survived into registered state");
|
|
964
|
+
}));
|