COMPATIBILITY: WHAT RUNS IN IT

Everything you read runs. Nothing you read can act.

secure-terminal has two modes. CLI mode (the default, line-oriented) interprets no screen escapes and no vertical or absolute cursor movement: a program's output is reduced to printable ASCII plus safe, contrast-guarded colour, and the scrollback is append-only and tamper-evident -- once a line has scrolled past it can no longer be moved or erased, so a hostile log cannot repaint your screen or rewrite what you have already read. The four line-local edits a shell's line editor needs (cursor forward/back, cursor-to-column, erase-in-line) are honoured within the line being written, so a program can redraw its own current line exactly as a \r progress bar does -- see what a program can still do to its own line. TUI mode (opt-in, per tab) interprets escapes inside an isolated in-memory screen so full-screen programs render, while every cell is still character-filtered and the terminal still answers no query and touches no clipboard. The tables below are backed by real tests, not claims: the top programs run in both modes, and every protocol, control byte and OSC code is accounted for.

How this was tested. CLI mode was exercised through the shipped secure-terminal-cli wrapper; TUI mode through the exact pyte screen engine secure-terminal drives, and the real Qt GUI was driven headless on an isolated display to confirm it renders (vim and htop, colour included). Program popularity is the Debian popularity-contest install rank. Every "works" below means the program was run and its output or interface was captured and checked, on Debian trixie.

Hostile byte streams: the difference, shown

Pipe a burst of untrusted bytes into a normal terminal - random data, a program's output, a hostile log - and it garbles the screen, sticks it in the wrong colours or character set, and can silently rewrite your window or tab title to attacker-chosen text. secure-terminal reduces the identical bytes to inert printable ASCII: no title change, no charset tricks, no clipboard write, and only its own bounded, contrast-guarded colour - never the attacker's. We measured this against ten terminal emulators, with screenshots - every one was corrupted, secure-terminal was not.

The 10 most popular command-line programs

Line-oriented tools run unchanged in CLI mode (the default). Their output is intact, colour and all: 24-bit colour (from ls, grep, git, ...) renders under a contrast guard, so nothing can be painted the same colour as the background to hide it, and carriage-return progress bars (git/curl/wget/rsync) redraw in place. All 16 below were run and their output confirmed. (Colour is on by default; turn it off with the Colors toggle or NO_COLOR.)

Program (package)Popcon rankCLI mode (default)
ls, cat, cp (coreutils)7Works. Output and --color intact.
find (findutils)13Works. Pure line output.
tar16Works. Listings and messages intact.
grep20Works. Matches and match-highlight colour shown.
gzip, zcat22Works. Round-trips cleanly.
sed25Works. Stream output intact.
bash35Works. Interactive prompt is basic in CLI mode; richer readline editing needs TUI mode.
diff, cmp (diffutils)51Works. Diff output and colour intact.
awk (mawk)61Works. Pure line output.
ps, free, uptime (procps)62Works. Columns intact.
ssh (openssh-client)80Works for line-oriented use; a remote full-screen program needs TUI mode.
wget130Works. Carriage-return progress bar redraws in place.
gpg (gnupg)239Works. Output intact.
curl304Works. Progress meter redraws in place.
git398Works. Colour and progress render; pager runs (PAGER=cat).
rsync557Works. Progress redraws in place.

The one thing CLI mode does not do is interpret cursor-positioning escapes, so a full-screen redraw (an editor, a live dashboard) needs TUI mode. Ordinary colour and single-line progress are honoured, so everyday command output looks the way you expect.

What a program can still do to its own line

Being precise about the one thing CLI mode does not prevent, because a guarantee you have to guess at is worth less than a narrow one you can rely on.

Why they are honoured at all. These are the escapes a shell's line editor emits to redraw the line you are typing. Without them, tab completion prints a mangled duplicate instead of updating the line, Ctrl+R history search and Ctrl+A/Ctrl+E editing leave debris, and a \r progress bar stacks one line per update. They are the price of a usable shell, kept as small as it can be.

So CLI mode honours four line-local escapes: cursor forward, cursor back, cursor-to-column, and erase-in-line. All four are clamped to the line currently being written. Vertical and absolute movement (cursor-up, row;column addressing) is stripped, so nothing can reach a line you have already scrolled past, and a whole-screen clear is a deliberate no-op.

The consequence, stated plainly: a program can overwrite what it itself just printed on the current line, exactly as a \r progress bar does. Feed a terminal STATUS=FAIL, then an erase-line, then STATUS=PASS, and the line reads STATUS=PASS. That is not an escape being "interpreted" in the dangerous sense: no escape byte reaches the display, the cursor cannot leave the line, and the scrollback above is untouched. But it does mean transcript integrity is per-line: everything already scrolled past is append-only, while the line still being written is the program's to redraw.

