OSC 8 Terminal Hyperlinks: Framing, Shell Output, and Trust Boundaries
Emit OSC 8 terminal links with deliberate framing, validate shell output, diagnose emulator and multiplexer support, and keep URI targets trustworthy.
Terminal output is usually plain text, but many terminal emulators also interpret control sequences that attach presentation state to characters. OSC 8 is a convention for making displayed text resolve to a separate URI. A command can print “Open the report” while storing a URL as the click target. That is useful for concise paths, diagnostics, and log navigation, but it is not ordinary shell syntax, a POSIX guarantee, or proof that every terminal layer will preserve the link.
The important boundary is between the bytes a process writes and the behavior of the terminal emulator that eventually renders them. Shell printf can emit an OSC 8 sequence; it cannot confirm that the emulator recognized it or that the target is safe to open.
A hyperlink is a stream attribute
The commonly implemented OSC 8 form is:
OSC 8 ; parameters ; URI ST
visible text
OSC 8 ; ; ST
OSC is the operating-system-command control string, usually beginning with ESC ]. ST is the string terminator; this example uses the standard two-byte form, ESC \. The first sequence opens a link for subsequently painted text. The second has an empty URI and closes it. Parameters are optional; the OSC 8 implementation notes say cells with the same URI and same nonempty id are underlined together on mouseover.
This is a stream-state change, not an HTML element surrounding a document node. The terminal associates the target with cells as it paints output. Changing the active target or closing it affects later cells; it does not rewrite text already on the screen. Therefore, a short link should always include its closing sequence, and a full-screen application should restore the link state it owns on every normal exit path.
The displayed label and target are independent. Open the report may point to a web page, a local file, or another URI scheme. The terminal decides which schemes it supports and what application or action follows activation. A readable label is not evidence about the hidden target.
Emit a link with a fixed printf format
This small example uses a literal HTTPS target and label:
printf '\033]8;;https://example.org/docs\033\\Open the docs\033]8;;\033\\\n'
The octal escape \033 emits ESC, and \\ in the format emits the backslash that completes ESC \. The command writes an open sequence, the label, a closing sequence, and a newline. It uses printf rather than relying on shell-specific echo -e behavior. The sequence format and the resulting bytes can be inspected without asking a terminal to render them:
printf '\033]8;;https://example.org/docs\033\\Open the docs\033]8;;\033\\\n' |
od -An -tx1c
The output should begin with bytes 1b 5d 38 3b 3b (ESC ] 8 ; ;), then the URI, and later contain 1b 5c (ESC \). The close sequence begins 1b 5d 38 3b 3b 1b 5c. This pipe verifies the encoder only: od prints a byte representation, so it cannot test whether the terminal draws a clickable link. For that, run the first command directly in the terminal being evaluated.
Keep the format string constant if you later build links from variables. A constant format prevents data containing % from becoming printf directives, but %s does not sanitize terminal control bytes. Validate the URI as a URI, percent-encode bytes that the convention requires to be encoded, reject control characters, and avoid passing untrusted labels or targets straight through to the terminal. Do not use a hidden link to make an unreviewed command, custom URI handler, or credential-bearing URL look trustworthy.
Separate support from successful byte output
The shell’s successful printf status means it wrote output; it is not an OSC 8 acknowledgment. The OSC 8 convention has no in-band query or response that reports whether a terminal accepted a link. Capability knowledge can come from out-of-band metadata when a particular layer provides it, but programs still need implementation knowledge or a controlled display test rather than an assumption based only on successful writes.
WezTerm documents explicit OSC 8 hyperlinks and provides a direct printf example. The OSC 8 implementation notes describe the same start and close forms and say that terminal support currently has no detection mechanism. These references describe a convention, not a guarantee that every terminal supports it; the protocol may evolve, so verify the installed stack. $TERM is a terminal-type label, not a universal runtime negotiation for every newer extension.
Multiplexers can be another protocol boundary. A reviewed tmux source snapshot defines a separate hyperlinks terminal feature and an Hls terminfo extension; that capability string is conditional on the build’s curses library exposing the required extended capabilities, and defaults vary by terminal entry. Link support can therefore depend on the multiplexer and terminal description, not just the shell that emitted the bytes. Test the actual route: directly in the emulator, inside each supported tmux or screen session, and over SSH if remote output is part of the workflow. Do not force an unrelated $TERM value just to make links appear; a false capability description can mislead other programs about the terminal contract.
Implicit URL detection is a different feature. An emulator may make a visible https://... string clickable by matching screen text. OSC 8 instead lets the application supply a URI that is not part of the visible label. A plain URL that happens to be clickable does not prove OSC 8 works, and a short OSC 8 label need not look like a URL at all.
Treat the URI as actionable input
OSC 8 does not contain an “open now” operation; the emulator decides what gesture or confirmation, if any, activates the URI. Still, the target can be more consequential than the visible text suggests. The specification’s security notes call out custom URI schemes and suggest that emulators expose the target and use deliberate activation or confirmation; these are recommendations, not universal guarantees. A terminal may delegate a URI to a browser, file viewer, or registered application. Do not assume those policies are identical across systems.
Prefer a reviewed https target for ordinary documentation links. Avoid placing tokens or other secrets in a URL, because a terminal scrollback buffer, session recorder, or copied transcript may preserve it. Treat file: links carefully, especially when output originates on a remote host: a file URI is not automatically a safe or portable way to refer to that host’s files. If logs or command output include attacker-controlled text, do not wrap it in OSC 8 without validating both the URI and label. The shell format string being quoted protects printf’s grammar; it does not neutralize escape bytes inside the values.
Diagnose the exact rendering path
Use a known, static example and test one layer at a time:
- Run the byte-inspection command through
odand verify both the opening and closing sequences. - Run the display example directly in a terminal emulator. Hover or use that emulator’s documented activation gesture, and inspect the target it presents before opening it.
- Repeat inside the exact multiplexer, remote session, or pager where the real program runs. If direct output works but nested output does not, investigate that intermediate layer’s version, terminal feature configuration, and terminfo data.
- If the label appears but is not clickable, support may be absent or the sequence may have been stripped. A successful byte check cannot distinguish those cases.
- If unrelated later text remains linked, inspect for a missing close sequence. If the wrong destination opens, compare the URI bytes with the visible label; OSC 8 intentionally keeps them separate.
These checks distinguish encoding from rendering and rendering from activation. Test each supported terminal and multiplexer combination before making OSC 8 output part of an operational interface, and retain readable plain-text output for logs and terminals that do not implement it.
Related:
- Terminal Emulators vs. Shells: What Each One Actually Does
- Bracketed Paste Mode: How Terminals and Shells Mark Pasted Text Safely
Sources:
- OSC 8 Hyperlinks in Terminal Emulators proposal and implementation notes, pinned revision
- WezTerm 20240203 release: Hyperlinks and explicit OSC 8 output
- tmux upstream terminal feature definitions (
hyperlinksandHls) at commitae2fb3797c0f1b1cc5a31a4ecb310d44cd2613ac - The Open Group POSIX
printfutility specification - The Open Group POSIX
echoutility specification