diff --git a/CHANGELOG.md b/CHANGELOG.md index df4963b..a52db92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed - macOS cookie extraction now supports Brave keychain storage (#40) — thanks @gakonst. +- Terminal hyperlinks now sanitize control characters before emitting OSC 8 sequences (#29) — thanks @mafulafunk. - Following/followers pagination now guards repeat cursors and standardizes JSON output (#28) — thanks @malpern. - Lists GraphQL feature flags updated to prevent 400s (#27) — thanks @zheli. - Likes pagination now follows cursors and avoids stalling on duplicate pages (#12) — thanks @titouv. diff --git a/src/lib/output.ts b/src/lib/output.ts index d492f7d..7d6779f 100644 --- a/src/lib/output.ts +++ b/src/lib/output.ts @@ -106,8 +106,10 @@ export function hyperlink(url: string, text?: string, cfg?: OutputConfig): strin if (!cfg?.hyperlinks) { return displayText; } + const safeUrl = url.replaceAll('\x1b', '').replaceAll('\x07', ''); + const safeText = displayText.replaceAll('\x1b', '').replaceAll('\x07', ''); // OSC 8 hyperlink: \x1b]8;;URL\x07TEXT\x1b]8;;\x07 - return `\x1b]8;;${url}\x07${displayText}\x1b]8;;\x07`; + return `\x1b]8;;${safeUrl}\x07${safeText}\x1b]8;;\x07`; } export function formatTweetUrlLine(tweetId: string, cfg: OutputConfig): string { diff --git a/tests/output.test.ts b/tests/output.test.ts index 3a43b81..2f33882 100644 --- a/tests/output.test.ts +++ b/tests/output.test.ts @@ -111,4 +111,11 @@ describe('output', () => { expect(result).toContain('Click here'); expect(result).toContain('\x1b]8;;https://x.com/test\x07'); }); + + it('hyperlink strips OSC control characters from url and text', () => { + const cfg = { plain: false, emoji: true, color: true, hyperlinks: true }; + const result = hyperlink('https://x.com/\u001btest\u0007', 'Hi\u001b\u0007', cfg); + expect(result).not.toContain('\u001btest\u0007'); + expect(result).not.toContain('Hi\u001b\u0007'); + }); });