secure-terminal: safe to paste, safe to read

What You See Is What You Get (WYSIWYG)

Safe to paste. Safe to read.

Invisible characters in pasted commands are made visible, and control sequences in terminal output are neutralized, so neither input nor output can hide what's really there.

A GNOME Terminal window whose title bar has been silently changed by the file it is displaying to read 'gnome-terminal (window title injected)'. The text is a pre-deploy verification checklist for acme-cli release v3.2.0 that reads as entirely trustworthy: it fetches from https://example.com, reports a good maintainer signature, an attachment named q3-report.exe.pdf 'reported document-only, safe', a deploy under the account administrator@corp.internal, and a support contact ops@example.com, ending 'All preflight checks passed -- ready to deploy'. Only the injected title hints anything is off.
The same file shown by secure-terminal in Show mode, its own title bar unchanged. It flags every trap in the same bytes: both 'example.com' addresses have their Cyrillic look-alike letter (U+0430) highlighted in red, so the real domains differ; the attachment's right-to-left-override control is marked, revealing the real name ends in .exe not .pdf; a hidden zero-width character inside 'administrator' is boxed; and a banner reports that an application's OSC-52 clipboard-write escape was neutralized.
The same file, cat'd in two terminals with the same font. Left, secure-terminal in Show mode; right, a traditional terminal (GNOME Terminal). It is a routine "verify this release before you deploy" checklist -- and every reassuring line hides a still-applicable trick, spread left to right: a Cyrillic look-alike in two example.com addresses (U+0430), a right-to-left override that shows an .exe as a .pdf, a zero-width character inside "administrator", a silently hijacked window title, and a silent OSC-52 clipboard overwrite. The traditional terminal renders all of it as a trustworthy session; secure-terminal shows the same bytes and flags every one. Real screenshots, captured under a labwc compositor; title-hijack behaviour varies by terminal (konsole, for instance, resets it). Drag to compare, or click a side to open it full size.
Reproduce this · the same board, cat'd in two terminals - the corpus and copyable commands are on the comparison and screenshots pages.

What is this? A terminal whose display shows printable ASCII in its default display mode (everything else is neutralized to a visible, inert placeholder) and which runs no escape-sequence parser on program output beyond safe colour and four edits clamped to the line being written. Paste a command you copied from the web, or read text a program printed, and trust that what you see is what is there. When you need more, an opt-in TUI mode runs full-screen programs, ssh, vim, htop, through a restricted screen model that still filters every character.

Safe by design

The security property is not a filter bolted on. It is what the terminal can and cannot do by default.

Output is printable ASCII by default

Program output is rendered as printable ASCII plus newline and tab. Bidi overrides, zero-width characters, homoglyphs, ANSI escapes and control bytes never reach the screen as anything but their neutralized form. A hostile filename in ls, a forged [ OK ] in a log, a Trojan-Source comment: none of them can reorder what you read or repaint a line you have already scrolled past. One honest caveat: a program can still redraw the line it is currently writing -- see what a program can still do to its own line.

No escape parser in CLI mode

By default there is no general ANSI parser to attack: no vertical or absolute cursor movement, no alternate screens, no OSC hyperlinks, no window-title control, no clipboard-write escape (OSC 52). Parsing attacker-influenced escapes is the entire vulnerability class; a terminal that will not parse them cannot fall for them. Two narrow exceptions are honoured on purpose - safe, contrast-guarded colour, and the four line-local edits a shell's line editor emits, which cannot leave the line being written. Each OSC feature is a separate, off-by-default toggle you may enable at your own risk, and turning one on dims a security indicator so the extra surface is never invisible.

What a program can still do to its own line

One honest caveat, stated narrowly. By default, CLI mode honours the four line-local edits a shell's line editor emits (cursor forward/back, cursor-to-column, erase-in-line), all clamped to the line currently being written - so a program can overwrite what it itself just printed on that line, exactly as a \r progress bar does, but nothing can reach a line you have already scrolled past. Transcript integrity is per-line: everything above is append-only, and the overwritten text is gone (capture it with stcat if you need a byte-exact record). And it is configurable: drop even this line-local editing with View -> Line editing (line_edits, on by default), and a program can then no longer alter even its own current line. Full detail on the compatibility page.

Line-oriented input, and you can never get stuck