To be equally plain about the limit: the overwritten text is gone. Switching display mode replays the retained output through the same line editor, and a saved transcript records the rendered document, so neither brings STATUS=FAIL back. If you need a byte-exact record of what a program emitted, capture its output with stcat or pipe it to a file - there the erase is neutralised to a visible placeholder instead of being applied.

Or turn it off. View -> Line editing (config key line_edits, default on) drops the four ops entirely, so output becomes append-only against escapes and nothing can redraw a line at all. The trade is real: tab completion and progress bars then append a line per update instead of redrawing in place. An administrator can lock the key either way. Carriage return and backspace are raw control bytes rather than escapes, so they stay honoured regardless - this is append-only against escapes, not against every byte.

The contrast guard: colour that can never hide text

Honouring a program's colours reopens an old trick: paint text the same colour as the background and it becomes invisible - a fake [ OK ] that hides a failure, a prompt that conceals what you are really about to run. secure-terminal keeps colour and closes that trick with a contrast guard, on in both modes whenever colour is on.

Before drawing any cell, the terminal compares the foreground and the actual background luminance. If they are too close to read, it discards the program's foreground and substitutes a fixed high-contrast one - black on a light background, near-white on a dark one - so the text is always legible. The substitute is a built-in colour, never one the program chose, so the guard cannot be defeated by moving the default colours together (for example via an OSC 10/11 palette change). The threshold is deliberately low, so ordinary combinations like red on a near-black background still render exactly as intended; only genuinely unreadable, deceptive pairings are corrected.

The net effect: every colour a program can produce stays readable, and no colour can conceal what is on screen. You saw this on the comparison page - the crafted log's stuck red-on-red, meant to hide text, was forced to a plainly readable form. Turn colour off entirely with the Colors toggle, NO_COLOR, or TERM=dumb.

24-bit truecolour: it renders everything, in full colour

Neutralising hostile bytes does not mean a plain display. secure-terminal renders full 24-bit truecolour. Below are two safe, display-only boards - a cat art.payload sunset scene and a cat gradient.payload 24-bit gamut slice (hue across, lightness down, plus a greyscale ramp). Both are built from SGR truecolour and the half-block glyph (U+2580) only - no cursor moves, no screen clear, no OSC, no escapes to neutralise - so both are safe to cat. Switch the view to see the honest per-mode outcome: Show paints the colour field; Box and Detail are the strict CLI defaults, where the half-block glyph is itself neutralised - shown as an inert risk-coloured box, or named inline as <U+2580 UPPER HALF BLOCK> - so no colour field is drawn. That is the point: colour renders where a program simply prints it, and even a wall of a single glyph is still character-filtered.

Choose the secure-terminal view. Both boards switch together.
secure-terminal (CLI, Box) - the strict default: the sunset scene is neutralised, every U+2580 half-block an inert risk-coloured box, so no colour field is drawn.
secure-terminal in CLI Box mode on the sunset art board: the truecolour fill is neutralised to a grid of inert risk-coloured boxes, one per half-block glyph.
secure-terminal (CLI, Detail) - each byte named inline as <U+2580 UPPER HALF BLOCK>: the half-block glyph is expanded to its Unicode name, not painted.
secure-terminal in CLI Detail mode on the sunset art board: every half-block byte expanded inline to the text U+2580 UPPER HALF BLOCK instead of a colour.
secure-terminal (CLI, Show) - the colour board: full 24-bit truecolour, a display-only sunset scene rendered from a safe cat of half-block art.
secure-terminal in CLI Show mode rendering a smooth 24-bit-colour sunset-beach scene built from half-block glyphs.
secure-terminal (TUI, Box) - opt-in full-screen layout, yet every half-block cell is still an inert risk-coloured box. Detail needs CLI mode, so TUI offers Box and Show.
secure-terminal in TUI Box mode on the sunset art board: full-screen layout allowed, but the truecolour fill is still neutralised to inert risk-coloured boxes.
secure-terminal (TUI, Show) - the same truecolour sunset scene in opt-in full-screen mode.
secure-terminal in TUI Show mode rendering the same 24-bit-colour sunset-beach scene full-screen.
secure-terminal (CLI, Box) - the strict default neutralises the 24-bit gamut slice: every U+2580 half-block an inert risk-coloured box, no colour field drawn.
secure-terminal in CLI Box mode on the 24-bit gradient board: the colour gamut is neutralised to a grid of inert risk-coloured boxes.
secure-terminal (CLI, Detail) - each half-block byte named inline as <U+2580 UPPER HALF BLOCK>, the safe default view.
secure-terminal in CLI Detail mode on the 24-bit gradient board: every half-block byte expanded inline to the text U+2580 UPPER HALF BLOCK.
secure-terminal (CLI, Show) - the colour board: a 24-bit gamut slice - hue across, lightness down, plus a greyscale ramp - smooth where a 256-colour terminal bands.
secure-terminal in CLI Show mode rendering a smooth 24-bit-colour gamut slice: hue varying across, lightness down, with a greyscale ramp along the bottom.
secure-terminal (TUI, Box) - full-screen layout, every half-block cell still an inert risk-coloured box.
secure-terminal in TUI Box mode on the 24-bit gradient board: full-screen layout allowed, but the colour gamut is still neutralised to inert risk-coloured boxes.
secure-terminal (TUI, Show) - the same 24-bit gamut slice in opt-in full-screen mode.
secure-terminal in TUI Show mode rendering the same smooth 24-bit-colour gamut slice full-screen.

Two safe, display-only boards (the harness truecolor-art.py sunset scene and truecolor-gradient.py gamut slice), each SGR truecolour plus the U+2580 half-block glyph and newlines only. Click any shot for full size.

A restricted terminfo that tells programs the truth

The mode itself decides what programs are told, over the ordinary terminfo protocol - there is nothing to turn on. A CLI-mode tab reports TERM=secure-terminal, a restricted entry, so a well-behaved program adapts at the source: a pager, a completion menu or a progress bar that would otherwise address the cursor or clear the screen instead prints plain line-by-line output it knows this terminal can show - cleaner to read, with less stripped-escape noise, and the terminal is never even asked to do something it refuses. A TUI-mode tab reports the fuller xterm-256color, so full-screen programs and remote hosts (over ssh) get the entry they need.

The secure-terminal entry inherits the correct colour and SGR capabilities from xterm-16color, then cancels every capability CLI mode does not render - cursor addressing, the alternate screen, clear/erase-display, insert/delete line and character, scroll regions, save/restore cursor, tab stops, the title and status line, mouse - and the u6/u7/u8/u9 device-query capabilities. So a well-behaved program emits only what this terminal actually shows (line-local moves plus colour) and never probes a terminal that will not answer. It is defence in depth, not the guarantee: the renderer strips those sequences regardless of TERM; this just stops them being generated.

Switching a tab between CLI and TUI re-exports TERM to the running shell as a plain, visible export TERM=... command - the shell re-reads terminfo live, so no restart and no lost shell state. The switch is refused (with a message) while a program is running, since it owns the terminal. The entry is a short, readable source file: secure-terminal.ti.

Three terminfo entries, three points on a spectrum: dumb (the classic "line-oriented, no cursor control" entry every system already ships), secure-terminal (CLI mode's safe-but-comfortable subset), and xterm-256color (TUI mode's full standard entry). CLI's two entries WITHHOLD cursor addressing so a program cannot redraw; TUI advertises the full entry and instead confines what a program draws to a neutralised pyte grid. Note that dumb reaches the core safety by having almost nothing - no colour, no bold, not even cursor-left, so an interactive shell cannot edit within a line - while secure-terminal keeps the safe, useful, line-local half of a real terminal and cancels only the dangerous half.

The entry advertises...
(all three are values secure-terminal sets for TERM)
TERM=dumb TERM=secure-terminal
(CLI mode)
TERM=xterm-256color
(TUI mode)
Cursor addressing / alternate screen (full-screen redraw) RefusedRefused Advertised *
Tab-completions ListedListedDrawn as a cursor menu
Answers device queries (u6-u9) NoNoYes *
Colours + attributes (bold, dim, underline, reverse) None16-colour256-colour
In-line editing: cursor left/right, erase-to-EOL (cub/cuf/el) NoYesYes
Named keys: arrows, Home/End, function keys NoYesYes
In the base terminfo db - resolves on any host, incl. over ssh YesNo (local)Yes

* TUI mode advertises the full entry on purpose - a full-screen program needs cursor addressing - and is kept safe NOT by withholding capabilities but by interpreting every escape into a confined pyte screen grid where each glyph is neutralised. A capability it advertises is one the renderer contains, not a hole.

The progression is the whole design: dumb strips everything for maximum portability; secure-terminal keeps colour and line editing while still refusing the full-screen and probe capabilities CLI mode cannot safely draw; xterm-256color restores the full set for TUI mode, where the confined grid - not the terminfo - is what keeps it safe. That is why a shell you ssh into uses TUI's portable xterm-256color, while a local CLI shell uses secure-terminal. Either way, safety rests on the renderer, never on TERM.

