1081 lines
29 KiB
TypeScript
1081 lines
29 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
import type { AuditEntry, HousekeepingAuditWriter } from "@/lib/services/audit";
|
|
import type { HousekeepingCapabilityContext } from "../contracts";
|
|
import { anyCapability, fail, ok } from "../contracts";
|
|
import { dispatchHousekeepingCommand } from "./dispatcher";
|
|
import {
|
|
getHousekeepingCommand,
|
|
type HousekeepingCommand,
|
|
registerHousekeepingCommand,
|
|
} from "./registry";
|
|
|
|
const actor = { id: 42, username: "operator", rank: 9 };
|
|
|
|
function capabilityContext(
|
|
granted: readonly string[] = ["admin.settings.edit"],
|
|
): HousekeepingCapabilityContext {
|
|
const permissions = new Set(granted);
|
|
return {
|
|
actor,
|
|
isSuperAdmin: false,
|
|
has: (slug) => permissions.has(slug),
|
|
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
|
|
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
|
|
};
|
|
}
|
|
|
|
function auditRecorder(events: string[] = []): {
|
|
entries: AuditEntry[];
|
|
writer: HousekeepingAuditWriter;
|
|
} {
|
|
const entries: AuditEntry[] = [];
|
|
return {
|
|
entries,
|
|
writer: {
|
|
write: async (entry) => {
|
|
entries.push({ ...entry });
|
|
events.push(`audit:${entry.outcome}`);
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function dependencies(options?: {
|
|
context?: HousekeepingCapabilityContext;
|
|
ipAddress?: string;
|
|
audit?: HousekeepingAuditWriter;
|
|
rateLimit?: (
|
|
key: string,
|
|
attempts: number,
|
|
windowMs: number,
|
|
) => Promise<boolean>;
|
|
}) {
|
|
return {
|
|
context: options?.context ?? capabilityContext(),
|
|
ipAddress: options?.ipAddress ?? "198.51.100.8",
|
|
audit: options?.audit ?? auditRecorder().writer,
|
|
rateLimit: options?.rateLimit ?? (async () => true),
|
|
};
|
|
}
|
|
|
|
function register<I, O>(command: HousekeepingCommand<I, O>): void {
|
|
registerHousekeepingCommand(command);
|
|
}
|
|
|
|
function baseCommand<I, O>(
|
|
id: string,
|
|
overrides: Pick<HousekeepingCommand<I, O>, "input" | "execute"> &
|
|
Partial<
|
|
Pick<HousekeepingCommand<I, O>, "risk" | "requiresReason" | "rateLimit">
|
|
>,
|
|
): HousekeepingCommand<I, O> {
|
|
return {
|
|
id,
|
|
owner: "system",
|
|
risk: overrides.risk ?? "safe",
|
|
capability: anyCapability("admin.settings.edit"),
|
|
input: overrides.input,
|
|
requiresReason: overrides.requiresReason ?? false,
|
|
rateLimit: overrides.rateLimit ?? { attempts: 3, windowMs: 45_000 },
|
|
execute: overrides.execute,
|
|
};
|
|
}
|
|
|
|
describe("dispatchHousekeepingCommand", () => {
|
|
it("returns a typed not-found result for an unknown command", async () => {
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.missing", input: {} },
|
|
dependencies(),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "NOT_FOUND",
|
|
messageKey: "errors.housekeeping.notFound",
|
|
},
|
|
});
|
|
expect(result.correlationId).toMatch(/^[0-9a-f-]{36}$/i);
|
|
});
|
|
|
|
it("rechecks capability without treating actor rank as authorization", async () => {
|
|
let executed = false;
|
|
const audit = auditRecorder();
|
|
register(
|
|
baseCommand("system.dispatch.denied", {
|
|
risk: "sensitive",
|
|
input: z.object({}),
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.denied", input: {} },
|
|
dependencies({ context: capabilityContext([]), audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "FORBIDDEN" },
|
|
});
|
|
expect(executed).toBe(false);
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["denied"]);
|
|
expect(audit.entries[0]?.correlationId).toBe(result.correlationId);
|
|
});
|
|
|
|
it("rejects invalid input before the command can execute", async () => {
|
|
let executed = false;
|
|
register(
|
|
baseCommand("system.dispatch.invalid-input", {
|
|
input: z.object({ count: z.number().int().positive() }),
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.invalid-input", input: { count: -1 } },
|
|
dependencies(),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "VALIDATION" },
|
|
});
|
|
expect(executed).toBe(false);
|
|
});
|
|
|
|
it("uses unchanged private validation after public descriptor mutation attempts", async () => {
|
|
const commandId = "system.dispatch.reflective-schema-mutation";
|
|
let executions = 0;
|
|
register(
|
|
baseCommand(commandId, {
|
|
input: z.object({
|
|
objectDescriptor: z.string().min(3),
|
|
objectDescriptors: z.string().min(3),
|
|
reflectDescriptor: z.string().min(3),
|
|
}),
|
|
execute: async (context) => {
|
|
executions += 1;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
const registered = getHousekeepingCommand(commandId);
|
|
if (!registered) throw new Error("registered command missing");
|
|
|
|
const readers = [
|
|
["objectDescriptor", Object.getOwnPropertyDescriptor],
|
|
[
|
|
"objectDescriptors",
|
|
(target: object, property: PropertyKey) =>
|
|
Reflect.get(Object.getOwnPropertyDescriptors(target), property) as
|
|
| PropertyDescriptor
|
|
| undefined,
|
|
],
|
|
["reflectDescriptor", Reflect.getOwnPropertyDescriptor],
|
|
] as const;
|
|
const descriptorValue = (
|
|
target: object,
|
|
property: PropertyKey,
|
|
readDescriptor: (
|
|
target: object,
|
|
property: PropertyKey,
|
|
) => PropertyDescriptor | undefined,
|
|
): unknown => {
|
|
const descriptor = readDescriptor(target, property);
|
|
if (!descriptor) {
|
|
throw new Error(`missing descriptor: ${String(property)}`);
|
|
}
|
|
if ("value" in descriptor) return descriptor.value;
|
|
if (descriptor.get) return Reflect.apply(descriptor.get, target, []);
|
|
return undefined;
|
|
};
|
|
|
|
for (const [field, readDescriptor] of readers) {
|
|
const definition = descriptorValue(
|
|
registered.input,
|
|
"def",
|
|
readDescriptor,
|
|
) as object;
|
|
const shape = descriptorValue(
|
|
definition,
|
|
"shape",
|
|
readDescriptor,
|
|
) as Record<string, z.ZodType>;
|
|
const child = shape[field];
|
|
if (!child) throw new Error(`public child missing: ${field}`);
|
|
const childDefinition = descriptorValue(
|
|
child,
|
|
"def",
|
|
readDescriptor,
|
|
) as object;
|
|
const checks = descriptorValue(
|
|
childDefinition,
|
|
"checks",
|
|
readDescriptor,
|
|
) as readonly object[];
|
|
const internal = descriptorValue(
|
|
checks[0] as object,
|
|
"_zod",
|
|
readDescriptor,
|
|
) as object;
|
|
const checkDefinition = descriptorValue(
|
|
internal,
|
|
"def",
|
|
readDescriptor,
|
|
) as { minimum: number };
|
|
try {
|
|
shape[field] = z.number();
|
|
} catch {
|
|
// A readonly facade may reject the assignment.
|
|
}
|
|
try {
|
|
checkDefinition.minimum = 0;
|
|
} catch {
|
|
// A readonly facade may reject the assignment.
|
|
}
|
|
}
|
|
|
|
const forged = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId,
|
|
input: {
|
|
objectDescriptor: 7,
|
|
objectDescriptors: 7,
|
|
reflectDescriptor: 7,
|
|
},
|
|
},
|
|
dependencies(),
|
|
);
|
|
expect(forged).toMatchObject({
|
|
ok: false,
|
|
error: { code: "VALIDATION" },
|
|
});
|
|
expect(executions).toBe(0);
|
|
|
|
const valid = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId,
|
|
input: {
|
|
objectDescriptor: "valid",
|
|
objectDescriptors: "valid",
|
|
reflectDescriptor: "valid",
|
|
},
|
|
},
|
|
dependencies(),
|
|
);
|
|
expect(valid).toMatchObject({ ok: true });
|
|
expect(executions).toBe(1);
|
|
});
|
|
it("rejects public error callbacks before they can weaken dispatcher validation", async () => {
|
|
const commandId = "system.dispatch.callback-schema-mutation";
|
|
let executions = 0;
|
|
register(
|
|
baseCommand(commandId, {
|
|
input: z.object({ value: z.string().min(3) }),
|
|
execute: async (context) => {
|
|
executions += 1;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
const registered = getHousekeepingCommand(commandId);
|
|
if (!registered) throw new Error("registered command missing");
|
|
|
|
let callbackCalls = 0;
|
|
let capturedInstance: unknown;
|
|
let attackError: unknown;
|
|
try {
|
|
registered.input.safeParse(
|
|
{ value: "x" },
|
|
{
|
|
error: (issue) => {
|
|
callbackCalls += 1;
|
|
capturedInstance = issue.inst;
|
|
if (issue.inst) {
|
|
const internal = issue.inst._zod as {
|
|
def?: { minimum?: number };
|
|
};
|
|
if (typeof internal.def?.minimum === "number") {
|
|
internal.def.minimum = 0;
|
|
}
|
|
}
|
|
return "forged validation error";
|
|
},
|
|
},
|
|
);
|
|
} catch (error) {
|
|
attackError = error;
|
|
}
|
|
|
|
const forged = await dispatchHousekeepingCommand(
|
|
{ commandId, input: { value: "x" } },
|
|
dependencies(),
|
|
);
|
|
const valid = await dispatchHousekeepingCommand(
|
|
{ commandId, input: { value: "valid" } },
|
|
dependencies(),
|
|
);
|
|
|
|
expect({
|
|
attackError:
|
|
attackError instanceof TypeError ? attackError.message : undefined,
|
|
callbackCalls,
|
|
capturedInstance,
|
|
forgedOk: forged.ok,
|
|
forgedCode: forged.ok ? undefined : forged.error.code,
|
|
validOk: valid.ok,
|
|
executions,
|
|
}).toEqual({
|
|
attackError: "callback-bearing schema arguments are not supported",
|
|
callbackCalls: 0,
|
|
capturedInstance: undefined,
|
|
forgedOk: false,
|
|
forgedCode: "VALIDATION",
|
|
validOk: true,
|
|
executions: 1,
|
|
});
|
|
});
|
|
it("rejects a missing required reason before the command can execute", async () => {
|
|
let executed = false;
|
|
const audit = auditRecorder();
|
|
register(
|
|
baseCommand("system.dispatch.reason", {
|
|
risk: "sensitive",
|
|
requiresReason: true,
|
|
input: z.object({}),
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.reason", input: {}, reason: " " },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "VALIDATION",
|
|
fieldErrors: { reason: ["errors.validation.required"] },
|
|
},
|
|
});
|
|
expect(executed).toBe(false);
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["denied"]);
|
|
});
|
|
|
|
it("uses actor, IP, command ID, and the approved command limit", async () => {
|
|
const calls: Array<[string, number, number]> = [];
|
|
let executed = false;
|
|
register(
|
|
baseCommand("system.dispatch.rate-limit", {
|
|
input: z.object({}),
|
|
rateLimit: { attempts: 2, windowMs: 90_000 },
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.rate-limit", input: {} },
|
|
dependencies({
|
|
rateLimit: async (key, attempts, windowMs) => {
|
|
calls.push([key, attempts, windowMs]);
|
|
return false;
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(calls).toEqual([
|
|
[
|
|
"housekeeping-command:42:198.51.100.8:system.dispatch.rate-limit",
|
|
2,
|
|
90_000,
|
|
],
|
|
]);
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "RATE_LIMITED" },
|
|
});
|
|
expect(executed).toBe(false);
|
|
});
|
|
|
|
it("executes a safe command with parsed input and writes one success outcome", async () => {
|
|
const audit = auditRecorder();
|
|
let receivedCount = 0;
|
|
register(
|
|
baseCommand("system.dispatch.safe-success", {
|
|
input: z.object({ count: z.coerce.number().int().positive() }),
|
|
execute: async (_context, input) => {
|
|
receivedCount = input.count;
|
|
return ok({ doubled: input.count * 2 }, "wrong-command-correlation");
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.safe-success", input: { count: "4" } },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(receivedCount).toBe(4);
|
|
expect(result).toMatchObject({ ok: true, data: { doubled: 8 } });
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["success"]);
|
|
expect(audit.entries[0]?.correlationId).toBe(result.correlationId);
|
|
expect(result.correlationId).not.toBe("wrong-command-correlation");
|
|
});
|
|
|
|
it("persists sensitive intent before execution and success afterward", async () => {
|
|
const events: string[] = [];
|
|
const audit = auditRecorder(events);
|
|
let executionCorrelation = "";
|
|
register(
|
|
baseCommand("system.dispatch.sensitive-success", {
|
|
risk: "sensitive",
|
|
requiresReason: true,
|
|
input: z.object({ enabled: z.boolean() }),
|
|
execute: async (context) => {
|
|
executionCorrelation = context.correlationId;
|
|
events.push("execute");
|
|
return ok({ saved: true }, "untrusted-command-correlation");
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId: "system.dispatch.sensitive-success",
|
|
input: { enabled: true },
|
|
reason: " Planned change ",
|
|
},
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(events).toEqual(["audit:intent", "execute", "audit:success"]);
|
|
expect(audit.entries).toMatchObject([
|
|
{
|
|
userId: 42,
|
|
action: "system.dispatch.sensitive-success",
|
|
target: "system",
|
|
domain: "system",
|
|
reason: "Planned change",
|
|
ipAddress: "198.51.100.8",
|
|
outcome: "intent",
|
|
},
|
|
{ outcome: "success" },
|
|
]);
|
|
expect(executionCorrelation).toBe(result.correlationId);
|
|
expect(audit.entries[0]?.correlationId).toBe(result.correlationId);
|
|
expect(audit.entries[1]?.correlationId).toBe(result.correlationId);
|
|
});
|
|
|
|
it("persists sensitive failure after execution with the same correlation", async () => {
|
|
const events: string[] = [];
|
|
const audit = auditRecorder(events);
|
|
register(
|
|
baseCommand("system.dispatch.sensitive-failure", {
|
|
risk: "sensitive",
|
|
input: z.object({}),
|
|
execute: async () => {
|
|
events.push("execute");
|
|
return fail(
|
|
"DEPENDENCY_UNAVAILABLE",
|
|
"errors.housekeeping.dependencyUnavailable",
|
|
"wrong-command-correlation",
|
|
);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.sensitive-failure", input: {} },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(events).toEqual(["audit:intent", "execute", "audit:failure"]);
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "DEPENDENCY_UNAVAILABLE" },
|
|
});
|
|
expect(audit.entries[0]?.correlationId).toBe(result.correlationId);
|
|
expect(audit.entries[1]?.correlationId).toBe(result.correlationId);
|
|
});
|
|
|
|
it("blocks sensitive execution when intent persistence fails", async () => {
|
|
let executed = false;
|
|
register(
|
|
baseCommand("system.dispatch.intent-failure", {
|
|
risk: "sensitive",
|
|
input: z.object({}),
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.intent-failure", input: {} },
|
|
dependencies({
|
|
audit: {
|
|
write: async () => {
|
|
throw new Error("audit credentials exposed");
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(executed).toBe(false);
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "INTERNAL",
|
|
messageKey: "errors.housekeeping.internal",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("sanitizes unknown exceptions and records a failure outcome", async () => {
|
|
const audit = auditRecorder();
|
|
register(
|
|
baseCommand("system.dispatch.exception", {
|
|
risk: "sensitive",
|
|
input: z.object({}),
|
|
execute: async () => {
|
|
throw new Error("database password=hunter2");
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.exception", input: {} },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "INTERNAL",
|
|
messageKey: "errors.housekeeping.internal",
|
|
},
|
|
});
|
|
expect(JSON.stringify(result)).not.toContain("hunter2");
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual([
|
|
"intent",
|
|
"failure",
|
|
]);
|
|
expect(audit.entries[1]?.correlationId).toBe(result.correlationId);
|
|
});
|
|
it("runs sensitive capability and rate preflight before intent and execution", async () => {
|
|
const events: string[] = [];
|
|
const audit = auditRecorder(events);
|
|
const tracedContext = {
|
|
...capabilityContext(),
|
|
hasAny: (...slugs: string[]) => {
|
|
events.push("capability");
|
|
return slugs.includes("admin.settings.edit");
|
|
},
|
|
};
|
|
register(
|
|
baseCommand("system.dispatch.preflight-order", {
|
|
risk: "sensitive",
|
|
requiresReason: true,
|
|
input: z.object({ enabled: z.boolean() }),
|
|
execute: async (context) => {
|
|
events.push("execute");
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
await dispatchHousekeepingCommand(
|
|
{
|
|
commandId: "system.dispatch.preflight-order",
|
|
input: { enabled: true },
|
|
reason: "Approved change",
|
|
},
|
|
dependencies({
|
|
context: tracedContext,
|
|
audit: audit.writer,
|
|
rateLimit: async () => {
|
|
events.push("rate");
|
|
return true;
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(events).toEqual([
|
|
"capability",
|
|
"rate",
|
|
"audit:intent",
|
|
"execute",
|
|
"audit:success",
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
["capability", "system.dispatch.preflight-denied"],
|
|
["input", "system.dispatch.preflight-invalid"],
|
|
["rate", "system.dispatch.preflight-limited"],
|
|
] as const)(
|
|
"writes only denied evidence when sensitive %s preflight rejects",
|
|
async (boundary, commandId) => {
|
|
const audit = auditRecorder();
|
|
let executed = false;
|
|
register(
|
|
baseCommand(commandId, {
|
|
risk: "sensitive",
|
|
input: z.object({ count: z.number().positive() }),
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId,
|
|
input: { count: boundary === "input" ? -1 : 1 },
|
|
},
|
|
dependencies({
|
|
context:
|
|
boundary === "capability"
|
|
? capabilityContext([])
|
|
: capabilityContext(),
|
|
audit: audit.writer,
|
|
rateLimit: async () => boundary !== "rate",
|
|
}),
|
|
);
|
|
|
|
expect(result).toMatchObject({ ok: false });
|
|
expect(executed).toBe(false);
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["denied"]);
|
|
expect(audit.entries[0]?.correlationId).toBe(result.correlationId);
|
|
},
|
|
);
|
|
|
|
it("writes generic server-owned evidence for a validated unknown command", async () => {
|
|
const audit = auditRecorder();
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.unknown-audited", input: {} },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "NOT_FOUND" },
|
|
});
|
|
expect(audit.entries).toEqual([
|
|
{
|
|
userId: 42,
|
|
action: "housekeeping.command.dispatch",
|
|
target: "system.dispatch.unknown-audited",
|
|
correlationId: result.correlationId,
|
|
outcome: "denied",
|
|
ipAddress: "198.51.100.8",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
null,
|
|
[],
|
|
Object.assign(Object.create(Object.freeze({})), {
|
|
commandId: "system.dispatch.unknown",
|
|
input: {},
|
|
}),
|
|
{ commandId: "system.dispatch.unknown", input: {}, risk: "safe" },
|
|
{ commandId: `system.${"x".repeat(200)}`, input: {} },
|
|
{
|
|
commandId: "system.dispatch.unknown",
|
|
input: {},
|
|
reason: "x".repeat(1001),
|
|
},
|
|
])(
|
|
"returns a correlated validation result for malformed runtime request %#",
|
|
async (request) => {
|
|
const result = await dispatchHousekeepingCommand(
|
|
request as never,
|
|
dependencies(),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "VALIDATION",
|
|
messageKey: "errors.housekeeping.validation",
|
|
},
|
|
});
|
|
expect(result.correlationId).toMatch(/^[0-9a-f-]{36}$/i);
|
|
},
|
|
);
|
|
|
|
it("sanitizes a capability exception and writes failure evidence", async () => {
|
|
const audit = auditRecorder();
|
|
register(
|
|
baseCommand("system.dispatch.capability-exception", {
|
|
input: z.object({}),
|
|
execute: async (context) => ok(null, context.correlationId),
|
|
}),
|
|
);
|
|
const brokenContext = {
|
|
...capabilityContext(),
|
|
hasAny: () => {
|
|
throw new Error("capability secret exposed");
|
|
},
|
|
};
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.capability-exception", input: {} },
|
|
dependencies({ context: brokenContext, audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTERNAL", messageKey: "errors.housekeeping.internal" },
|
|
});
|
|
expect(JSON.stringify(result)).not.toContain("secret exposed");
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["failure"]);
|
|
});
|
|
|
|
it("sanitizes a schema exception and writes failure evidence", async () => {
|
|
const audit = auditRecorder();
|
|
register(
|
|
baseCommand("system.dispatch.schema-exception", {
|
|
input: z.object({ value: z.string() }),
|
|
execute: async (context) => ok(null, context.correlationId),
|
|
}),
|
|
);
|
|
const throwingInput = Object.defineProperty({}, "value", {
|
|
enumerable: true,
|
|
get: () => {
|
|
throw new Error("schema secret exposed");
|
|
},
|
|
});
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.schema-exception", input: throwingInput },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTERNAL", messageKey: "errors.housekeeping.internal" },
|
|
});
|
|
expect(JSON.stringify(result)).not.toContain("secret exposed");
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["failure"]);
|
|
});
|
|
|
|
it("throws completed-operation evidence and persists partial when success audit rejects", async () => {
|
|
const attempts: string[] = [];
|
|
const persisted: string[] = [];
|
|
register(
|
|
baseCommand("system.dispatch.success-audit-rejection", {
|
|
input: z.object({}),
|
|
execute: async (context) =>
|
|
ok({ changed: true }, context.correlationId),
|
|
}),
|
|
);
|
|
|
|
const dispatch = dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.success-audit-rejection", input: {} },
|
|
dependencies({
|
|
audit: {
|
|
write: async (entry) => {
|
|
attempts.push(entry.outcome ?? "missing");
|
|
if (entry.outcome === "success") {
|
|
throw new Error("success audit unavailable");
|
|
}
|
|
persisted.push(entry.outcome ?? "missing");
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
await expect(dispatch).rejects.toMatchObject({
|
|
name: "AuditOutcomePersistenceError",
|
|
operationCompleted: true,
|
|
operationResult: { ok: true, data: { changed: true } },
|
|
});
|
|
expect(attempts).toEqual(["success", "partial"]);
|
|
expect(persisted).toEqual(["partial"]);
|
|
});
|
|
|
|
it("preserves a returned command failure when failure audit rejects", async () => {
|
|
const attempts: string[] = [];
|
|
register(
|
|
baseCommand("system.dispatch.returned-failure-audit-rejection", {
|
|
input: z.object({}),
|
|
execute: async () =>
|
|
fail(
|
|
"DEPENDENCY_UNAVAILABLE",
|
|
"errors.housekeeping.dependencyUnavailable",
|
|
"wrong-correlation",
|
|
),
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId: "system.dispatch.returned-failure-audit-rejection",
|
|
input: {},
|
|
},
|
|
dependencies({
|
|
audit: {
|
|
write: async (entry) => {
|
|
attempts.push(entry.outcome ?? "missing");
|
|
throw new Error("failure audit unavailable");
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "DEPENDENCY_UNAVAILABLE" },
|
|
});
|
|
expect(result.correlationId).not.toBe("wrong-correlation");
|
|
expect(attempts).toEqual(["failure"]);
|
|
});
|
|
|
|
it("preserves a sanitized throwing-command failure when failure audit rejects", async () => {
|
|
const attempts: string[] = [];
|
|
register(
|
|
baseCommand("system.dispatch.thrown-failure-audit-rejection", {
|
|
input: z.object({}),
|
|
execute: async () => {
|
|
throw new Error("command database secret exposed");
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId: "system.dispatch.thrown-failure-audit-rejection",
|
|
input: {},
|
|
},
|
|
dependencies({
|
|
audit: {
|
|
write: async (entry) => {
|
|
attempts.push(entry.outcome ?? "missing");
|
|
throw new Error("audit replacement secret exposed");
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTERNAL", messageKey: "errors.housekeeping.internal" },
|
|
});
|
|
expect(JSON.stringify(result)).not.toContain("secret exposed");
|
|
expect(attempts).toEqual(["failure"]);
|
|
});
|
|
it("audits malformed envelopes without copying unvalidated metadata", async () => {
|
|
const audit = auditRecorder();
|
|
const result = await dispatchHousekeepingCommand(
|
|
{
|
|
commandId: "people.client-controlled-target",
|
|
input: { password: "secret" },
|
|
reason: "client reason",
|
|
domain: "people",
|
|
},
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "VALIDATION" },
|
|
});
|
|
expect(audit.entries).toEqual([
|
|
{
|
|
userId: 42,
|
|
action: "housekeeping.command.dispatch",
|
|
target: "request-envelope",
|
|
correlationId: result.correlationId,
|
|
outcome: "denied",
|
|
ipAddress: "198.51.100.8",
|
|
},
|
|
]);
|
|
expect(JSON.stringify(audit.entries)).not.toContain("client-controlled");
|
|
expect(JSON.stringify(audit.entries)).not.toContain("secret");
|
|
expect(JSON.stringify(audit.entries)).not.toContain("client reason");
|
|
});
|
|
|
|
it("uses canonical command-ID grammar before unknown-command evidence", async () => {
|
|
const audit = auditRecorder();
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId: "system.bad\nidentifier", input: {} },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "VALIDATION" },
|
|
});
|
|
expect(audit.entries).toMatchObject([
|
|
{
|
|
action: "housekeeping.command.dispatch",
|
|
target: "request-envelope",
|
|
outcome: "denied",
|
|
},
|
|
]);
|
|
expect(JSON.stringify(audit.entries)).not.toContain("bad\\nidentifier");
|
|
});
|
|
|
|
it.each([
|
|
["null", null],
|
|
["missing success data", { ok: true, correlationId: "forged" }],
|
|
[
|
|
"invalid success flag",
|
|
{ ok: "yes", data: null, correlationId: "forged" },
|
|
],
|
|
[
|
|
"invalid failure error",
|
|
{ ok: false, error: null, correlationId: "forged" },
|
|
],
|
|
[
|
|
"throwing getter",
|
|
Object.defineProperty({}, "ok", {
|
|
enumerable: true,
|
|
get: () => {
|
|
throw new Error("result getter secret exposed");
|
|
},
|
|
}),
|
|
],
|
|
] as const)(
|
|
"sanitizes malformed command result: %s",
|
|
async (label, commandResult) => {
|
|
const commandId = `system.dispatch.malformed-result-${label.replaceAll(" ", "-")}`;
|
|
const audit = auditRecorder();
|
|
register(
|
|
baseCommand(commandId, {
|
|
input: z.object({}),
|
|
execute: async () => commandResult as never,
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId, input: {} },
|
|
dependencies({ audit: audit.writer }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTERNAL", messageKey: "errors.housekeeping.internal" },
|
|
});
|
|
expect(result.correlationId).toMatch(/^[0-9a-f-]{36}$/i);
|
|
expect(result.correlationId).not.toBe("forged");
|
|
expect(JSON.stringify(result)).not.toContain("secret exposed");
|
|
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["failure"]);
|
|
expect(audit.entries[0]?.correlationId).toBe(result.correlationId);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["empty", ""],
|
|
["text", "not-an-ip"],
|
|
["range", "999.1.1.1"],
|
|
[
|
|
"pathological",
|
|
{
|
|
toString: () => {
|
|
throw new Error("IP getter secret exposed");
|
|
},
|
|
},
|
|
],
|
|
])(
|
|
"rejects invalid server IP without using it in keys or evidence %s",
|
|
async (label, ipAddress) => {
|
|
const audit = auditRecorder();
|
|
const rateKeys: string[] = [];
|
|
let executed = false;
|
|
const commandId = `system.dispatch.invalid-ip-${label}`;
|
|
register(
|
|
baseCommand(commandId, {
|
|
input: z.object({}),
|
|
execute: async (context) => {
|
|
executed = true;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = await dispatchHousekeepingCommand(
|
|
{ commandId, input: {} },
|
|
dependencies({
|
|
ipAddress: ipAddress as never,
|
|
audit: audit.writer,
|
|
rateLimit: async (key) => {
|
|
rateKeys.push(key);
|
|
return true;
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTERNAL", messageKey: "errors.housekeeping.internal" },
|
|
});
|
|
expect(executed).toBe(false);
|
|
expect(rateKeys).toEqual([]);
|
|
expect(audit.entries).toMatchObject([
|
|
{
|
|
action: "housekeeping.command.dispatch",
|
|
target: "server-context",
|
|
outcome: "failure",
|
|
},
|
|
]);
|
|
expect(audit.entries[0]).not.toHaveProperty("ipAddress");
|
|
expect(JSON.stringify(audit.entries)).not.toContain("not-an-ip");
|
|
expect(JSON.stringify(audit.entries)).not.toContain("secret exposed");
|
|
},
|
|
);
|
|
it("canonicalizes IPv6 for command context, rate identity, and audit evidence", async () => {
|
|
const audit = auditRecorder();
|
|
const rateKeys: string[] = [];
|
|
let executionIp = "";
|
|
register(
|
|
baseCommand("system.dispatch.canonical-ip", {
|
|
input: z.object({}),
|
|
execute: async (context) => {
|
|
executionIp = context.ipAddress;
|
|
return ok(null, context.correlationId);
|
|
},
|
|
}),
|
|
);
|
|
|
|
await dispatchHousekeepingCommand(
|
|
{ commandId: "system.dispatch.canonical-ip", input: {} },
|
|
dependencies({
|
|
ipAddress: "2001:0DB8:0:0:0:0:0:1",
|
|
audit: audit.writer,
|
|
rateLimit: async (key) => {
|
|
rateKeys.push(key);
|
|
return true;
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(executionIp).toBe("2001:db8::1");
|
|
expect(rateKeys).toEqual([
|
|
"housekeeping-command:42:2001:db8::1:system.dispatch.canonical-ip",
|
|
]);
|
|
expect(audit.entries[0]?.ipAddress).toBe("2001:db8::1");
|
|
});
|
|
});
|