You type printable ASCII and the usual control keys (Ctrl+C, Ctrl+A, Ctrl+R and the rest). They send the control byte exactly as a real terminal does, so a shell in its normal line mode interrupts on Ctrl+C while a raw-mode program - an editor, or a CLI that shows "press Ctrl+C again to exit" - reads the byte itself. A signal key is echoed in caret notation (^C, ^\) so you always see the interrupt you sent, even when the shell would print nothing. A Terminate button (SIGTERM then SIGKILL) is the guaranteed escape hatch for a program that ignores everything, so you can never get stuck.

Memory-safe

The code that reads hostile bytes is written in a memory-safe language (Python). The traditional terminal emulators, written in C, have a long history of memory-corruption bugs in exactly this parsing path (escape and image-decoder overruns); here that entire class, buffer overflows and use-after-free driven by attacker-controlled output, cannot happen by design.

The font is a security decision

The default font is Hack, chosen for disambiguation, not looks. Its glyphs are drawn so the characters attackers rely on being confusable are not: 0 vs O, 1 vs l vs I, rn vs m all read distinctly, so a look-alike cannot hide behind an ordinary glyph. It ships no ligatures or contextual alternates - and secure-terminal disables them anyway - because a ligature merges characters visually (!= drawn as a single ≠), hiding what the bytes really are in a terminal whose whole promise is that what you see is what is there. The default font is a hard dependency, enforced at startup: if Hack is missing the app fails loud rather than let the toolkit silently substitute some other font that could reintroduce the confusable glyphs or ligatures it exists to avoid.

A runaway tab stays in its tab

Each tab's shell runs in its own Linux control group with a process-count ceiling and a memory cap. A fork bomb hits its own PID limit while the other tabs keep their share; a program that runs away on memory is stopped inside its own cgroup, so the runaway is killed instead of the whole terminal or a sibling tab. The memory cap is sized from what is actually free at the time, so it trips before the system does. This is resource containment, not a sandbox - the programs you launch are your own commands and run with your own privileges; it bounds an accidental or crude runaway, it is not an escape boundary (isolating the terminal from the rest of the system is your OS's job). Best-effort by design: where the OS does not delegate cgroup v2, a tab simply falls back to the session's own limits, no worse than any other terminal, never a blocked shell.

Safe, and actually usable

Safety you can live with day to day, not a museum piece.

Four unicode display modes

Per tab: Box (every non-ASCII character becomes an inert box, coloured by risk class), Reveal (each as a <U+XXXX> badge, to inspect exactly what is there), Detail (the same badge with the codepoint's Unicode name inline, the safe default), and Show (render legitimate unicode so you can read a log, each glyph still tinted by risk class (except inert box-drawing and block elements, drawn in the program's own colour) - a homoglyph confusable with ASCII wears a louder colour than honest foreign text, and a non-ASCII space is shown as a distinct marker and copies as _, never as a space). The figure below is one ls listing in all four.

Show does not mean run. All four modes remove escape sequences first (there is no general escape parser in CLI mode), so nothing a program prints - a title change, a clipboard write, an alternate screen, a jump to another line - is ever interpreted, in any mode, Show included. The mode only changes how the remaining, already-safe characters are drawn, never whether escapes are honoured; the only escapes honoured in every mode are safe colour and the four line-local edits described under the current line.

The same ls listing, all four modes - three of the filenames are lying (a right-to-left override, a Cyrillic look-alike, a zero-width space) and two are honest non-ASCII (résumé.pdf, a CJK name).
One ls listing rendered four times by secure-terminal, ending with Detail, the default. Box: every non-ASCII byte, hostile or honest, becomes a single coloured placeholder box, so the accented and CJK names are boxed too. Show: the honest resume.pdf and the CJK name are readable as themselves, while the Cyrillic look-alike 'a' is kept but tinted red and the bidi override and zero-width space still collapse to placeholders. Reveal: each hidden character is named inline as a badge, U+202E, U+0430, U+200B, U+00E9, U+4E2D, U+6587. Detail: the same badges expanded to full Unicode names, RIGHT-TO-LEFT OVERRIDE, CYRILLIC SMALL LETTER A, ZERO WIDTH SPACE, LATIN SMALL LETTER E WITH ACUTE, CJK UNIFIED IDEOGRAPH.
This is also the honest answer to "is it ASCII only?" - it is not, and the panels show why. Box is ASCII in text but draws a non-ASCII placeholder glyph, and it boxes honest non-ASCII too. Show deliberately keeps real printable non-ASCII, tinting a look-alike rather than removing it. Reveal and Detail are pure ASCII, naming what was hidden. What holds in every mode is narrower and more useful: no invisible, bidi or control character reaches you unmarked.