Isn't a custom TERM name a fingerprint? Only a weak one, and not a property worth claiming. A remote host is told the string "secure-terminal", but terminals are identified far more reliably by behaviour - how they answer device queries (DA/DSR), their size, their quirks - than by a TERM value that many emulators share anyway. If anything CLI mode is harder to probe: it answers no queries at all (the u6-u9 capabilities are cancelled and every escape is stripped). So the honest cost of the custom entry is not fingerprinting - it is compatibility: a remote host has no secure-terminal terminfo, so a curses program there sees an unknown terminal. That is the reason to ssh in TUI mode, whose xterm-256color every host already has.

Does the richer entry widen the attack surface for hostile DATA? No. Malicious output - a log full of embedded escapes that you cat - emits raw bytes WITHOUT consulting terminfo, so it can send the exact same sequences whichever entry is advertised; a richer TERM hands it nothing. And the neutralising renderer never reads TERM at all (its only environment read is NO_COLOR): it strips or contains the same fixed set of sequences regardless. So cat hostile.log renders byte-for-byte identically under secure-terminal and under dumb - the OSC 0 title hijack stripped, the stuck colour and the DEC line-drawing shift neutralised to plain text. The terminfo entry only changes what well-behaved programs choose to emit; it grants hostile data no extra reach.

Fingerprintability: what a program can learn by asking

The TERM name is only a weak, passive hint. Terminals are identified far more reliably by active probing: a program - or a remote host you ssh into - sends a query escape and reads the answer. Each answer is a datum that pins down your terminal and its configuration. The table is produced by a reproducible probe, secure-terminal-fingerprint (in dist-ai): it fires each standard query at every terminal under a nested Xvfb and records the reply. A Leaks cell shows a real captured response; Silent means the terminal answered nothing.

QueryWhat an answer leaksA typical terminal (xterm) secure-terminal (CLI + TUI)
Primary device attributes (DA1)the feature set / emulator family Leaks ?64;1;2;6;9;...Silent
Secondary device attributes (DA2)terminal type + firmware version Leaks >41;398;0 (xterm v398)Silent
Tertiary device attributes (DA3)a unit-ID string Leaks !|00000000Silent
Name + version (XTVERSION)the exact emulator NAME and version Leaks XTerm(398)Silent
Cursor position (DSR 6)the cursor row/column Leaks 1;1RSilent
Terminal status (DSR 5)that the terminal is present Leaks 0nSilent
Foreground colour (OSC 10)your exact text colour (theme) Leaks rgb:0000/0000/0000Silent
Background colour (OSC 11)your exact background colour (theme) Leaks rgb:ffff/ffff/ffffSilent
Palette colour (OSC 4)your exact palette (theme) Leaks rgb:cdcd/0000/0000Silent
Synchronised-output mode (DECRQM 2026)which private modes it supports Leaks ?2026;0Silent
Text-area / cell pixel size (CSI 14 t / 16 t)window + font pixel size Silent (this build)Silent
Report window title (CSI 21 t)the window TITLE text Silent (this build)Silent
Total answered 10 of 130 of 13

A typical terminal hands a program its name, version, feature set and your exact theme colours on request; secure-terminal answers nothing, in both CLI and TUI mode, because it strips every escape and never writes a reply back (the query capabilities u6-u9 are cancelled too). So the honest picture is the reverse of the usual worry: the custom TERM name is a weak passive label, while against the strong vector - a program actively interrogating the terminal - secure-terminal is the LEAST fingerprintable of the three, not the most. Re-run the probe yourself with secure-terminal-fingerprint.

The 10 most popular full-screen (TUI) programs

Curses and full-screen programs are driven entirely by escape sequences, so they need the opt-in TUI mode. There they render through the confined screen model; every one below was run and its interface captured. How box-drawing and other non-ASCII glyphs appear depends on the unicode display mode: the default Detail mode names each non-ASCII character inline as <U+XXXX NAME>, Box mode shows each as a compact box coloured by risk class (which you copy or save as an ASCII _), and Show mode renders the real glyphs (line-drawing borders and the like), each tinted by risk class so a homoglyph confusable with ASCII stands out - while the invisible, bidi and control classes, which have no visible glyph, stay neutralised in every mode. Either way, layout and text are faithful, and the terminal still answers no query and cannot set the title or touch the clipboard.

