tirith: a terminal gate for homograph URLs, pipe-to-shell and malicious AI skills
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 hooks into your shell and inspects commands, pasted text and scanned files before they run. It ships 244 detection rules and a signed threat database, but its blocking power depends on the shell and the mode.
- Who is it for?
- Adopt tirith if you paste untrusted URLs into a terminal, run AI agents that execute shell commands, or scan third-party skill and config files before loading them, and start with tirith doctor to confirm the hook is loaded. Skip it if you need an unbypassable authorization boundary, since TIRITH=0 disables a check for a single command and enforcement varies by shell.
- 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 received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap tirith targets: a terminal that renders anything
The README opens with a pair of curl commands that look identical. In the second one, two characters in the hostname are Cyrillic U+0456 rather than Latin i, so the URL resolves to an attacker's server. Browsers have had homograph defenses for years; a terminal renders the bytes and moves on. The same gap applies to ANSI escape sequences, bidi overrides, zero-width characters and base64 decode chains, and it widens when an AI agent runs shell commands and installs packages without a human reading them. tirith is aimed at developers who paste install scripts from chat, docs and issue threads, and at teams running agents that execute commands on their behalf. It is a gate in front of execution, not a sandbox around it.
How the hook, the rule engine and the threat database fit together
The mechanism is a shell hook. You activate it by evaluating the output of tirith init in your shell profile, and the README notes that eval "$(tirith init)" auto-detects the shell by inspecting the parent process and falling back to $SHELL. Commands accepted by that shell are checked while the hook is loaded and healthy. The README is explicit that exact blocking behavior depends on the shell and mode, and points to an enforcement by shell section before you treat the hook as an authorization boundary. Detection is rule-based: 244 rules across 35 categories, covering homograph and punycode hostnames, ANSI and bidi injection, invisible whitespace steganography, pipe-to-shell paths through wrappers and decode chains, curl and wget upload flags, credential patterns, and post-compromise behavior such as /proc/*/mem scraping. A separate scan path takes JS and Python files and reports obfuscated payloads, dynamic code execution and secret exfiltration. Known-bad packages, domains and IPs come from a signed threat intelligence database, published as a rolling threatdb-current release alongside versioned releases such as v0.4.1.
Installing tirith and watching the first block happen
The README gives a Homebrew install as the shortest path. On macOS or Linux with Homebrew present, run:
brew install tirithThen activate the hook in your shell profile. For zsh:
eval "$(tirith init --shell zsh)"For bash:
eval "$(tirith init --shell bash)"For fish:
tirith init --shell fish | sourceThe README also documents npm, cargo, mise, apt and dnf as alternative channels, plus a Dockerfile in the repository. After installation and upgrades, run tirith doctor; the README recommends it and it is the fastest way to confirm the hook is loaded and healthy. To see a block without touching a real attacker server, scan a file instead:
tirith scan evil_skill.pyThe README shows output in this shape: a header line with the filename and finding count, then one line per finding with a severity label and a rule name such as dynamic_code_execution or obfuscated_payload. Clean commands such as git status, ls -la and docker compose up -d produce no output at all, which is the intended default.
Blocked, warned, bypassed: reading tirith's three outcomes
tirith does not treat every suspicious command the same way. A homograph hostname is BLOCKED with a CRITICAL severity and the command never executes, according to the README's worked example. A clean URL piped to a shell is only a WARNING at MEDIUM severity, printed to stderr, and the command still runs. That distinction matters: pipe-to-shell is common in legitimate install instructions, so blocking it outright would make the tool unusable, while a mixed-script hostname has almost no benign explanation. The bypass is worth understanding before you deploy this. Prefixing a command with TIRITH=0 disables the check for that command only. Anyone who can type in the shell can therefore walk around the gate, which is why the README frames the hook as a check rather than an authorization boundary. If your threat model includes a user who wants to evade tirith, this is the wrong tool.
What tirith will not catch
The detection surface is text and file content. A malicious binary that arrives through a channel tirith does not inspect, or a compromise that happens after the command has already been accepted, is outside the rule set. The README's own enforcement caveat is the bigger limitation: exact blocking behavior depends on the shell and mode, so a hook that is loaded but not healthy gives you less than the demo output suggests. The scan path is also narrow by design, covering JS and Python files for obfuscated payloads, dynamic code execution and secret exfiltration; other languages are not described. And the threat database is a rolling artifact, so its value tracks how recently it was pulled, not how many rules the binary ships with. The repository pins several transitive dependencies below the Rust 1.83 floor, including home <0.5.12 and time <0.3.38, and the Cargo.toml comments state that a RUSTSEC advisory is ignored in deny.toml because tirith never parses user-controlled RFC 2822 dates. If your policy forbids ignored advisories, that is a conversation to have before adopting.
tirith compared with a shell linter
The closest familiar alternative is a shell linter such as ShellCheck, and the difference in approach is stark. ShellCheck reads a script you have already written and reports syntax and portability problems; it does not run in your interactive shell, does not see pasted text, and has no concept of a homograph hostname or a signed threat feed. tirith evaluates the command you are about to accept, and it inspects Unicode structure rather than shell grammar. The trade-off runs the other way too: ShellCheck is a static analysis pass you run deliberately and can be wired into CI, while tirith depends on a shell hook being installed and healthy in each interactive session. If your problem is a badly written deploy script, ShellCheck is the better fit. If your problem is a URL that looks right and is not, tirith addresses something ShellCheck was never designed to see.
Licence, upgrade cost and where the project publishes changes
tirith is licensed AGPL-3.0-only per the workspace Cargo.toml, with the repository carrying both LICENSE-AGPL and LICENSE-COMMERCIAL files. That dual layout means commercial terms exist separately from the open source grant; whether you need them depends on how you distribute or host a modified version, and that is a question for your own counsel rather than something to settle from a README. The project is not archived and the last push was on 2026-09-10, with v0.4.1 released on 2026-09-02 and the threat database published as a rolling threatdb-current artifact on 2026-08-25. Upgrades are not just binary swaps: the shell hook is generated by tirith init, so a version change can mean regenerating the hook in every profile, and the README asks you to run tirith doctor after installation and upgrades. Budget for that in dotfile management and in any image or container build that bakes in a shell profile. The repository also carries flake.nix, packaging/ and an action.yml, so there are more distribution surfaces to keep in step than a single brew formula.
Editorial conclusion
Adopt tirith if you paste untrusted URLs into a terminal, run AI agents that execute shell commands, or scan third-party skill and config files before loading them, and start with tirith doctor to confirm the hook is loaded. Skip it if you need an unbypassable authorization boundary, since TIRITH=0 disables a check for a single command and enforcement varies by shell. Before rollout, verify the MSRV 1.83 floor against your toolchain, read enforcement by shell in the docs, and decide how you will handle AGPL-3.0-only obligations versus the separate LICENSE-COMMERCIAL file.
Frequently asked questions
What is tirith?
It is a Rust command line tool that inspects commands, pasted content and scanned files for homograph URLs, obfuscated payloads, credential exfiltration and known-bad packages, domains and IPs before they execute. It activates through a shell hook installed with tirith init. The README describes it as terminal security for developers and AI agents.
How do I install tirith?
The README's shortest path is brew install tirith, followed by activating the hook in your shell profile with eval "$(tirith init --shell zsh)" or the equivalent for bash or fish. It is also published via npm, cargo, mise, apt and dnf, and the repository contains a Dockerfile. Run tirith doctor after installation and upgrades.
Does tirith block every suspicious command?
No. The README's examples show a homograph hostname blocked at CRITICAL severity, while a clean URL piped to a shell is only a WARNING at MEDIUM severity and the command still runs. The README also states that exact blocking behavior depends on the shell and mode.
Can I bypass a tirith check?
Yes. Prefixing a command with TIRITH=0 disables the check for that command only, as shown in the README's blocked-command output. The README therefore advises reading the enforcement by shell section before treating the hook as an authorization boundary.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sheeki03-tirith)