fix(security): harden channel auth path checks and exec approval routing

This commit is contained in:
Peter Steinberger
2026-02-26 12:45:56 +01:00
parent b096ad267e
commit da0ba1b73a
18 changed files with 314 additions and 6 deletions

View File

@@ -52,6 +52,10 @@ export function createExecApprovalHandlers(
agentId?: string;
resolvedPath?: string;
sessionKey?: string;
turnSourceChannel?: string;
turnSourceTo?: string;
turnSourceAccountId?: string;
turnSourceThreadId?: string | number;
timeoutMs?: number;
twoPhase?: boolean;
};
@@ -91,6 +95,12 @@ export function createExecApprovalHandlers(
agentId: p.agentId ?? null,
resolvedPath: p.resolvedPath ?? null,
sessionKey: p.sessionKey ?? null,
turnSourceChannel:
typeof p.turnSourceChannel === "string" ? p.turnSourceChannel.trim() || null : null,
turnSourceTo: typeof p.turnSourceTo === "string" ? p.turnSourceTo.trim() || null : null,
turnSourceAccountId:
typeof p.turnSourceAccountId === "string" ? p.turnSourceAccountId.trim() || null : null,
turnSourceThreadId: p.turnSourceThreadId ?? null,
};
const record = manager.create(request, timeoutMs, explicitId);
record.requestedByConnId = client?.connId ?? null;

View File

@@ -493,6 +493,56 @@ describe("exec approval handlers", () => {
expect(resolveRespond).toHaveBeenCalledWith(true, { ok: true }, undefined);
});
it("forwards turn-source metadata to exec approval forwarding", async () => {
vi.useFakeTimers();
try {
const manager = new ExecApprovalManager();
const forwarder = {
handleRequested: vi.fn(async () => false),
handleResolved: vi.fn(async () => {}),
stop: vi.fn(),
};
const handlers = createExecApprovalHandlers(manager, { forwarder });
const respond = vi.fn();
const context = {
broadcast: (_event: string, _payload: unknown) => {},
hasExecApprovalClients: () => false,
};
const requestPromise = requestExecApproval({
handlers,
respond,
context,
params: {
timeoutMs: 60_000,
turnSourceChannel: "whatsapp",
turnSourceTo: "+15555550123",
turnSourceAccountId: "work",
turnSourceThreadId: "1739201675.123",
},
});
for (let idx = 0; idx < 20; idx += 1) {
await Promise.resolve();
}
expect(forwarder.handleRequested).toHaveBeenCalledTimes(1);
expect(forwarder.handleRequested).toHaveBeenCalledWith(
expect.objectContaining({
request: expect.objectContaining({
turnSourceChannel: "whatsapp",
turnSourceTo: "+15555550123",
turnSourceAccountId: "work",
turnSourceThreadId: "1739201675.123",
}),
}),
);
await vi.runOnlyPendingTimersAsync();
await requestPromise;
} finally {
vi.useRealTimers();
}
});
it("expires immediately when no approver clients and no forwarding targets", async () => {
vi.useFakeTimers();
try {