Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2f1b650
fix(security): isolate issue task execution
WhinoDo Jul 15, 2026
4e168a3
fix(security): isolate provider task snapshots
WhinoDo Jul 15, 2026
dc0589f
fix(security): close provider task execution gaps
WhinoDo Jul 15, 2026
72e7545
fix(security): bind provider state to task roots
WhinoDo Jul 15, 2026
43d1767
fix(security): reject hardlinked local directories
WhinoDo Jul 15, 2026
ef129ab
fix(isolation): allow stable Linux usr aliases
WhinoDo Jul 16, 2026
63757af
fix(security): close isolation validate-to-start TOCTOU
WhinoDo Jul 16, 2026
12fc393
fix(agent): bind task execution to isolated identities
WhinoDo Jul 16, 2026
e3480ff
fix(agent): support trusted usr-merge command aliases
WhinoDo Jul 16, 2026
4e37ac3
fix(cli): deny task-scoped run management
WhinoDo Jul 16, 2026
04dd2af
fix(daemon): require persisted task execution capability
WhinoDo Jul 16, 2026
9c71203
test(handler): mark executable runtime fixtures capable
WhinoDo Jul 16, 2026
d6456a5
test(agent): preserve trusted launcher in kiro fixtures
WhinoDo Jul 16, 2026
61ff460
test(handler): preserve runtime capability fixtures
WhinoDo Jul 16, 2026
9122ff7
test(handler): keep owner guard fixture executable
WhinoDo Jul 16, 2026
0f8fdf5
fix(repocache): bind worktree operations to identities
WhinoDo Jul 16, 2026
ed90dc6
fix daemon worktree publication races
WhinoDo Jul 16, 2026
30ee83b
fix(repocache): quarantine rollback targets atomically
WhinoDo Jul 16, 2026
cc6f66b
fix(agent): synchronize task command process state
WhinoDo Jul 16, 2026
53ed039
fix(task-scope): bind managed CLI authority
WhinoDo Jul 16, 2026
1bc37d3
fix(agent): prevent protected mount shadowing
WhinoDo Jul 16, 2026
438f98a
fix(daemon): protect operator diagnostics
WhinoDo Jul 16, 2026
c9e66c0
fix(desktop): bound daemon diagnostics parsing
WhinoDo Jul 16, 2026
10c748b
fix(desktop): harden daemon health parsing
WhinoDo Jul 16, 2026
27ec141
fix(lifecycle): require idle daemon restarts
WhinoDo Jul 16, 2026
ea844d4
fix(lifecycle): serialize daemon restarts and require idle shutdown
WhinoDo Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions apps/desktop/src/main/daemon-diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { chmod, mkdir, rm, symlink, writeFile } from "fs/promises";
import { dirname, join } from "path";
import {
fetchDaemonDiagnostics,
profileOperatorCredentialPath,
} from "./daemon-diagnostics";

const credential = Buffer.alloc(32, 7).toString("base64url");
let originalHome: string | undefined;
let home: string;

beforeEach(async () => {
originalHome = process.env.HOME;
home = join(process.cwd(), `.daemon-diagnostics-test-${process.pid}`);
process.env.HOME = home;
await rm(home, { recursive: true, force: true });
vi.restoreAllMocks();
});

afterEach(async () => {
process.env.HOME = originalHome;
await rm(home, { recursive: true, force: true });
vi.restoreAllMocks();
});

async function writeCredential(profile: string, value = credential) {
const path = profileOperatorCredentialPath(profile);
await mkdir(dirname(path), { recursive: true });
await writeFile(path, `${value}\n`, { mode: 0o600 });
if (process.platform !== "win32") await chmod(path, 0o600);
return path;
}

function validDiagnostics(overrides: Record<string, unknown> = {}) {
return {
status: "running",
os: "darwin",
pid: 1234,
uptime: "1m2s",
daemon_id: "daemon-1",
device_name: "test-device",
server_url: "https://example.test",
cli_version: "v9.9.9",
active_task_count: 2,
agents: ["codex"],
workspaces: [{ id: "workspace-1", runtimes: ["codex"] }],
...overrides,
};
}

