fix: harden agent profile boundaries

This commit is contained in:
Federico Jaramillo Martinez
2026-07-13 23:39:49 +02:00
parent 97e0afc6fa
commit adc2e297a4
25 changed files with 419 additions and 150 deletions
+7 -1
View File
@@ -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,
+13 -1
View File
@@ -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);
}
+13
View File
@@ -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));
+4
View File
@@ -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 };
}