Program (package)Popcon rankTUI mode (opt-in)
top (procps)62Renders. Live process grid, header, load average.
less68Renders. Pager and key navigation.
nano129Renders. Editor, title and shortcut bars.
vi (vim-tiny)134Renders. Full editor; zero dropped frames.
htop1115Renders. Meters, colour, process list.
vim1189Renders. Full editor; zero dropped frames.
screen1918Renders. Multiplexer; pane content and status.
mc (Midnight Commander)2017Renders. Both panels, menu and function-key bars; borders per the unicode mode.
tmux2158Renders. Panes and the bottom status bar; zero dropped frames.
w3m2547Renders. Text-mode web page layout.
ncdu (bonus)3600Renders. Disk-usage browser.
btop (bonus)4017Renders. Full resource dashboard, colour and meters.

Honorable mention: Claude Code (the claude CLI). Not a Debian package (it ships via npm), so it is not in the popularity list above - included because it is one of the most complex TUIs in common use: a Node.js/Ink renderer that leans hard on the alternate screen, cursor addressing, colour and capability queries. It renders in TUI mode; claude --print / --version work in CLI mode.

The trade-off, stated plainly. Because TUI mode lets a program position the cursor, a program you run can draw a misleading interface or overwrite a line you already read - so only run programs you trust in it. A good habit, since the mode is per tab: keep a dedicated TUI tab (or a few) for the trusted full-screen programs you run, and read untrusted output in a separate CLI-mode tab, so the two never share a screen. What TUI mode still cannot do is set the window title, read or write the clipboard, open a hyperlink escape, or smuggle an invisible character onto the screen: those stay closed in both modes.

Conformance suites, by mode

The public terminal conformance suites test how faithfully an emulator implements the VT/xterm escape repertoire. secure-terminal's two modes relate to them differently: CLI mode interprets nothing on purpose, so it "fails" conformance by design (the security property, not a bug); TUI mode renders only the safe subset its confined pyte engine implements, and still answers no query. We track these suites not to pass them but as a corpus of every sequence a real program might emit, to confirm our stripping (CLI) and our subset (TUI) behave. Each was reviewed for malicious content before use.

SuiteWhat it checksIn CLI modeIn TUI mode
esctest2xterm/VT escape behaviour, exhaustively, by driving a terminal and reading its replies.Every escape stripped; it answers none of the query probes (by design).Drawing sequences render via pyte; the query probes still get no reply.
pyte testsThe screen-model semantics of the engine secure-terminal uses for TUI mode.Verified bypassed: a conformance-harness check asserts the pyte screen is never even instantiated in CLI mode, so there is no VT state to attack.Directly relevant: these are the semantics our TUI grid inherits.
libvterm testsA reference state machine for VT parsing and rendering.All sequences stripped; used to confirm what we drop.Cross-checks our pyte subset against a known-good parser.
vttestThe classic interactive VT100/VT220 test; requires a human at the keyboard.Everything stripped (used manually as a reference).Drawing tests render via pyte; interactive, not in CI.

These suites are references for the spec surface, not attacks. An automated conformance invariant distilled from them runs in the project's continuous-integration on every change: a reflection-oracle test feeds a 387-sequence spec-surface query corpus (the DA/DSR/DECRQM, OSC dynamic-colour and OSC 52-read, XTGETTCAP, DECRQSS and XTWINOPS query and report sequences) through the sanitizer and asserts zero bytes are ever written back to the pty - in both CLI and TUI mode.

The attack corpus (separate from conformance)

Conformance suites are not attacks - they check ordinary rendering. The security corpus of actual, publicly-disclosed terminal attacks lives separately in the terminal-poc-corpus: hex-encoded, canary-forked, sandbox-only proofs across every attack class, each asserting that secure-terminal neutralises it. That corpus is what proves the guarantees; the conformance suites above only confirm ordinary programs still render. For the full picture of every automated check - the corpus, the spec-surface invariant, the unit, GUI, property and fuzz suites, AppArmor and the resilience test - see the AI Review page.

Environment variables

The program you run inherits your environment, with deliberate exceptions: the size is taken from the kernel, colour is advertised as a fixed value, and every host-emulator fingerprint variable is scrubbed so a child (or a host it SSHes into) cannot learn which terminal you use or correlate your session.

