fix(tess): redact sensitive persistence and egress #723
93
apps/gateway/src/chat/chat.gateway-redaction.spec.ts
Normal file
93
apps/gateway/src/chat/chat.gateway-redaction.spec.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ChatGateway } from './chat.gateway.js';
|
||||
|
||||
const CONVERSATION_ID = 'conversation-1';
|
||||
const CANARY = 'token=sk_canary12345678';
|
||||
|
||||
type GatewayInternals = {
|
||||
clientSessions: Map<string, unknown>;
|
||||
relayEvent(client: unknown, conversationId: string, event: unknown): void;
|
||||
};
|
||||
|
||||
function buildGateway() {
|
||||
const brain = {
|
||||
conversations: {
|
||||
addMessage: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
};
|
||||
const agentService = {
|
||||
getSession: vi.fn().mockReturnValue(undefined),
|
||||
};
|
||||
const gateway = new ChatGateway(
|
||||
agentService as never,
|
||||
{} as never,
|
||||
brain as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
return { gateway: gateway as unknown as GatewayInternals, brain };
|
||||
}
|
||||
|
||||
describe('ChatGateway redaction boundary', (): void => {
|
||||
it('redacts an assistant delta before egress and before its persistence buffer', (): void => {
|
||||
const { gateway } = buildGateway();
|
||||
const client = {
|
||||
connected: true,
|
||||
id: 'client-1',
|
||||
data: { user: { id: 'user-1' } },
|
||||
emit: vi.fn(),
|
||||
};
|
||||
const session = {
|
||||
conversationId: CONVERSATION_ID,
|
||||
cleanup: vi.fn(),
|
||||
assistantText: '',
|
||||
toolCalls: [],
|
||||
pendingToolCalls: new Map(),
|
||||
scope: { userId: 'user-1', tenantId: 'tenant-1' },
|
||||
};
|
||||
gateway.clientSessions.set(client.id, session);
|
||||
|
||||
gateway.relayEvent(client, CONVERSATION_ID, {
|
||||
type: 'message_update',
|
||||
assistantMessageEvent: { type: 'text_delta', delta: CANARY },
|
||||
});
|
||||
|
||||
expect(client.emit).toHaveBeenCalledWith('agent:text', {
|
||||
conversationId: CONVERSATION_ID,
|
||||
text: '[REDACTED_SECRET]',
|
||||
});
|
||||
expect(session.assistantText).toBe('[REDACTED_SECRET]');
|
||||
expect(JSON.stringify(client.emit.mock.calls)).not.toContain(CANARY);
|
||||
});
|
||||
|
||||
it('persists only redacted assistant content with classifications', (): void => {
|
||||
const { gateway, brain } = buildGateway();
|
||||
const client = {
|
||||
connected: true,
|
||||
id: 'client-1',
|
||||
data: { user: { id: 'user-1' } },
|
||||
emit: vi.fn(),
|
||||
};
|
||||
gateway.clientSessions.set(client.id, {
|
||||
conversationId: CONVERSATION_ID,
|
||||
cleanup: vi.fn(),
|
||||
assistantText: CANARY,
|
||||
toolCalls: [],
|
||||
pendingToolCalls: new Map(),
|
||||
scope: { userId: 'user-1', tenantId: 'tenant-1' },
|
||||
});
|
||||
|
||||
gateway.relayEvent(client, CONVERSATION_ID, { type: 'agent_end' });
|
||||
|
||||
expect(brain.conversations.addMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
content: '[REDACTED_SECRET]',
|
||||
metadata: expect.objectContaining({ classifications: ['secret'] }),
|
||||
}),
|
||||
'user-1',
|
||||
);
|
||||
expect(JSON.stringify(brain.conversations.addMessage.mock.calls)).not.toContain(CANARY);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user