Skip to content

Parameterized OSC 8 hyperlinks leak control payload when wrapped #62

Description

@Stanzilla

Description

wrapAnsi() does not recognize OSC 8 hyperlinks with a non-empty parameter field, such as a generated hyperlink ID:

ESC ] 8 ; id=md-test ; https://example.com BEL

When the link is hard-wrapped, the parameter and URI payload can be treated as visible text and split across output lines. In a terminal this exposes text such as ]8;id=md-...;https://... instead of rendering one clean clickable link.

Minimal reproduction

import wrapAnsi from 'wrap-ansi';

const input = '\u001B]8;id=md-test;https://example.com\u0007abcdefghij\u001B]8;;\u0007';
const output = wrapAnsi(input, 5, {hard: true});

console.log(JSON.stringify(output));

The visible label should wrap to abcde\nfghij, with a complete OSC 8 close/open pair around the line break and id=md-test preserved on the reopened link. Instead, current main inserts newlines inside the OSC control payload.

Cause

The OSC 8 syntax is OSC 8 ; params ; URI BEL|ST, but the hyperlink parser and wrapWord() detector currently recognize only the empty-parameter prefix ]8;;. A parameterized opener is therefore parsed as a generic escape, and part of its payload is subsequently counted and wrapped as visible text.

Proposed fix

A reviewed fix and regression test are available here:

The change recognizes the parameter field and retains it when hyperlinks are closed and reopened across wrapped lines. It preserves existing empty-parameter BEL/ST behavior.

The repository currently limits pull-request creation to collaborators, so I could not open the prepared branch as a PR.

Verification

  • focused regression passes
  • all existing hyperlink tests pass
  • 54 relevant tests pass
  • adversarial probes covered BEL/ST terminators, colon-delimited parameters, semicolons in URIs, multiple links, malformed input, narrow widths, and the exact long-URL shape

Downstream context

This was isolated while investigating github/copilot-cli#4356.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions