# tirith
**Your browser would catch this. Your terminal won't.**
[](https://github.com/sheeki03/tirith/actions/workflows/ci.yml)
[](LICENSE-APACHE)
---
Can you spot the difference?
```
```
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.**
```bash
brew install sheeki03/tap/tirith && eval "$(tirith init)"
```
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 install -g tirith`, `cargo install tirith`, `scoop`, and [more](#install).
---
## See it work
**Homograph attack — blocked before execution:**
```
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.
Set TIRITH=0 to bypass (use with caution)
```
The command never executes.
**Pipe-to-shell with clean URL — warned, not blocked:**
```
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.
| **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:**
```bash
brew install sheeki03/tap/tirith
```
**Linux / macOS (shell script):**
```bash
**npm:**
```bash
npm install -g tirith
```
**Cargo:**
```bash
cargo install tirith
```
**Windows:**
```powershell
scoop bucket add tirith https://github.com/sheeki03/scoop-tirith
scoop install tirith
```
**Arch Linux (AUR):**
```bash
pacman -S tirith
```
Then activate — add to your `.zshrc`, `.bashrc`, or `config.fish`:
```bash
eval "$(tirith init)"
```
| zsh | preexec + paste widget | 5.8+ |
| bash | preexec (two modes) | 5.0+ |
| fish | fish_preexec event | 3.5+ |
| PowerShell | PSReadLine handler | 7.0+ |
---
## Commands
```
tirith check -- <cmd> Analyze a command without executing
tirith paste Analyze clipboard/pasted content
tirith score <url> URL trust breakdown
tirith diff <url> Byte-level Unicode comparison
tirith why Explain the last triggered rule
tirith run <url> Download-first safe installer runner
tirith receipt {last,list,verify} Install script tracking
tirith init Print shell hook for eval
tirith doctor Diagnostic info (paths, shell, policy)
```
**`tirith run`** replaces `curl | bash` with a safe workflow: download to temp file, show SHA256, static analysis, review in pager, execute only after confirmation. Creates a receipt you can verify later.
**`tirith diff`** shows you exactly what's wrong, byte by byte:
```
$ tirith diff https://exаmple.com
---
## What tirith never does
- **No network calls** during `check` or `paste` — 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:
1. `.tirith/policy.yaml` in current directory (walks up to repo root)
2. `~/.config/tirith/policy.yaml`
```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](docs/cookbook.md).
**Bypass** for the rare case you know exactly what you're doing:
```bash
Organizations can disable this: `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](docs/threat-model.md) — what tirith defends against and what it doesn't
- [Cookbook](docs/cookbook.md) — policy examples for common setups
- [Troubleshooting](docs/troubleshooting.md) — shell quirks, latency, false positives
- [Compatibility](docs/compatibility.md) — stable vs experimental surface
- [Security policy](SECURITY.md) — vulnerability reporting
- [Uninstall](docs/uninstall.md) — clean removal per shell and package manager
## License
Apache-2.0. See [LICENSE-APACHE](LICENSE-APACHE) for details.
Third-party data attributions in [NOTICE](NOTICE).