test(tess): cover streamed private key redaction
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
This commit is contained in:
@@ -94,6 +94,31 @@ describe('ChatGateway redaction boundary', (): void => {
|
|||||||
expect(JSON.stringify(client.emit.mock.calls)).not.toContain('canaryvalue123');
|
expect(JSON.stringify(client.emit.mock.calls)).not.toContain('canaryvalue123');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('holds a streamed private key until it can be redacted', (): void => {
|
||||||
|
const { gateway } = buildGateway();
|
||||||
|
const client = {
|
||||||
|
connected: true,
|
||||||
|
id: 'client-1',
|
||||||
|
data: { user: { id: 'user-1' } },
|
||||||
|
emit: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
gateway.relayEvent(client, CONVERSATION_ID, {
|
||||||
|
type: 'message_update',
|
||||||
|
assistantMessageEvent: { type: 'text_delta', delta: '-----BEGIN PRIVATE KEY-----\ncanary' },
|
||||||
|
});
|
||||||
|
gateway.relayEvent(client, CONVERSATION_ID, {
|
||||||
|
type: 'message_update',
|
||||||
|
assistantMessageEvent: { type: 'text_delta', delta: '\n-----END PRIVATE KEY-----' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.emit).toHaveBeenCalledWith('agent:text', {
|
||||||
|
conversationId: CONVERSATION_ID,
|
||||||
|
text: '[REDACTED_SECRET]',
|
||||||
|
});
|
||||||
|
expect(JSON.stringify(client.emit.mock.calls)).not.toContain('canary');
|
||||||
|
});
|
||||||
|
|
||||||
it('drops an oversized unterminated stream fragment rather than retaining it', (): void => {
|
it('drops an oversized unterminated stream fragment rather than retaining it', (): void => {
|
||||||
const { gateway } = buildGateway();
|
const { gateway } = buildGateway();
|
||||||
const client = {
|
const client = {
|
||||||
|
|||||||
@@ -858,6 +858,17 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const privateKeyStart = content.lastIndexOf('-----BEGIN');
|
||||||
|
if (privateKeyStart >= 0) {
|
||||||
|
const privateKey = content.slice(privateKeyStart);
|
||||||
|
if (/-----END(?: [A-Z]+)* KEY-----/.test(privateKey)) {
|
||||||
|
// Release the complete block in one pass so the full-block classifier can redact it.
|
||||||
|
retainedFrom = content.length;
|
||||||
|
} else {
|
||||||
|
retainedFrom = Math.min(retainedFrom, privateKeyStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return retainedFrom;
|
return retainedFrom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const SECRET_PATTERNS: RegExp[] = [
|
|||||||
/\b(?:authorization\s*:\s*)?bearer\s+[A-Za-z0-9._~+/-]+=*/gi,
|
/\b(?:authorization\s*:\s*)?bearer\s+[A-Za-z0-9._~+/-]+=*/gi,
|
||||||
/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
||||||
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
||||||
/-----BEGIN(?: [A-Z]+)* PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z]+)* PRIVATE KEY-----/g,
|
/-----BEGIN(?: [A-Z]+)* KEY-----[\s\S]*?-----END(?: [A-Z]+)* KEY-----/g,
|
||||||
/https?:\/\/[^\s?#]+[^\s]*[?&](?:token|key|secret|signature|sig)=[^\s&#]+/gi,
|
/https?:\/\/[^\s?#]+[^\s]*[?&](?:token|key|secret|signature|sig)=[^\s&#]+/gi,
|
||||||
];
|
];
|
||||||
const PII_PATTERNS: RegExp[] = [
|
const PII_PATTERNS: RegExp[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user