Paste warning

Paste anything with unicode or control characters and a review bar opens inside the window, holding the paste before a byte reaches the shell. It shows the paste four ways - as it looks, each hidden character named inline as a <U+XXXX> badge, and exactly what each send button would deliver (stripped to ASCII, or with printable unicode kept) - all risk-coloured by the terminal's own pipeline. Input is suspended and both send buttons are countdown-gated while it is held, so a stray key reaches nothing. When to warn is configurable (always / only on unicode-or-control, the default / never); never turns off the prompt, not the safety - a paste is always sanitised to ASCII, there is no pass-through mode. A multi-line paste is held too, so a hidden second command cannot run the instant you paste.

Copy is reviewed the same way, in the other direction - the same bar guards text leaving via copy, configured separately because copy and paste are opposite trust directions. The two screenshots below show both bars, and the review bar page walks through every part.

Transcripts safe to open anywhere

Save a tab's scrollback to a file. The saved file is lossless - each neutralized character is named inline (<U+0430 CYRILLIC SMALL LETTER A>), never collapsed to a bare _ - and pure ASCII unless you opted into Show mode's real glyphs. So it is safe to open in any editor, unlike a normal terminal's raw log which can carry the very escapes this terminal refuses, and it still records exactly what was there.

Guaranteed exit

Signal keys reach the foreground program even in raw mode, and the Terminate action force-kills a program that ignores everything. You are never stuck inside something you cannot get out of.

Granular escape-feature controls

The OSC escapes that reach out of the grid - window title, desktop notification, clickable hyperlink, clipboard write, clipboard read, palette change, working-directory report - are each a separate, off-by-default toggle with a one-line note on what it exposes. (The iTerm2 OSC 1337 extensions have no toggle - they can never be safely enabled, so they are always neutralised.) A three-part security indicator dims from green toward red as you enable riskier features. When a program tries to use a feature you left off, a dismissible banner tells you once per feature per tab - visible but never nagging, and silenceable globally or per feature.

Clipboard sanitizer for the whole desktop

The same neutralization, for text you paste into other programs - an editor, a chat box, anything that does not treat unicode as carefully. sclip is a tiny filter that reads text and writes it back clipboard-safe: strictest by default (printable ASCII plus tab and newline, so an invisible, bidi or homoglyph character cannot ride out), or --unicode to keep printable non-ASCII. It composes with the system clipboard tools, and drop-in wrappers - st-wl-paste, st-wl-copy, st-xclip - run the pipeline for you.

Or run it as a tray watcher (secure-terminal --clipboard-watch, no terminal window): copy something deceptive and the same in-window review bar pops up and offers to replace it - flag-and-offer, never a silent rewrite. You choose Keep original, Replace (ASCII), or Replace (keep unicode). Honest limit: keeping unicode keeps printable look-alikes too (a homoglyph is printable unicode); only the ASCII strip removes them, which is why the preview names every character so you can judge. It sanitises the local clipboard; on Qubes the inter-VM global clipboard is untouched - run it after pasting into the target VM.

You can also drive the watcher from an open terminal: View > Clipboard sanitizer (and the right-click menu) start / stop it, toggle the warn-on-any-non-ASCII mode, review the clipboard on demand, and set start-on-login. Since it is a tray app, turning the terminal's system tray off also turns its autostart off.