Variablesecure-terminal behaviourWhat it is for / effect
TERMSet to xterm-256color, or the opt-in custom secure-terminal terminfo that advertises exactly the supported subset. The secure-terminal-cli wrapper uses dumb.Programs look up their terminfo (what escapes the terminal understands) by TERM. A program then only emits what we render; anything else is stripped anyway.
COLORTERMForced to truecolor - a fixed value, never inherited, so it cannot be a fingerprint.Programs check it to decide whether to emit 24-bit colour; with it set they do, and the contrast guard renders it safely. Nothing breaks if a program ignores it.
PAGERDefaulted to cat.Programs pipe long output through $PAGER; cat means the pager adds no escapes of its own. Set your own to use less, etc.
NO_COLORHonoured (per no-color.org): if set to any value, colour is disabled.The standard opt-out; a program that honours it emits no colour.
LINES, COLUMNSRemoved.Programs read the terminal size; the true size comes from the kernel (TIOCSWINSZ), so a stale env value cannot make an app mis-wrap.
TERM_PROGRAM, TERM_PROGRAM_VERSIONScrubbed (anti-fingerprint).Apps/scripts sniff these to detect the host emulator; scrubbed so a child (or an SSH host) cannot learn which terminal you use. A rare app that keys behaviour off the emulator falls back to defaults.
VTE_VERSION, KONSOLE_VERSION, KONSOLE_DBUS_*Scrubbed (anti-fingerprint).Same: emulator-identity / session-correlation signals, removed.
WT_SESSION, WT_PROFILE_IDScrubbed (anti-fingerprint).Windows Terminal session identifiers, removed.
ITERM_SESSION_ID, ITERM_PROFILEScrubbed (anti-fingerprint).iTerm2 session identifiers, removed.
KITTY_WINDOW_ID, KITTY_PID, ALACRITTY_WINDOW_IDScrubbed (anti-fingerprint).kitty / alacritty window identifiers, removed.
TERMINFO_DIRSPrepended with the shipped terminfo directory when the custom TERM is in use; otherwise untouched.Where terminfo entries are found; prepended so the custom secure-terminal entry resolves.
Everything else (PATH, HOME, LANG, SHELL, ...)Inherited unchanged.Your normal environment; untouched.

Capability and reporting protocols

This is the heart of the guarantee. A terminal and the program it runs share one channel, so any reply a terminal writes back to a query lands on the program's input, exactly as if typed. secure-terminal therefore answers no query: nothing on the output path ever writes to the pseudo-terminal. That closes the whole "output becomes input" reflection / echoback / answerback CVE class by design. The single, strictly opt-in and human-gated exception is clipboard read.

