security: implement deceptive URL detection and disclosure in tool confirmations (#19288)

This commit is contained in:
Emily Hedlund
2026-02-20 15:21:31 -05:00
committed by GitHub
parent 49b2e76ee1
commit a01d7e9a05
4 changed files with 337 additions and 7 deletions
@@ -88,6 +88,122 @@ describe('ToolConfirmationMessage', () => {
unmount();
});
it('should display WarningMessage for deceptive URLs in info type', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'info',
title: 'Confirm Web Fetch',
prompt: 'https://täst.com',
urls: ['https://täst.com'],
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
availableTerminalHeight={30}
terminalWidth={80}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
expect(output).toContain('Original: https://täst.com');
expect(output).toContain(
'Actual Host (Punycode): https://xn--tst-qla.com/',
);
unmount();
});
it('should display WarningMessage for deceptive URLs in exec type commands', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
command: 'curl https://еxample.com',
rootCommand: 'curl',
rootCommands: ['curl'],
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
availableTerminalHeight={30}
terminalWidth={80}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
expect(output).toContain('Original: https://еxample.com/');
expect(output).toContain(
'Actual Host (Punycode): https://xn--xample-2of.com/',
);
unmount();
});
it('should exclude shell delimiters from extracted URLs in exec type commands', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
command: 'curl https://еxample.com;ls',
rootCommand: 'curl',
rootCommands: ['curl'],
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
availableTerminalHeight={30}
terminalWidth={80}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
// It should extract "https://еxample.com" and NOT "https://еxample.com;ls"
expect(output).toContain('Original: https://еxample.com/');
// The command itself still contains 'ls', so we check specifically that 'ls' is not part of the URL line.
expect(output).not.toContain('Original: https://еxample.com/;ls');
unmount();
});
it('should aggregate multiple deceptive URLs into a single WarningMessage', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'info',
title: 'Confirm Web Fetch',
prompt: 'Fetch both',
urls: ['https://еxample.com', 'https://täst.com'],
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
availableTerminalHeight={30}
terminalWidth={80}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
expect(output).toContain('Original: https://еxample.com/');
expect(output).toContain('Original: https://täst.com/');
unmount();
});
it('should display multiple commands for exec type when provided', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'exec',