The paste review - a curl | bash line hiding two Cyrillic homoglyphs (in example and bash), a zero-width space and a right-to-left override.
secure-terminal's in-window paste-review bar holding a hostile 'curl | bash' paste before any byte reaches the shell. A red dot and the summary 'This paste hides 1 bidirectional control, 1 invisible character, 2 non-ASCII characters.' head the bar, with a Restore original button, a safe-green Reject button and a red, disabled 'Paste blocked - 2 hidden' button top-right. Below is the ONE editable box that opens FULLY REVEALED: nothing is dropped, so the two Cyrillic look-alikes are highlighted and named inline as U+0430 CYRILLIC SMALL LETTER A, and the invisible and the reordering override are shown IN PLACE as named badges, U+200B ZERO WIDTH SPACE and U+202E RIGHT-TO-LEFT OVERRIDE. A status line reads 'Showing the full paste - 2 hidden characters (invisible / reordering / control) block sending; each is named in the box. Remove them (a Text transformation, or delete the badges) to continue.' Under a 'Text transformations:' heading three buttons offer Strip unicode, ASCII-fold and Keep printable unicode, each with a plain-language gloss. A Structure section lists 1 line, an 'If accepted: waits on the command line - press Enter to run' guarantee and a 65-character length, and a live per-class Hidden-characters table names Bidirectional control 1, Invisible / zero-width 1 and Look-alike (homoglyph) 2, every other class 0.
The bar names what is hidden at the top, then shows the paste in one editable box that opens fully revealed - nothing is silently pre-dropped, so a trap cannot hide by being cleaned away before you see it: each look-alike is tinted and named inline, and every invisible or reordering character is shown in place as a named badge. While any hidden character remains, the Paste button is disabled and red (blocked - N hidden) so nothing unreviewed can cross. Three transforms clear it - Strip unicode deletes every non-ASCII character (the domain collapses to the visibly-broken exmple.com), ASCII-fold folds each look-alike to the ASCII it imitates (revealing example.com), and Keep printable unicode drops only the invisible/reordering characters - or you edit the box directly; Restore original re-reveals it. Paste then enables, still gated by an anti-fat-finger countdown. Rendered headless from the real bar.
The same bar, for copy going the other way - selecting and copying that same line in Show mode, before it reaches the system clipboard.
secure-terminal's review bar reviewing a copy going out to the system clipboard. The summary reads 'This copy would carry 1 bidirectional control, 1 invisible character, 2 non-ASCII characters onto the clipboard.' It is the same fully-revealed single-box layout as the paste bar - the two Cyrillic look-alikes highlighted and named inline, the invisible and the reordering override shown in place as named badges - with a 'Showing the full copy - 2 hidden characters block sending' status line, Strip unicode / ASCII-fold / Keep printable unicode transforms, a Structure section whose 'If accepted' guarantee reads 'placed on the system clipboard (nothing runs)', and a live per-class Hidden-characters table naming Bidirectional control 1, Invisible / zero-width 1 and Look-alike (homoglyph) 2. The reject button reads Don't copy and the Copy button is disabled and red, 'Copy blocked - 2 hidden'; there is no countdown, since a copy is not executed.
Copy is reviewed with the same fully-revealed bar, relabelled for the other direction (the reject button reads Don't copy). The box is the exact preview of what would land on the clipboard, and the same Strip unicode / ASCII-fold / Keep printable unicode transforms clear it. The If accepted guarantee is shown here too - for a copy it reads placed on the system clipboard (nothing runs), so the row is consistent whichever direction you are in. There is no countdown - a copy is not executed, so the anti-fat-finger gate the paste needs does not apply.
Every risky feature explained in place - each escape-feature toggle carries a selectable pop-up naming exactly what enabling it would expose.
secure-terminal's risk-explanation tooltip for the 'System clipboard (write)' OSC 52 feature: a rounded card headed 'System clipboard (write)  (OSC 52)' over the body 'Untrusted output could silently overwrite your system clipboard, so a later paste inserts text you did not copy. Write only; reading is a separate setting.' Unlike a plain hover tooltip it is a selectable pop-up whose colours follow the terminal theme, so the full risk note can be read and copied.
Unlike a plain hover tooltip this one is selectable and stays open, so the full risk note can be read and copied; its colours follow the terminal's theme and its text scales with the zoom. The same pop-up backs every entry in the granular escape-feature list above.

TUI mode: run full-screen programs

When you need to run ssh, an editor like vim, or htop, opt in with your eyes open.

Opt-in, clearly flagged / ssh, vim, htop, tmux

The strict default cannot run curses apps. TUI mode (per tab, off by default, clearly marked in the toolbar and status bar) interprets escapes inside an isolated in-memory screen model (pyte) that has no OS reach: it never sets the OS window title from output and cannot touch the clipboard. A program-set window/tab title is shown, but quarantined on a separate, visibly-untrusted line of the tab (plain ASCII, never reported back), so it can never pose as the trusted, app-set label. The clipboard, hyperlink and notification side-effects are separate opt-in toggles (off by default) that dim a security indicator when enabled, so those exfiltration/phishing vectors stay closed unless you deliberately open one. Every character it places is still ASCII/unicode-filtered, and colours use a contrast guard so nothing can be painted invisibly.

What you give up: a program can draw a misleading interface within its own screen, so only run programs you trust. This is "restricted-emulator safe," not "safe by design." The default CLI mode, and everything the guarantee rests on, is unchanged.

# in TUI mode, verified end to end: ssh, vim, htop, tmux and apt's progress bar all render correctly, with every cell still filtered, the clipboard off-limits, and any program-set title quarantined.

Everyday comforts

The conveniences a terminal is expected to have, without loosening the model.

Tabs

Named, colour-coded, each its own shell. Per-tab theme, zoom, unicode mode and colours. Rename by double-click; a user name and a program title are kept separately.

Safe optional colours

Full ANSI colour -- 16-colour, 256-colour and 24-bit truecolor -- off by default, with a contrast guard so text can never be invisible or hide itself against the background (a crafted red-on-red is forced legible). Honours NO_COLOR and TERM=dumb.

Session restore

Tabs and their scrollback survive a restart or reboot, under a fresh shell. Stored under ~/.local/state; on by default, and you can turn it off or clear it.

Drop-in settings

systemd-style .conf directories: a distro or admin seeds defaults in /etc/secure-terminal.d, the user overrides them. Plain KEY=value.

Install

A standard Debian source package (Debian trixie). Pick a method; click to expand.

Run from a checkoutno install

Clone and run it in place, no packaging step:

git clone https://github.com/secure-terminal/secure-terminal cd secure-terminal ./usr/bin/secure-terminal # the GUI ./usr/bin/secure-terminal-cli # the sanitizing CLI wrapper

Needs PyQt6 (and python3-pyte for TUI mode), both in Debian main.

Build the Debian package.deb
git clone https://github.com/secure-terminal/secure-terminal cd secure-terminal dpkg-buildpackage -us -uc -b sudo dpkg -i ../secure-terminal_*.deb sudo apt-get install -f # pull dependencies (Debian main)

Then run secure-terminal for the GUI, or secure-terminal-cli to wrap a shell or command in the terminal you are already in.

Kicksecure repositorycoming soon

An apt install from the Kicksecure repository is planned, so updates arrive with your normal system upgrades. Not available yet.

Kicksecurecoming soon

secure-terminal is planned to be installed by default in Kicksecure, so a hardened system ships with a safe terminal out of the box. Not available yet.

Source codeAGPL-3+

Read, audit and build every line: github.com/secure-terminal/secure-terminal. Free Software under the AGPL-3+.

FAQ

The questions worth answering before you trust a terminal.

Is it Open Source? What licence?

Yes. secure-terminal is Free Software under the AGPL-3+, developed in the open on GitHub. Read the source, build it yourself, audit exactly what it does.

Does it send my data anywhere?

No. It runs entirely on your machine: no telemetry, no network calls, nothing about what you type or what your programs print leaves the terminal.

What platforms does it run on?

Linux. It ships as a standard Debian source package (Debian trixie and derivatives such as Kicksecure and Whonix); it is a PyQt6 application, so it runs anywhere PyQt6 and a pseudo-terminal are available. The secure-terminal-cli wrapper needs only Python and a POSIX pty.

Is it memory-safe?

Yes. It is written in Python (PyQt6); there is no C parser of untrusted output to overflow. The whole point is that untrusted bytes meet a small, auditable sanitiser, not a sprawling escape-sequence interpreter.

How is this different from my normal terminal?

A normal terminal interprets whatever a program prints, including attacker-controlled escape sequences, invisible characters and homoglyphs. secure-terminal does not: by default it parses no escapes, shows every character as itself (or a visible marker), and warns before a paste with hidden characters reaches the shell. See the comparison for the specifics.

Was this AI vibe coded?

It is an AI-assisted project - written and reviewed with the process documented at org-ai-assisted, with human review and AI review. The code is open for you to audit regardless of how it was produced - and the security claims are testable, not taken on trust.

Traditional terminal emulators have a live vulnerability class: merely viewing a file - cat a log, tail a server's output - can make the terminal type an attacker's text into your shell and run it, no keypress needed. It is not a historical footnote: the same class recurs, most recently as remote code execution in iTerm2 in 2024 (CVE-2024-38395), one of the twenty-odd real cases the terminal-poc-corpus collects. secure-terminal closes the whole class by design - CLI mode runs no escape parser and answers no query sequence, so output can never become input. See the comparison, measured with screenshots.