tirith: a shell hook that inspects commands before your terminal runs them
Terminal security for developers and AI agents. Intercepts homograph URLs, pipe-to-shell, ANSI injection, obfuscated payloads, data exfiltration, and malicious AI skills/configs before they execute.
At a glance
- What is it?
- Tirith is a Rust CLI that intercepts commands, pasted text and scanned files for homograph URLs, pipe-to-shell chains and obfuscated payloads. The README's own example is the clearest case for it, and the clearest case against treating it as an authorization boundary.
- Who is it for?
- Adopt tirith if you paste install commands from search results, run curl-pipe-shell one-liners, or let AI agents execute shell commands on your machine, and you accept a shell hook as a signal rather than an authorization boundary. Do not adopt it if you need a kernel-level or CI-enforced control, or if AGPL-3.0 is incompatible with how you ship.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap tirith targets: terminals render Unicode without asking
Browsers have had homograph defenses for years. Terminals have not. The README's opening example makes the point better than any explanation: two curl commands look identical, but in the second one the i characters in the hostname are Cyrillic U+0456, so the URL resolves to a different server. A human reading the line cannot tell. A shell will run it. Tirith is built for that specific blind spot, plus the adjacent ones that share the same execution path: ANSI escape sequences, bidirectional overrides, zero-width characters, base64 decode chains piped into an interpreter, and curl uploads that push credential files to a remote host. The intended user is a developer who pastes commands from documentation, issue threads or search results, and increasingly an AI agent that runs shell commands on the developer's behalf without reading them. The README states the project's posture directly: tirith stands at the gate and intercepts before execution.
How the interception actually happens: a shell hook, not a sandbox
The mechanism visible in the material is a shell hook. You install the binary, then add an eval line to your shell profile, and from that point commands accepted by that interactive shell are checked while the hook is loaded. The README is explicit that this is the boundary: commands are checked while the hook is loaded and healthy, and exact blocking behavior depends on the shell and mode. That phrasing matters. Tirith is not a seccomp filter, a container policy or a kernel module. It is a function that runs inside your shell and decides whether to print a warning, print a block, or stay silent. Detection runs against 244 rules across 35 categories, covering homograph and confusable domains, terminal injection, steganographic whitespace, pipe-to-shell in many wrapper and indirection forms, base64 decode-execute including PowerShell -EncodedCommand, data exfiltration through curl and wget upload flags, credential patterns including AWS keys and GitHub PATs, and post-compromise behaviors such as /proc/*/mem reads and credential file sweeps. A separate scan path handles files: tirith scan evil_skill.py, in the README's example, reports dynamic code execution, an obfuscated payload and a suspicious exfiltration call in a Python file.
Install and shell activation, with the exact commands
Homebrew is the shortest path: brew install tirith. The README also lists npm, cargo, mise, and apt/dnf packages as additional distribution channels, without printing the individual commands for each. Activation is one line per shell. For zsh, eval "$(tirith init --shell zsh)". For bash, eval "$(tirith init --shell bash)". For fish, tirith init --shell fish | source. The README notes that eval "$(tirith init)" auto-detects the current shell by inspecting the parent process and falling back to $SHELL, and that the explicit --shell flag is only needed to override detection. After installation and after upgrades, the documented next step is tirith doctor. There is also a per-command escape hatch: prefixing a command with TIRITH=0 bypasses the check for that command only, which the block message itself advertises. That bypass is worth thinking about before you deploy this anywhere shared, because it is a documented, one-token way past every rule.
Block, warn, or silence: the three outcomes and what they imply
The README's examples show three distinct behaviors. A homograph URL piped to bash is BLOCKED with a CRITICAL non_ascii_hostname finding naming the Cyrillic character and the U+0456 codepoint. A clean-URL pipe-to-shell such as curl -fsSL https://get.docker.com | sh is a WARNING at MEDIUM severity, printed to stderr, and the command still runs. Ordinary commands like git status, ls -la and docker compose up -d produce nothing at all. That tiering is the most defensible design decision in the project. Blocking every pipe-to-shell would break routine installs and train users to disable the tool. Warning on the clean case and blocking on the confusable-hostname case keeps the false positive rate low enough that the hook survives in a real profile. The cost is that the MEDIUM tier is advisory only, so a compromised get.docker.com or a hijacked CDN response would pass through with a line of stderr text that most users will scroll past.
Where tirith is the wrong tool: hook health, bypasses, and non-interactive paths
The README itself supplies the main limitation. Commands are checked while the hook is loaded and healthy, and the exact blocking behavior depends on the shell and mode. Anything that does not go through that interactive shell is outside the hook's reach: a systemd unit, a cron job, a Makefile invoked by a CI runner, a container entrypoint, or a process spawned by an editor or IDE that never sources your profile. The TIRITH=0 prefix is a second gap, and it is documented rather than hidden, which is honest but also means any attacker who can influence the command string can prepend it. The scan path is a separate mode with separate coverage: tirith scan works on files you point it at, not on files that arrive some other way. And the rule set is pattern-based, so it detects shapes it has rules for. The README frames the post-compromise rules as calibrated against TeamPCP and UNC1069 tooling, which tells you the detection surface tracks known tooling rather than proving absence of unknown tooling. A negative result from tirith is not evidence that a command is safe.
Alternatives and the difference in approach
The closest conventional comparison is a shell linter such as ShellCheck, and the difference is the point in time. ShellCheck reads a script and reports style and correctness problems before you run it, but it does not sit in your interactive shell, it does not parse URLs for Cyrillic lookalikes, and it has no notion of a threat database or of credential exfiltration through curl flags. A second comparison is running everything inside a container or VM with restricted network egress. That approach is strictly stronger as an enforcement boundary, because it does not depend on a hook being loaded or on the user not typing TIRITH=0. It is also heavier: you cannot containerize a git status, and a homograph URL pasted into a host shell is still a homograph URL. Tirith occupies the space between those two, closer to a spell checker for command lines than to a sandbox. The rolling threatdb-current release channel, which the release list shows updating separately from the versioned v0.4.x tags, is the piece neither ShellCheck nor a container gives you: remote-updated indicators for known-bad packages, domains and IPs. How that database is distributed and verified is not described in the material I have, and the README calls it a signed threat intelligence database without detailing the signing or update mechanism.
Maintenance cost, release cadence and the AGPL-3.0 question
The release list shows v0.4.0 on 2026-08-25, v0.4.1 on 2026-09-02, and a separate rolling threatdb-current artifact published between them. A rolling database means the useful part of the tool updates on its own schedule, which is good for detection freshness and awkward for reproducibility: two machines on the same tirith binary can behave differently because they pulled different threat data. The README's instruction to run tirith doctor after installation and upgrades implies that upgrades can leave the hook in a state worth checking, which is a maintenance step you have to remember rather than one the tool performs for you. On licensing, the project is AGPL-3.0. That is a strong copyleft licence with a network-use clause. If you run a modified tirith as part of a service that users interact with over a network, the AGPL's source-disclosure obligations are generally understood to apply, though the exact scope depends on your deployment and you should get your own legal review rather than relying on a summary. Using the unmodified binary as a local developer tool is a different situation from embedding it in a product. If your organization bans AGPL dependencies outright, that policy decides the question before any technical evaluation does.
Who should install it, and what to check before trusting it
Install it if your workflow includes pasting install one-liners from the web, if you run curl-pipe-shell commands as a habit, or if you have an AI agent executing shell commands on your machine and you want a second opinion printed to stderr before those commands run. The homograph case alone justifies a shell hook for that audience, because no amount of care makes U+0456 visible in a terminal. Do not install it if you need an enforcement boundary that survives a hostile process, if your execution paths are mostly non-interactive, or if AGPL-3.0 conflicts with your distribution model. Before relying on it, do three things: run tirith doctor immediately after install and again after the next upgrade, read the enforcement-by-shell section the README points to so you know which of your shells is actually covered, and deliberately test TIRITH=0 on a command you would want blocked, so you understand the bypass you are handing to anyone who can shape a command string. Tirith is a well-scoped signal on the interactive path. Treating it as anything more is the failure mode the README warns about in its own words.
Editorial conclusion
Adopt tirith if you paste install commands from search results, run curl-pipe-shell one-liners, or let AI agents execute shell commands on your machine, and you accept a shell hook as a signal rather than an authorization boundary. Do not adopt it if you need a kernel-level or CI-enforced control, or if AGPL-3.0 is incompatible with how you ship. Verify first by running tirith doctor after install, reading the enforcement-by-shell section, and testing the TIRITH=0 bypass on a command you would actually block.
Community notes