Archived
fix: harden agent profile boundaries
This commit is contained in:
+53
-18
@@ -69,23 +69,50 @@ describe("PI WEB config persistence", () => {
|
||||
expect(loadPiWebConfig(testOptions()).config.agent).toEqual({ command: "acme-agent", dir: "/opt/acme-agent/state" });
|
||||
});
|
||||
|
||||
it("defaults to the Pi agent directory only for Pi commands and launchers", () => {
|
||||
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "/tmp/pi.cmd" } })).toMatchObject({
|
||||
command: "/tmp/pi.cmd",
|
||||
dir: join(tempDir, ".home", ".pi", "agent"),
|
||||
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
|
||||
});
|
||||
it("defaults to the Pi agent directory only for canonical Pi companion names", () => {
|
||||
for (const command of ["pi", "pi.cmd"]) {
|
||||
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command } })).toMatchObject({
|
||||
command,
|
||||
dir: join(tempDir, ".home", ".pi", "agent"),
|
||||
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("requires an explicit agent directory for non-Pi commands", () => {
|
||||
expect(() => effectiveAgentConfig({}, { agent: { command: "acme-agent" } })).toThrow('PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is "acme-agent"');
|
||||
expect(() => savePiWebConfig({ agent: { command: "acme-agent" } }, testOptions())).toThrow('PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is "acme-agent"');
|
||||
it("requires explicit state for alternate names and absolute Pi launchers", () => {
|
||||
const absolutePiCommand = join(tempDir, "bin", "pi");
|
||||
for (const command of ["acme-agent", absolutePiCommand]) {
|
||||
expect(() => effectiveAgentConfig({}, { agent: { command } })).toThrow(`PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is ${JSON.stringify(command)}`);
|
||||
expect(() => savePiWebConfig({ agent: { command } }, testOptions())).toThrow(`PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is ${JSON.stringify(command)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts safe bare executable names and host-absolute executable paths", () => {
|
||||
const absoluteCommand = join(tempDir, "bin", "acme-agent");
|
||||
const agentDir = join(tempDir, "state", "acme");
|
||||
|
||||
expect(effectiveAgentConfig({}, { agent: { command: "acme-agent", dir: agentDir } })).toMatchObject({ command: "acme-agent", dir: agentDir });
|
||||
expect(effectiveAgentConfig({}, { agent: { command: absoluteCommand, dir: agentDir } })).toMatchObject({ command: absoluteCommand, dir: agentDir });
|
||||
});
|
||||
|
||||
it.each(["./acme-agent", "bin/acme-agent", "../acme-agent", "node acme-agent.js", "acme-agent;other", "-acme-agent"])("rejects unsafe or workspace-relative agent command %j", (command) => {
|
||||
expect(() => savePiWebConfig({ agent: { command, dir: join(tempDir, "agent") } }, testOptions())).toThrow("safe bare executable name or host-absolute executable path");
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")("rejects foreign-platform absolute agent command and state paths", () => {
|
||||
expect(() => effectiveAgentConfig({}, { agent: { command: "C:\\tools\\acme-agent.exe", dir: join(tempDir, "agent") } })).toThrow("safe bare executable name or host-absolute executable path");
|
||||
expect(() => effectiveAgentConfig({}, { agent: { command: "acme-agent", dir: "C:\\profiles\\acme" } })).toThrow("agent.dir must be a host-absolute path");
|
||||
});
|
||||
|
||||
it("rejects home expansion that would create a workspace-relative agent directory", () => {
|
||||
expect(() => effectiveAgentConfig({ HOME: "relative-home" })).toThrow("agent.dir must be a host-absolute path");
|
||||
});
|
||||
|
||||
it("resolves explicit alternate agent command and state directory settings", () => {
|
||||
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "acme-agent", dir: "~/agent-profiles/acme" } })).toMatchObject({
|
||||
command: "acme-agent",
|
||||
dir: join(tempDir, ".home", "agent-profiles", "acme"),
|
||||
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR"],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,21 +145,29 @@ describe("PI WEB config persistence", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps legacy Pi env directory overrides scoped to Pi commands", () => {
|
||||
expect(effectiveAgentConfig({
|
||||
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
||||
}, { agent: { dir: join(tempDir, "config-agent") } })).toMatchObject({
|
||||
dir: join(tempDir, "pi-env-agent"),
|
||||
});
|
||||
it("keeps legacy Pi env directory overrides scoped to the canonical Pi command", () => {
|
||||
const legacyDir = join(tempDir, "pi-env-agent");
|
||||
expect(effectiveAgentConfig({ PI_CODING_AGENT_DIR: legacyDir }, { agent: { dir: join(tempDir, "config-agent") } })).toMatchObject({ dir: legacyDir });
|
||||
|
||||
expect(() => effectiveAgentConfig({
|
||||
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
||||
}, { agent: { command: "acme-agent" } })).toThrow('PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is "acme-agent"');
|
||||
for (const command of ["acme-agent", join(tempDir, "bin", "pi")]) {
|
||||
expect(() => effectiveAgentConfig({ PI_CODING_AGENT_DIR: legacyDir }, { agent: { command } }))
|
||||
.toThrow(`PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is ${JSON.stringify(command)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses only explicit session directory env keys", () => {
|
||||
expect(agentSessionDirEnvKeys()).toEqual(["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"]);
|
||||
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home"), PI_WEB_AGENT_COMMAND: "acme-agent", PI_WEB_AGENT_DIR: join(tempDir, "agent") }).sessionDirEnvKeys).toEqual(["PI_WEB_AGENT_SESSION_DIR"]);
|
||||
expect(agentSessionDirEnvKeys(join(tempDir, "bin", "pi"))).toEqual(["PI_WEB_AGENT_SESSION_DIR"]);
|
||||
});
|
||||
|
||||
it("rejects unknown nested agent keys instead of erasing them", async () => {
|
||||
const original = { agent: { command: "acme-agent", dir: join(tempDir, "agent"), futureSetting: true } };
|
||||
await writeFile(configPath, `${JSON.stringify(original, null, 2)}\n`, "utf8");
|
||||
|
||||
expect(() => loadPiWebConfig(testOptions())).toThrow('PI WEB config agent contains unknown key "futureSetting"');
|
||||
expect(() => savePiWebConfig({ port: 9000 }, testOptions())).toThrow('PI WEB config agent contains unknown key "futureSetting"');
|
||||
expect(JSON.parse(await readFile(configPath, "utf8"))).toEqual(original);
|
||||
});
|
||||
|
||||
it("exposes the default upload folder in the effective config", () => {
|
||||
|
||||
Reference in New Issue
Block a user