describe("daemon diagnostics", () => {
it("uses the exact default and named profile credential paths", () => {
expect(profileOperatorCredentialPath("")).toBe(
join(home, ".multica", "daemon.shutdown-token"),
);
expect(profileOperatorCredentialPath("desktop-localhost-8082")).toBe(
join(
home,
".multica",
"profiles",
"desktop-localhost-8082",
"daemon.shutdown-token",
),
);
});

it("authenticates diagnostics without exposing the credential in the URL", async () => {
await writeCredential("test-profile");
const fetchMock = vi.fn().mockResolvedValue(
new Response(
JSON.stringify(validDiagnostics()),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
vi.stubGlobal("fetch", fetchMock);

const result = await fetchDaemonDiagnostics("test-profile", 19514);
expect(result?.cli_version).toBe("v9.9.9");
expect(fetchMock).toHaveBeenCalledOnce();
const [url, options] = fetchMock.mock.calls[0];
expect(url).toBe("http://127.0.0.1:19514/diagnostics");
expect(url).not.toContain(credential);
expect(options.redirect).toBe("error");
expect(options.headers).toEqual({
"X-Multica-Shutdown-Credential": credential,
});
});

it("fails closed for invalid credential files", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);

await writeCredential("malformed", "not-base64url!");
expect(await fetchDaemonDiagnostics("malformed", 19514)).toBeNull();

if (process.platform !== "win32") {
const broad = await writeCredential("broad");
await chmod(broad, 0o644);
expect(await fetchDaemonDiagnostics("broad", 19514)).toBeNull();

const target = await writeCredential("target");
const link = profileOperatorCredentialPath("link");
await mkdir(dirname(link), { recursive: true });
await symlink(target, link);
expect(await fetchDaemonDiagnostics("link", 19514)).toBeNull();
}

expect(fetchMock).not.toHaveBeenCalled();
});

it("fails closed for authentication, redirects, and malformed JSON", async () => {
await writeCredential("test-profile");
for (const response of [
new Response("unauthorized", { status: 401 }),
new Response("redirect", { status: 307 }),
new Response("not json", { status: 200 }),
]) {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(response));
expect(await fetchDaemonDiagnostics("test-profile", 19514)).toBeNull();
}
});

it("rejects oversized diagnostics responses", async () => {
await writeCredential("test-profile");
for (const response of [
new Response("{}", {
status: 200,
headers: { "Content-Length": String((1 << 20) + 1) },
}),
new Response(`{"status":"running","padding":"${"x".repeat(1 << 20)}"}`, {
status: 200,
}),
]) {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(response));
expect(await fetchDaemonDiagnostics("test-profile", 19514)).toBeNull();
}
});

it("rejects duplicate keys, trailing content, and unknown fields", async () => {
await writeCredential("test-profile");
for (const source of [
'{"status":"running","status":"starting"}',
'{"status":"running","workspaces":[{"id":1,"id":2}]}',
'{"status":"running"} true',
'{"status":"running","unexpected":true}',
'{"status":"running","pid":1e400}',
`${"[".repeat(65)}0${"]".repeat(65)}`,
]) {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(source, { status: 200 })),
);
expect(await fetchDaemonDiagnostics("test-profile", 19514)).toBeNull();
}
});

it("fails closed for malformed diagnostics fields", async () => {
await writeCredential("test-profile");
for (const payload of [
{},
{ status: "stopped" },
{ status: "running", active_task_count: -1 },
{ status: "running", active_task_count: 0.5 },
{ status: "running", pid: -1 },
{ status: "running", pid: 0.5 },
{ status: "running", agents: ["codex", 7] },
{ status: "running", workspaces: "workspace-id" },
{ status: "running", workspaces: [{ id: 7, runtimes: [] }] },
{ status: "running", workspaces: [{ id: "id", runtimes: [7] }] },
{ status: "running", workspaces: [{ id: "id", runtimes: [], extra: true }] },
validDiagnostics({ os: undefined }),
validDiagnostics({ active_task_count: Number.MAX_SAFE_INTEGER + 1 }),
]) {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
new Response(JSON.stringify(payload), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
),
);
expect(await fetchDaemonDiagnostics("test-profile", 19514)).toBeNull();
}
});
});
147 changes: 147 additions & 0 deletions apps/desktop/src/main/daemon-diagnostics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { constants } from "fs";
import { open, lstat } from "fs/promises";
import { join } from "path";
import { homedir } from "os";
import { readBoundedDaemonJSON } from "./daemon-json";

const OPERATOR_CREDENTIAL_HEADER = "X-Multica-Shutdown-Credential";
const MAX_CREDENTIAL_BYTES = 256;
const DIAGNOSTICS_FIELDS = new Set([
"status",
"os",
"pid",
"uptime",
"daemon_id",
"device_name",
"server_url",
"cli_version",
"active_task_count",
"agents",
"workspaces",
]);

export interface DiagnosticsPayload {
status: "running" | "starting";
os: string;
pid: number;
uptime: string;
daemon_id: string;
device_name: string;
server_url: string;
cli_version: string;
active_task_count: number;
agents: string[];
workspaces: Array<{ id: string; runtimes: string[] }>;
}

function isWorkspace(value: unknown): value is { id: string; runtimes: string[] } {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const workspace = value as Record<string, unknown>;
const keys = Object.keys(workspace);
return (
keys.length === 2 &&
keys.includes("id") &&
keys.includes("runtimes") &&
typeof workspace.id === "string" &&
Array.isArray(workspace.runtimes) &&
workspace.runtimes.every((runtime) => typeof runtime === "string")
);
}