ProtocolQuerysecure-terminalWho asks, and what happens without a reply
Primary Device Attributes (DA1)ESC [ cNever answered.vim, tmux, less probe it to detect features; with no reply they fall back to the TERM baseline after a short timeout. At most a brief startup pause.
Secondary DA (DA2)ESC [ > cNever answered.Apps that gate features on the terminal version fall back to defaults.
Tertiary DA (DA3)ESC [ = cNever answered.Rarely used; no visible effect.
Cursor position report (DSR/CPR)ESC [ 6 nNever answered.Prompt frameworks (powerline, starship) and resize read the cursor position; they use a timeout fallback, so a prompt may briefly mis-measure width.
Device status reportESC [ 5 nNever answered.A health probe; apps assume OK.
Request setting (DECRQSS)ESC P $ q ... ESC \Never answered.Apps reading back a setting they just set proceed with the value they wrote.
Terminal name/version (XTVERSION)ESC [ > 0 qNever answered.kitty and newer apps sniff the terminal name; they fall back to TERM.
Request termcap/terminfo (XTGETTCAP)ESC P + q ... ESC \Never answered.tmux/kitty query terminfo capabilities; they fall back to the TERM terminfo.
Answerback (ENQ)0x05Never answered. No answerback string exists to set.A legacy mechanism; effectively unused today.
Window title reportESC [ 21 t, OSC title queryNever answered.Title-save/restore tools; the title is simply not reported. (This report is the classic reflection-to-RCE vector.)
Window size/position reportESC [ 14 t, ESC [ 18 t, ...Never answered.In-terminal image protocols (sixel, kitty, iTerm) need the pixel size; those image features degrade to no-image. Cell layout is unaffected.
Palette colour queryOSC 4/10/11/12 ; ?Not answered by default.Apps that theme to the background (vim's dark/light detection) fall back to their configured default.
Clipboard read (OSC 52)ESC ] 52 ; c ; ?The one exception: refused by default. If opted in, answered only after a per-tab human approval, with a delayed Allow button and a rate-limited reply.Remote paste-over-ssh; clipboard read is unavailable until you opt in per tab.

The pattern across the whole column: apps that query the terminal are built to survive no answer (they time out and assume a safe default), so they still run - they just cannot auto-enable an optional feature that needed the reply. An automated test in the corpus feeds every one of these queries and asserts that zero bytes are ever written back to the pseudo-terminal, so the guarantee is checked, not merely asserted.

DEC private modes (mouse, bracketed paste, focus, alternate screen)

The ESC [ ? Ps h/l modes a program uses to change how the terminal behaves are handled by mode, same as everything else:

ModeWhat a program uses it forCLI mode (default)TUI mode (opt-in)
Bracketed paste (2004)Ask the terminal to wrap pasted text so it is treated as data, not typed keystrokes.Stripped; paste is sanitised to ASCII and warned on regardless.Honoured - a paste is wrapped as data for the program, on top of the same sanitising.
Mouse reporting (1000 / 1002 / 1003 / 1006)Have clicks and motion sent to the program as escape sequences.Stripped; the terminal never writes mouse events back.Not forwarded - a TUI app cannot track your mouse (selection stays a terminal function).
Focus reporting (1004)Tell the program when the window gains or loses focus.Stripped.Not forwarded - focus is not reported, so it cannot be used to fingerprint or time you.
Alternate screen (1049 / 47)Switch to a full-screen buffer (vim, less, htop) and restore the scrollback on exit.Not entered; detected only to hint that TUI mode is needed.Honoured inside the confined pyte screen; still character-filtered, still answers no query.

Control characters

In CLI mode the terminal honours only the few control bytes needed to edit the current line, drops the rest, and removes every escape sequence outright - there is no parser to attack. In TUI mode the confined screen model interprets cursor and colour escapes, but every resulting cell is still ASCII/unicode-filtered. For the neutralised bytes below, how they are shown follows the unicode display mode: named inline as <U+XXXX NAME> in the default Detail mode, a risk-coloured box in Box mode, or a <U+XXXX> badge in Reveal mode - all ASCII, so what you copy or save from them is pure ASCII (Show mode, which draws the real glyph, is the one exception, and a copy from it is reviewed first).

ByteNameCLI mode
0x09Tab (HT)Passed through.
0x0ANewline (LF)Passed through (ends the line).
0x0DCarriage return (CR)Honoured - moves to column 0 (line-local).
0x08Backspace (BS)Honoured - one cell left; never crosses a line, so it cannot rewrite earlier output.
0x07Bell (BEL)Honored per the bell setting (default: silent; options: audible / visual / tray). Not shown on screen.
0x1BEscape (ESC) + any sequenceRemoved, with two deliberate exceptions: safe colour (SGR) and the four line-local edits CSI C/D/G/K, clamped to the line being written. No OSC/APC/DCS parser exists, and no vertical or absolute movement is honoured.
0x20-0x7EPrintable ASCIIShown verbatim.
0x00-0x06, 0x0B, 0x0C, 0x0E-0x1FOther C0 controlsNeutralised; shown per the unicode mode (see above).
0x7FDelete (DEL)Neutralised.
0x80-0x9FC1 controls (non-ASCII)Neutralised; shown per the unicode mode (see above).

The keys you type are forwarded as bytes, not interpreted: printable ASCII, the control keys a shell's line editor uses (the whole Ctrl+A-Ctrl+Z range - Ctrl+C SIGINT, Ctrl+R, Ctrl+U, ...), and the arrow / Home / End / Delete line-edit keys. Each is written as its control byte; the pseudo-terminal turns the signal keys (Ctrl+C/Z/\/D) into signals. What never reaches the shell is a paste's hidden bytes - those go through the review bar first.

Two forms: the GUI app and the CLI wrapper

"secure-terminal" ships in two forms that share one sanitization core. The GUI app (secure-terminal, Qt) is the full terminal with tabs, TUI mode and the paste gate. The CLI wrapper (secure-terminal-cli) is a tiny, Qt-free program that runs a command in a sanitizing pseudo-terminal, so you can wrap untrusted output even on a plain console or over SSH where no GUI can run. It is stricter and simpler; the automated CLI-mode tests above were run through it. Both strip escapes and keep output safe.

Capabilitysecure-terminal (GUI app)secure-terminal-cli (wrapper)
Safe display (escapes stripped, ASCII-filtered)YesYes
Runs over SSH / on a plain console (no display)No - needs Qt and a displayYes - pure Python, no Qt
Paste / copy review bar (in-window, delayed Allow)YesNo - a raw stdin stream cannot tell typing from a paste
TUI mode (run full-screen programs)Yes (opt-in, per tab)No - CLI mode only
ColourYes - contrast-guarded, on by defaultStripped (shows plain text)
Unicode display modes (Box / Show / Reveal / Detail)Yes (per tab)Yes (--mode)
Tabs, each its own shell + ptyYesNo - one command or shell
Themes, zoom, session restoreYesNo - inherits the outer terminal
OSC feature toggles (title, notifications, clipboard)YesNo - all neutralised
Command hook (plugin)Yes (CLI mode)Planned

OSC sequences

Operating System Command (OSC) escapes are the ones that act beyond the text on screen - setting the window title, the system clipboard, a desktop notification, or a hyperlink. Every one is a registered feature, off by default (neutralised); each can be individually turned on at your own risk, and even then its value is validated and sanitised to plain ASCII first. Left off, this closes clipboard exfiltration, hyperlink phishing, notification spoofing and title spoofing by default.

OSC codeFeatureDefaultOpt-in?RiskWhen you opt in
0, 2Window / tab titleOffYesmediumSanitised to plain ASCII; the title is still never reported back.
7Working-directory reportOffYeslowPath validated and sanitised.
8HyperlinksOffYesmediumThe true target is surfaced next to the visible text, so a phishing mismatch is visible.
9Desktop notificationsOffYesmediumText sanitised to plain ASCII.
4, 10, 11, 12Palette / coloursOffYesmediumApplied under the contrast guard, so nothing can be painted invisibly. (Colour rendering itself is a separate setting, on by default.)
52 (write)System clipboard writeOffYeshighWrite only, when enabled.
52 (read)System clipboard readOffYes (gated)highAsked once per tab, with a delayed Allow and rate-limited reply.
1337iTerm2 extensions (file transfer, variables)n/aNo toggle-Recognized and always stripped. There is deliberately NO toggle for it: iTerm2 OSC 1337 can never be safely enabled, so secure-terminal implements none of it; the file-transfer / variable surface a conformant iTerm2 would expose stays closed, with nothing to turn on.

Who uses these. Title (0/2): shells and tmux set the window/tab title to the running command or directory. Working directory (7): terminals track the shell's cwd to open a new tab there. Hyperlinks (8): ls --hyperlink, delta and some compilers make paths clickable. Notifications (9): long-build and IRC tools (weechat, irssi) raise a desktop popup. Palette (4/10/11/12): themes and background-detection (vim). Clipboard write (52): tmux set-clipboard and editor yank plugins copy to the system clipboard. Clipboard read (52 query): remote paste-over-ssh (tmux, neovim OSC 52 yank/paste). iTerm2 (1337): imgcat and file transfer - which secure-terminal does not implement.

What the iTerm2 (OSC 1337) extensions actually are. They are proprietary to iTerm2 (a macOS terminal), not a standard, and they bundle several features behind one escape: inline files - imgcat photo.png or cat chart.png | imgcat draws the image in the scrollback; file transfer - it2dl file pulls a file from a remote host down to your Mac and it2ul pushes one up, entirely over the terminal connection; shell/user variables and reporting - shell-integration scripts set values (the current directory, git branch, a "badge", command-success marks) that the terminal reads back to draw status marks and let you jump between prompts. In practice you meet them through iTerm2's imgcat/it2dl/it2ul utilities and its shell-integration profile; almost nothing outside the iTerm2 ecosystem emits them. Why secure-terminal implements none of it: each one is the terminal acting on program output - rendering arbitrary image bytes, writing files to your disk, or reporting state back onto the input channel - which is exactly the surface CLI mode (the default) removes by design. The escape is recognised only so it can be stripped; there is deliberately no toggle for it, so it is always neutralized - a program gets no file-transfer, no image, and no variable channel. If you need those, that is a reason to open iTerm2 (or a dedicated graphical terminal) for that task.

Deliberately not supported: in-terminal graphics

Some modern terminals can draw pictures in the grid - a chart from a CLI tool, a thumbnail, a plot. secure-terminal does not, and that is a design choice, not a gap: every one of these protocols works by having the terminal parse an escape sequence and render arbitrary attacker-supplied bytes, which is exactly the parser and the attack surface CLI mode (the default) removes by design.

FeatureHow it worksUsed bysecure-terminal
Sixel graphicsA DCS escape carrying a bitmap, drawn into the character grid.img2sixel, mpv, gnuplot, some pagersNot implemented. The DCS escape is stripped like any other; no image is rendered.
kitty graphics protocolAn APC escape (ESC _G) streaming PNG/raw pixels, optionally from a shared file or memory.kitty's icat, timg, some file managersNot implemented. APC is not parsed; the shared-file/memory variant (a local-file read primitive) never runs.
iTerm2 inline images (OSC 1337)An OSC 1337 payload carrying a base64 image (and file transfer, shell variables).iTerm2's imgcat, it2dlNot implemented (stub). Recognised and stripped; the toggle performs no action even when on. See the OSC table above.

If you need inline images, that is a legitimate need - and a legitimate reason to open a dedicated graphical terminal in a separate tab for that task. secure-terminal's promise is that reading text and pasting commands is safe by design; rendering arbitrary remote image bytes is not a promise it can keep while keeping that one.