Archived
fix: harden agent profile boundaries
This commit is contained in:
@@ -17,7 +17,7 @@ describe("active agent profile descriptor", () => {
|
||||
expect(first.revision).toMatch(/^sha256:[0-9a-f]{64}$/u);
|
||||
expect(createActiveAgentProfileDescriptor({ ...baseAgent, command: "other-agent" }).revision).not.toBe(first.revision);
|
||||
expect(createActiveAgentProfileDescriptor({ ...baseAgent, dir: "/other/state" }).revision).not.toBe(first.revision);
|
||||
expect(createActiveAgentProfileDescriptor({ ...baseAgent, sessionDirEnvKeys: ["OTHER_SESSION_DIR"] }).revision).not.toBe(first.revision);
|
||||
expect(createActiveAgentProfileDescriptor({ ...baseAgent, sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"] }).revision).not.toBe(first.revision);
|
||||
});
|
||||
|
||||
it("takes an immutable snapshot for the session daemon profile epoch", () => {
|
||||
@@ -32,6 +32,12 @@ describe("active agent profile descriptor", () => {
|
||||
expect(Reflect.set(profile.sessionDirEnvKeys, "0", "MUTATED_SESSION_DIR")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects profile fields outside the host and explicit environment policy", () => {
|
||||
expect(() => createActiveAgentProfileDescriptor({ ...baseAgent, command: "./acme-agent" })).toThrow("must be valid for this host");
|
||||
expect(() => createActiveAgentProfileDescriptor({ ...baseAgent, dir: "relative/state" })).toThrow("must be valid for this host");
|
||||
expect(() => createActiveAgentProfileDescriptor({ ...baseAgent, sessionDirEnvKeys: ["ARBITRARY_AGENT_SESSION_DIR"] })).toThrow("explicit PI WEB policy");
|
||||
});
|
||||
|
||||
it("copies only the secret-free descriptor fields", () => {
|
||||
const input = {
|
||||
...baseAgent,
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import type { EffectivePiWebAgentConfig } from "../config.js";
|
||||
import { isHostAbsoluteAgentDir, isSafeAgentCommandForHost, PI_CODING_AGENT_SESSION_DIR_ENV, PI_WEB_AGENT_SESSION_DIR_ENV, type EffectivePiWebAgentConfig } from "../config.js";
|
||||
import type { ActiveAgentProfileDescriptor } from "../shared/apiTypes.js";
|
||||
import { ACTIVE_AGENT_PROFILE_SCHEMA_VERSION } from "../shared/activeAgentProfile.js";
|
||||
|
||||
export function createActiveAgentProfileDescriptor(agent: EffectivePiWebAgentConfig): ActiveAgentProfileDescriptor {
|
||||
if (!isSafeAgentCommandForHost(agent.command) || !isHostAbsoluteAgentDir(agent.dir)) {
|
||||
throw new Error("Active agent profile command and directory must be valid for this host");
|
||||
}
|
||||
if (!hasValidSessionDirEnvKeys(agent.sessionDirEnvKeys)) {
|
||||
throw new Error("Active agent profile session directory environment keys must use the explicit PI WEB policy");
|
||||
}
|
||||
const sessionDirEnvKeys = Object.freeze([...agent.sessionDirEnvKeys]);
|
||||
const revisionInput = JSON.stringify({
|
||||
schemaVersion: ACTIVE_AGENT_PROFILE_SCHEMA_VERSION,
|
||||
@@ -20,3 +26,9 @@ export function createActiveAgentProfileDescriptor(agent: EffectivePiWebAgentCon
|
||||
sessionDirEnvKeys,
|
||||
});
|
||||
}
|
||||
|
||||
function hasValidSessionDirEnvKeys(keys: readonly string[]): boolean {
|
||||
return (keys.length === 1 || keys.length === 2)
|
||||
&& keys[0] === PI_WEB_AGENT_SESSION_DIR_ENV
|
||||
&& (keys.length === 1 || keys[1] === PI_CODING_AGENT_SESSION_DIR_ENV);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,19 @@ describe("SessionDaemonClient active agent profile protocol", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")("rejects foreign-platform active state paths before local consumers use them", async () => {
|
||||
const client = new SessionDaemonClient();
|
||||
vi.spyOn(client, "request").mockResolvedValue(runtimeResponse({
|
||||
...activeAgentProfile,
|
||||
dir: "C:\\agent-profiles\\acme",
|
||||
}));
|
||||
|
||||
await expect(client.getActiveAgentProfile()).resolves.toEqual({
|
||||
status: "invalid",
|
||||
error: "session daemon active agent profile was not valid for this host",
|
||||
});
|
||||
});
|
||||
|
||||
it("treats a legacy runtime response without a profile as invalid for profile-dependent work", async () => {
|
||||
const client = new SessionDaemonClient();
|
||||
vi.spyOn(client, "request").mockResolvedValue(runtimeResponse(undefined));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import http from "node:http";
|
||||
import { WebSocket } from "ws";
|
||||
import { isHostAbsoluteAgentDir, isSafeAgentCommandForHost } from "../config.js";
|
||||
import type { ActiveAgentProfileDescriptor } from "../shared/apiTypes.js";
|
||||
import { parsePiWebRuntimeComponent } from "../shared/piWebStatusParsing.js";
|
||||
import { sessiondHttpUrl, sessiondSocketPath } from "./config.js";
|
||||
@@ -108,6 +109,9 @@ export async function getSessionDaemonActiveAgentProfile(client: SessionDaemonRe
|
||||
if (runtime.activeAgentProfile === undefined) {
|
||||
return { status: "invalid", error: "session daemon runtime response did not include an active agent profile" };
|
||||
}
|
||||
if (!isSafeAgentCommandForHost(runtime.activeAgentProfile.command) || !isHostAbsoluteAgentDir(runtime.activeAgentProfile.dir)) {
|
||||
return { status: "invalid", error: "session daemon active agent profile was not valid for this host" };
|
||||
}
|
||||
return { status: "available", profile: runtime.activeAgentProfile };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user