function isDiagnosticsPayload(value: unknown): value is DiagnosticsPayload {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const payload = value as Record<string, unknown>;
return (
Object.keys(payload).length === DIAGNOSTICS_FIELDS.size &&
Object.keys(payload).every((key) => DIAGNOSTICS_FIELDS.has(key)) &&
(payload.status === "running" || payload.status === "starting") &&
typeof payload.os === "string" &&
typeof payload.pid === "number" &&
Number.isSafeInteger(payload.pid) &&
payload.pid > 0 &&
typeof payload.uptime === "string" &&
typeof payload.daemon_id === "string" &&
typeof payload.device_name === "string" &&
typeof payload.server_url === "string" &&
typeof payload.cli_version === "string" &&
typeof payload.active_task_count === "number" &&
Number.isSafeInteger(payload.active_task_count) &&
payload.active_task_count >= 0 &&
Array.isArray(payload.agents) &&
payload.agents.every((agent) => typeof agent === "string") &&
Array.isArray(payload.workspaces) &&
payload.workspaces.every(isWorkspace)
);
}

export function profileOperatorCredentialPath(profile: string): string {
const dir = profile
? join(homedir(), ".multica", "profiles", profile)
: join(homedir(), ".multica");
return join(dir, "daemon.shutdown-token");
}

async function readOperatorCredential(profile: string): Promise<string | null> {
const path = profileOperatorCredentialPath(profile);
let before;
try {
before = await lstat(path);
} catch {
return null;
}
if (!before.isFile() || before.isSymbolicLink()) return null;
if (process.platform !== "win32" && (before.mode & 0o077) !== 0) return null;
if (before.size <= 0 || before.size > MAX_CREDENTIAL_BYTES) return null;

let file;
try {
const flags =
process.platform === "win32"
? constants.O_RDONLY
: constants.O_RDONLY | constants.O_NOFOLLOW;
file = await open(path, flags);
const after = await file.stat();
if (!after.isFile()) return null;
if (before.dev !== after.dev || before.ino !== after.ino) return null;
if (process.platform !== "win32" && (after.mode & 0o077) !== 0) return null;
if (after.size <= 0 || after.size > MAX_CREDENTIAL_BYTES) return null;
const raw = await file.readFile({ encoding: "utf-8" });
if (Buffer.byteLength(raw) > MAX_CREDENTIAL_BYTES) return null;
const credential = raw.trim();
if (!/^[A-Za-z0-9_-]+$/.test(credential)) return null;
const decoded = Buffer.from(credential, "base64url");
if (decoded.length !== 32 || decoded.toString("base64url") !== credential) {
return null;
}
return credential;
} catch {
return null;
} finally {
await file?.close().catch(() => {});
}
}

export async function fetchDaemonDiagnostics(
profile: string,
port: number,
): Promise<DiagnosticsPayload | null> {
const credential = await readOperatorCredential(profile);
if (!credential) return null;

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 2_000);
try {
const res = await fetch(`http://127.0.0.1:${port}/diagnostics`, {
headers: { [OPERATOR_CREDENTIAL_HEADER]: credential },
redirect: "error",
signal: controller.signal,
});
if (!res.ok) return null;
const payload = await readBoundedDaemonJSON(res, controller.signal);
return isDiagnosticsPayload(payload) ? payload : null;
} catch {
return null;
} finally {
clearTimeout(timeout);
}
}
58 changes: 58 additions & 0 deletions apps/desktop/src/main/daemon-health.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { fetchDaemonHealth } from "./daemon-health";

afterEach(() => {
vi.restoreAllMocks();
});

describe("daemon health", () => {
it("accepts only the public contract and rejects redirects", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response('{"status":"running","os":"darwin"}', { status: 200 }),
);
vi.stubGlobal("fetch", fetchMock);

expect(await fetchDaemonHealth(19514)).toEqual({ status: "running", os: "darwin" });
expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:19514/health", {
redirect: "error",
signal: expect.any(AbortSignal),
});
});

it("rejects oversized, ambiguous, trailing, and unknown responses", async () => {
for (const response of [
new Response("{}", {
status: 200,
headers: { "Content-Length": String((1 << 20) + 1) },
}),
new Response(`{"status":"running","os":"${"x".repeat(1 << 20)}"}`, {
status: 200,
}),
new Response('{"status":"running","status":"starting"}', { status: 200 }),
new Response('{"status":"running"} true', { status: 200 }),
new Response('{"status":"running","pid":123}', { status: 200 }),
new Response('{"status":"running"}', { status: 200 }),
new Response(`${"[".repeat(65)}0${"]".repeat(65)}`, { status: 200 }),
]) {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(response));
expect(await fetchDaemonHealth(19514)).toBeNull();
}
});

it("keeps the timeout active while reading the response body", async () => {
vi.useFakeTimers();
try {
const body = new ReadableStream<Uint8Array>({ start() {} });
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(body, { status: 200 })),
);

const result = fetchDaemonHealth(19514);
await vi.advanceTimersByTimeAsync(2_001);
await expect(result).resolves.toBeNull();
} finally {
vi.useRealTimers();
}
});
});
Loading
Loading