黑客技术
点击放大

Your browser would catch this. Your terminal won't.
<p align="center">
<img src="assets/cover.png" alt="tirith, terminal security" width="100%" />
</p>
![CI]()
![GitHub Stars]()

Website | Docs | SKILL.md | Changelog | Releases
<a href="
<img alt=" OSS Program" src=" />
</a>
<sub>Independent open-源码 project, with hosting supported by the Open 源码 Program (Spring 2026 Cohort).</sub>
---
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 服务端. The script executes before you notice.
Browsers solved this years ago. Terminals still render Unicode, ANSI escapes, and in 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, malicious AI skills/configs, and known-bad packages/domains/IPs from a signed threat intelligence 数据库 before they execute.
Then activate in your shell profile:
> [!TIP]
> eval "$(tirith init)" auto-detects your current shell (it inspects the parent process and falls back to $SHELL if needed). The explicit --shell flag is only required when you want to o the detection.
That's it for interacti co. Commands accepted by that shell are
checked while the hook is loaded and healthy; exact blocking behavior depends
on the shell and mode. Run tirith doctor after installation and upgrades, and
read enforcement by shell before treating the hook as
an authorization boundary. Clean commands stay silent and normally take the
fast path.
Also a via npm, cargo, mise, apt/dnf, and more.
---
Homograph attack, blocked before execution:
The command ne.
Pipe-to-shell with clean URL, warned, not blocked:
Warning prints to stderr. Command still runs.
Base64 decode-execute chain, blocked:
Catches decode chains through sudo/en and PowerShell -EncodedCommand too.
Credential exfiltration, blocked:
Covers all curl/wget upload flags, env vars ($AWS_SECRET_ACCESS_KEY), and command substitution.
Malicious skill file, caught on scan:
Scans JS/Python files for obfuscated payloads, dynamic 代码 execution, and secret exfiltration patterns.
Normal commands, in:
Nothing. Zero output. You forget tirith is running.
---
244 detection rules across 35 categories.
| Category | What it stops |
|----------|--------------|
| Homograph attacks | Cyrillic/Greek lookalikes in hostnames, punycode domains, mixed-script labels, lookalike TLDs, confusable domains, text-le detection (math alphanumerics, same-word mixed-script) |
| Terminal injection | ANSI escape sequences, bidi o, zero-width characters, unicode tags, in math operators, selectors, Hangul fillers |
| Steganography defense | In whitespace encoding (12 Unicode space ), Mongolian Vowel Separator, Hangul Filler characters, math alphanumeric substitution, defenses against st3gg-style text steganography |
| Pipe-to-shell | curl \| bash, wget \| sh, httpie \| sh, xh \| sh, python <(curl ...), eval $(wget ...), plus many wrapper, decode, and indirection paths |
| Base64 decode-execute | base64 -d \| bash, python -c "exec(b64decode(...))", powershell -EncodedCommand, decode chains through sudo/en |
| Data exfiltration | curl -d @/etc/passwd, curl -T ~/.ssh/id_rsa, wget --post-file, env ($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 pri escalation, credential file sweeps, calibrated against TeamPCP and UNC1069 post-compromise tooling |
| Command safety | Dotfile o, archi to sensiti, cloud metadata endpoint access, private 网络 access |
| Insecure transport | Plain HTTP piped to shell, curl -k, disabled TLS , shortened URLs hiding destinations |
| En | Proxy hijacking, sensitive en, 代码 injection via en hijack, shell injection env |
| Config file security | Config injection, suspicious indicators, non-ASCII/in unicode in configs, MCP ser (insecure/untrusted/duplicate/permissive) |
| Ecosystem threats | Git clone typosquats, untrusted Docker registries, pip/npm URL installs, web3 RPC endpoints, |
| Install-command safety | APT repos added from a piped download, [trusted=yes] / --allow-unauthenticated / --nogpgcheck / pacman SigLevel = Never (disabled signature checks), kubectl apply -f against raw/shortened remote manifests, Helm charts from untrusted repos, Terraform modules from untrusted remote sources, brew install/tap from arbitrary URLs |
| 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, destructi at Medium) |
| Cloaking detection | Ser cloaking (bot ), clipboard hidden content, PDF hidden text |
| Windows / PowerShell | Set-ExecutionPolicy Bypass / -ep, Windows Defender exclusions (Add-MpPreference -Exclusion*), inline iex (iwr ...) download-execute |
| Terminal output defense | OSC 52 clipboard writes, fake prompts, OSC 8 hyperlink and title / clear-screen manipulation, prompt injection inside command or MCP 工具 output (scanned both raw and deobfuscated, so in, confusable, spaced-out, leetspeak, and short base64 / hex e are caught too), and output data exfiltration (beacon URLs or "read a secret then send it" directives) |
| Operational context | Destructi against labeled-prod cloud / k8s contexts and SSH hosts, Terraform / Pulumi / OpenTofu apply without a matching saved plan, risky sudo escalation, pri docker run |
| Workstation & persistence | Loose-permission credential files and plaintext tokens (~/.ssh, ~/.aws, .npmrc), persistence footholds (shell rc, authorized_keys, crontab, LaunchAgents, git core.hooksPath), PATH-hijack ordering, executable pro, risky aliases, and sensiti lifecycle |
| Blast radius & correlation | Deletes that escape the repo, mass deletions, executing files downloaded from risky sources, and session chains such as secret-write then 网络 or delete then git push --force |
| Trust, attestation & pro | Signed command-card mismatch, canary honeytoken touches, paste 源码-host mismatch, caller-origin (agent) policy denials, MCP lockfile drift, and AI-config drift a known-safe snapshot |
| Web3 command guard | On-chain writes from Cast / Forge / Hardhat / Solana / Anchor commands (High when the same command also disables a declared safety control), raw pri, keypair, or mnemonic material on the command line, and an RPC endpoint or signer the operator's web3_guard policy does not trust. Grammar and policy only: no chain state is read, no transaction is simulated, and no address is scored |
| Wallet exfiltration | Re 钱包, keystore, browser-钱包, and Solana-keypair material flowing to a pro sink, including archi, hex, compressor, and encryptor staging hops and xargs / find -exec operand promotion. A 源码-only read is deliberately not a finding |
| CI artifact poisoning | A fork-reachable workflow that uploads a build artifact, consumed by a pri workflow_run workflow bound to the triggering run that then executes, sources, PATH-mutates, publishes, or deploys it |
---
Tirith analyzes the structure of commands, pasted text, and files before
they execute. It is a pre-execution gate, not a runtime defense, and does not
cover:
tirith check warncapsule run --preset untrusted-project and enforcing pkg install pathstirith run checkstirith browser audit is an explicit,Local
State, ne or quarantines anything, and has no daemon.attest receipt records what two trees containedSee docs/threat-model.md for the full threat model and
explicit non-goals, and
docs/enforcement-co.md for a
capability-by-capability ledger of what tirith detects, decides, enforces,
contains, and attests.
---
tirith doctor to check liTMPDIR can therefore refuse every command, andTIRITH=0 cannot reco the binary is ne. Follow thetirith run, and tirith fetchtirith run --no-exec remains an inspection workflowtirith setup is cross-platform, while eachapt, dnf, yum, pacman).exec directly, or runs without the hook loaded is not coveredos, subprocess, and pty.spawn forms, but it isgetattr/__import__, or a third-party package thatdlp_custom_patterns can currentlytirith install --yes is accepted as therequire_approval channel. It is antask_gate.mode: enforce with action_incomplete_analysis: blockwarn is the default. The alternative,effects_denied_for_untrusted_sources, denies the named effect on every calltirith capsule run --presettirith pkg install are enforceable only onbash -c "cat <wallet>" | curl -d @- <url>,-c body is detected.exec transition, durable state update, authorized resume, andforge create is not yet modelled on engine surfaces;web3_guard fields are parsed but not enforced; and---
Tirith ships a signed local threat 数据库 for package, hostname, and IP reputation. When a shell hook or tirith check sees a package install or suspicious infrastructure reference, it matches that input against the 数据库 before the command executes, instead of relying only on static heuristics.
Signed DB (built by CI, on download and load):
ThreatDB v2 adds exact artifact SHA-256 , installed-file hashes,
malicious URLs, campaign membership, and behavior tags. The signed index,
updater, compiler, and loader support v1 and v2 during the staged cutover,
reject sequence rollback, publish transactionally, and retain a signed
last-known-good 数据库 when an update is incomplete or invalid. The
DigitalSide 源码 is implemented but intentionally inacti its
freshness and operating contract are approved.
Optional supplemental feeds (user-local o):
Optional li during tirith check and daemon mode:
By default, shell hooks and tirith check trigger a cheap background refresh check every 24 hours. Daemon mode keeps the same enrichment path warm in the background.
threat-db explain accepts a domain, a package name (name, ecosystem:name, or name@), or an IPv4 address. The binary retains no per-entry history, so threat-db diff reports category and per-源码 count deltas between snapshots, not the exact entries changed. Every threat-db command takes --format json; threatdb is an alias.
tirith package risk <ecosystem> <name> scores a package's supply-chain / maintainer risk the way tirith score scores a URL, a deterministic, fully explainable sum of named factors, no model and no learned weights. tirith package explain <ecosystem> <name> adds the factor-by-factor deri; both take --format json.
Offline by default. With no flags, every signal is local, with no 网络 call: (1) name packages: known-popular, unknown, or a one-edit near-miss of a popular name (the classic typosquat/slopsquat shape), from the local threat 数据库's popular set; (2) known malicious typosquat: an exact match in the threat DB's typosquat index; (3) install / lifecycle scripts and (4) bundled binary blobs, detected only when the package content is locally a (under node_modules / site-packages, or via --path). tirith ne the package.
--online adds registry pro. It consults the package's registry (npm, PyPI, or crates.io) for six more factors in the *same* factor-sum model: package/ age, an established package with no owners, an abnormal spike, very low downloads, a missing 源码 repo, and yanked/deprecated status. It is the only path on which package risk itself reaches the network; tirith check and daemon mode have a separate, policy-controlled runtime enrichment path. --offline / TIRITH_OFFLINE force this scorer offline regardless. Failures fall back to the offline score with an honest api signals: una, and responses are cached with a TTL so repeated runs do not hammer the registries.
The score is ad and standalone: package risk is not a detection rule and changes no , exit 代码, or audit log.
tirith ecosystem scan [path] is the directory-le to package risk. It walks a project, discovers every dependency manifest it understands, npm (package.json, package-lock.json), Python (requirements*.txt, pyproject.toml), Rust (Cargo.toml), Go (go.mod), Ruby (Gemfile), and scores every declared dependency with the same deterministic package_risk factor engine.
It folds in slopsquat detection. *Slopsquatting* is the registration of a plausible-but-fake name that LLMs tend to hallucinate as a dependency. ecosystem scan flags one only when all three hold: the name is not known-real or popular, it is shaped like an AI hallucination (a language prefix like python- / node- plus descripti, a stack of generic filler like helper / utils / client, or an unusually long name), and it sits near a real popular name (a one-edit near-miss, or it embeds a popular name as a word). Requiring all three keeps false positives low: an honest data-utils with no popular anchor does not fire.
Offline by default, opt-in --online. Name and typosquat signals come from the local threat 数据库; --online adds registry pro, gated and degraded exactly as package risk --online. This flag controls the ecosystem scan and does not alter tirith check's independent runtime-enrichment policy. Findings flow through tirith's normal / Finding model: explainable (tirith explain --rule threat_suspicious_package), audit-logged, and respecting the policy allowlist (an allowlisted package, by bare name or ecosystem:name, is suppressed). Exit codes match tirith scan: 1 for a blocking finding, 2 for ad, 0 when clean.
This helps catch known-malicious packages, confirmed typosquats, slopsquatted package names, malicious download infrastructure, and packages with live OSV / CISA KE data.
Package-name risk is only one layer. Tirith can inspect the exact Python bytes
you already have and, on supported hosts, enforce a hash-pinned install plan:
Inspection covers wheel structure and identity, RECORD integrity and file
ownership, Python startup hooks, native ELF/Mach-O/PE extensions, execution
edges, and loader/payload splits across distributions. pkg graph, pkg diff,pkg attest, and pkg receipt expose the corresponding pro and receipt
e.
The enforcing path supports pip on x86_64 Linux only and requires the
documented nati, a newly dedicated target directory, and an
enrolled fully static native uv. Every unsupported platform fails closed
before pip starts; it ne back to an ordinary install. npm and Cargo
remain non-enforcing e surfaces. See the
0.4.0 release notes and
command reference.
Attack families tirith is built for (illustrative, not a caught-by-current-代码 claim):
| Incident | Year | Attack shape |
|---|---|---|
| Shai-Hulud npm worm | 2025 | Self-propagating package malware; exfiltrated GitHub tokens and AWS keys from 180+ packages, published findings to public Shai-Hulud repos |
| Slopsquatting | 2023 to ongoing | Attackers register LLM-hallucinated package names on npm / PyPI / crates.io; USENIX 2025 found 58% of hallucinated names repeat across runs |
| Team PCP / UNC1069 tooling | ongoing | Post-compromise credential sweeps, /proc/*/mem scraping, Docker pri escalation |
| colors.js / faker.js sabotage | 2022 | Author self-sabotage of widely-used packages |
| e compromise | 2018 | Transferred ownership to attacker; payload targeted Bitcoin wallets |
Package-name extraction currently covers language ecosystems (pip, npm/yarn/pnpm/bun, cargo, gem, go, composer, dotnet, m), not distro-le managers (apt / dnf / yum / pacman). That's why xz-utils, which entered through Linux distro tarballs, is not in the table despite being a headline incident.
---
Tirith adds several independent protection layers around AI coding agents:
config scanning, cooperative MCP tools, an MCP gateway, interacti
hooks, and host-nati hooks where the host exposes a documented
blocking contract. Co depends on which layer the host actually loads.
When an AI agent executes through a hooked interacti (Claude 代码,
Codex, Cursor, etc.), tirith's shell hook checks that interacti before
the shell accepts it. This does not cover a non-interacti, a directexec, or an agent process that ne the hook:
Use tirith setup <tool> for one-command configuration (see AI Agent Integrations).
Run tirith mcp-server or use tirith setup <tool> --with-mcp to register tirith as an MCP 服务端. 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, en |
| 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, in Unicode, config poisoning |
| tirith_scan_directory | Recursive scan with AI config file prioritization |
| tirith_ | MCP configs for insecure servers, shell injection in args, wildcard tools |
| tirith_fetch_cloaking | Detect ser cloaking (different content for bots ) |
The default tools/list is a frozen compatibility contract, because clients
cache it and a 工具 that appears unannounced changes what an agent believes it
may call. A preview tool, tirith_check_task, is therefore **not ad by
default**: run TIRITH_MCP_PREVIEW=1 tirith mcp-server to ad it, and
without that opt-in a 客户端 that calls it by name is refused by name. See
docs/task-en.md.
tirith mcp lock captures every MCP 服务端 a repository declares, across .mcp.json / mcp.json / mcp_settings.json and the IDE config (./, .cursor/, .windsurf/, .cline/, .amazonq/, .continue/, .kiro/), into a deterministic lockfile at .tirith/mcp.lock. Each 服务端 is recorded with its transport (a remote URL, or a local command + args), declared tools, co metadata, and a content hash; servers are sorted by name/源码 so the lockfile is diff-friendly. Ambiguous or credential-bearing declarations are refused instead of copied into 源码 control. En and URL userinfo are represented only by fixed presence markers, never by raw or deterministic hashes: adding/removing a or userinfo still drifts, while secret rotation intentionally does not. V7 lockfiles require one explicit re-lock to migrate to this v8 privacy model. Discovery is repo-local only and touches no 网络. (tirith mcp is a separate command group from tirith mcp-server, which runs tirith *as* an MCP server.)
tirith mcp is the gating companion: it rebuilds the current in against the committed lockfile and exits 1 on drift or incomplete/rejected config co (0 match, 2 on usage errors like a missing lockfile). tirith mcp diff reports the same drift informationally (always exit 0, 2 only on usage errors, so a consumer can tell "no drift" from "could not check"). Drift also surfaces through tirith scan as mcp_ser (Medium or High), so a pre-commit hook or CI catches an MCP-surface change the way it catches an un-pinned action. / diff ne en or URL userinfos, only the names of what changed.
Two policy fields govern what is accepted. Both are keyed by an opaque mcp:v1:... identity binding 源码 path, 服务端 name, and transport: scan.trusted_mcp_servers suppresses that exact 服务端's config findings and drift, while scan.mcp_allowed_tools declares the exact tools it may expose. Bare names intentionally match nothing, so a same-named 服务端 in another config cannot inherit trust. An explicit 工具 allow-list also requires an operator-approved li set and checks both static declarations and li names. Run tirith mcp policy init to scaffold the exact keys into .tirith/mcp-policy.yaml.example, then use the gateway's --mcp-ser ... --appro flow to capture an inspected tools/list baseline atomically. Every scaffold entry is commented out so importing ne widens trust.
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/, ./, .windsurf/, .cline/, .continue/, .roo/, .codex/ configsmcp.json, .mcp.json, mcp_settings.json.github/copilot-instructions.md, .github/agents/*.mdWhat it catches in configs:
tirith scan also inspects the files a repo checks in to describe its own build and deploy pipeline. It detects the dangerous *pattern*, not the 工具: a SHA-pinned action, a digest-pinned image, a local Terraform module, and a normal package.json stay clean.
What it catches in CI / infrastructure files:
.github/workflows/*.yml), an action uses: reference pinned to a mutable ref (@v3, ) instead of a commit SHA; the pull_request_target trigger; a curl … | bash pipe-to-shell in a run: step; an attacker-controllable ${{ github.event.* }} value interpolated into a run: shell step (script injection)FROM base image on the mutable latest tag (or no tag) with no : digest pin*.tf), a module block sourced from a remote / untrusted location rather than a local path or the Terraform RegistryChart.yaml), a chart dependency from an untrusted chart repositorypackage.json: a preinstall / install / postinstall lifecycle script that runs a dangerous command (pipe-to-shell, obfuscated payload, download-and-run); these hooks run automatically on npm installThree built-in --profile tune the scan: ci-hardening (every check at full strength, fail-on high), ai-agent-repo (keeps injection findings, drops low-value pinning-hygiene noise), and oss-maintainer (emphasises contributor-controllable risk when re a change).
Detects content in to humans but readable by AI in HTML, Markdown, and PDF:
display:none, :hidden, opacity:0, font-size:0, off-screen positioningrm -rf or curl|bash (Medium), long comments hiding instructions (Low)tirith scan also inspects file types an AI coding agent (or a renderer) reads and acts on, looking for content smuggled past a human re. A normal notebook, an ordinary CLAUDE.md with instructions, and a plain S stay clean, only hidden / smuggled content fires.
*.ipynb), in / bidi / zero-width characters in cell 源码, a base64-encoded blob embedded in 源码, a cell hidden from the rendered view (metadata.jupyter.source_hidden / a hide_input tag), and cell *outputs* carrying in characters or active / hidden HTMLCLAUDE.md, AGENTS.md, .cursorrules, and similar), *hidden* directives only: an instruction inside an HTML comment (in in rendered Markdown) or a HTML element. These files legitimately contain instructions, so ordinary instructions never fire*.svg), an embedded <script>, an inline on* event handler, a ja: URI, a remote xlink:href / href, or an XXE external-entity declarationtirith fetch compares ser across 6 user-agents (Chrome, ClaudeBot, ChatGPT-User, PerplexityBot, Googlebot, curl) to detect when servers ser content to AI bots .
---
Beyond single commands, several command groups extend the gate to your operating context and workstation state. The ones that touch the hot path are opt-in (a policy flag); the rest run on demand.
Operational context (tirith context, ssh, iac, sudo). Label your prod cloud / Kubernetes contexts and SSH hosts once, and tirith escalates what matters: a destructi against a labeled-prod context, an SSH to a labeled-prod host, a Terraform / Pulumi / OpenTofu apply with no matching saved plan, or a sudo escalation without a reasoned session window. Labels live in ~/.config/tirith/context-labels.yaml and ssh-host-labels.yaml (or repo-scoped under .tirith/).
Workstation hygiene (tirith hygiene, persistence, aliases, env, exec, path, hooks). Scan for loose-permission credential files and plaintext tokens (~/.ssh, ~/.aws, ~/.kube, .npmrc, .pypirc), diff the persistence footholds an attacker uses (shell rc, authorized_keys, crontab, LaunchAgents / systemd-user units, git core.hooksPath), flag aliases that shadow critical commands or read credentials, audit $PATH for hijack ordering, and report a binary's pro (package owner, 代码 signature, whether it shadows a system command).
Blast radius & isolation (tirith preview, watch, temp-run, taint, intend, baseline). Preview the filesystem impact of a destructi before you run it, diff what a command actually changed afterward, run an untrusted command in a throwaway directory, and track files downloaded from risky sources so executing one later fires a finding. temp-run changes only the working directory; it is file isolation, not a sandbox.
tirith command-card) sign a known-good command with an ed25519 key; a trusted card that no longer matches the command fires High.tirith commands) is a .tirith/commands.yaml allowlist that quiets the unknown-command note for cleared commands and adds an ele dangerous[] list (it can tighten a , ne one).tirith canary) plant clearly-synthetic canary tokens; a touch in any checked command, paste, or 工具 output fires High. Detection is a local store lookup, not a shape match.tirith secret) reads recent credential findings from your audit log and prints pro rotate / revoke steps for 11 pro. It ne anything itself and makes no 网络 calls.tirith incident) declares an "under attack" posture: it forces fail_mode: closed, disables the TIRITH=0 bypass, and elevates the credential-sweep, decode-execute, and suspicious-binary rules until you stop it.tirith view, tirith output, gateway run --filter-output, and secure-by-default mcp-server) neutralizes terminal-deception escapes in command, MCP 工具, and resource-read output: OSC 52 clipboard writes, fake prompts, OSC 8 hyperlink mismatch, and title / clear-screen manipulation. It also scans output for prompt injection (raw and deobfuscated) and data-exfiltration beacons. Add custom seeds with injection_seeds_custom, and opt in to redacting an injection-only MCP block to a warning (instead of blocking the whole output) with mcp_redact_injection. The legacy mcp-ser-tool-output escape hatch is not recommended.tirith share, tirith redact, tirith logs) strips secrets and customer / tenant IDs before you paste into a GitHub issue, Slack, an LLM, or a public paste.tirith paste --with-source, tirith browser). With the companion Chrome nati host installed, tirith attributes a pasted command to its 源码 page and flags a paste whose 源码 host differs from where the command runs.---
Homebrew:
Debian / Ubuntu (.deb):
Download from GitHub Releases, then:
Fedora / RHEL / CentOS 8+ and Amazon Linux 2023 (.rpm):
Download from GitHub Releases, then:
The Linux GNU release binaries target a GLIBC 2.28 ceiling. CI runs both
x86_64 and aarch64 tarballs on AlmaLinux 8, Amazon Linux 2023, and Rocky Linux
9; the .deb and x86_64 .rpm contain those same canonical binaries.
Arch Linux (AUR):
Nix:
Android/Termux runs on Bionic libc, not glibc, so the aarch64-unknown-linux-gnu
build cannot run there, it needs glibc's dynamic linker. Use the musl build
instead: tirith-aarch64-unknown-linux-musl.tar.gz is statically linked and runs
on Termux without an external libc.
Then activate the shell hook in ~/.bashrc (Termux's default shell is bash):
> [!NOTE]
> Termux support is best-effort. The musl artifact is built and smoke-tested in
> CI, but tirith is not yet continuously tested on a real Android device.
> If a hook misbeha Termux, please open an issue with tirith doctor
> output.
Windows supports detection, scanning, webhooks, policy management, audit
uploads, and tirith setup. The PowerShell hook provides PSReadLine preflight
interception, but it does not claim a strict post-accept execution receipt.
Li execution and daemon mode remain una on Windows.
Scoop:
Chocolatey (community repository):
Chocolatey moderation can lag the GitHub release. Run choco info tirith to
see the currently appro. Use Scoop or a signed artifact from
GitHub Releases when the
newest release is required before Chocolatey moderation finishes.
npm:
Cargo:
Mise (official registry):
asdf:
Docker:
Add to your shell profile (.zshrc, .bashrc, or config.fish):
| Shell | Hook type | Tested on |
|-------|-----------|-----------|
| zsh | accept-line + paste widgets | 5.8+ |
| bash | enter-key macro or preexec (two modes) | 3.2 compatibility path; 5.0+ for the fully tested modern path |
| fish | Enter-key + paste handlers | 3.5+ |
| PowerShell | PSReadLine handler | 7.0+ |
Bash uses enter mode when a capability self-test has proven it works for your bash, and preexec otherwise. Since 0.4.1 that self-test passes on stock GNU bash, so enter mode is the ordinary outcome once tirith setup or tirith doctor has run it; the shell hook reads the cached at startup. See troubleshooting for details on the modes, the self-test, and SSH fallback behavior.
macOS's system Bash 3.2 remains a compatibility path, not the modern blocking
baseline. Its DEBUG-trap behavior can prevent the trampoline from sticking;
Tirith announces the resulting degradation when its heartbeat can observe it,
which may be one command later. Use Bash 5+ or a pro path when a
strict Bash authorization gate is required.
> [!WARNING]
> Bash's preexec mode is warn-only by default. Set TIRITH_BASH_PREEXEC_ENFORCE=1 for conditional blocking. Tirith scans the trustworthy typed line once, enables its own extdebug only after a block , and releases it before PROMPT_COMMAND runs. If prompt boundaries or a caller-owned DEBUG trap cannot be preser, or extdebug is already user-enabled, Tirith lea interception off instead of clobbering shell state.
#### Enforcement by shell
| Shell | Behavior |
|---|---|
| bash enter mode | Reliable blocking. Binds Enter to a readline macro that runs the checker and then a guarded accept-line, so a command can be stopped before bash commits to running it. Selected wherever the capability self-test (tirith doctor --simulate-enter) has pro and blocking for the running bash, which since 0.4.1 it does on stock GNU bash. A persisted safe-mode flag, an SSH session, or a forced TIRITH_BASH_MODE=preexec still selects preexec. |
| bash preexec + TIRITH_BASH_PREEXEC_ENFORCE=1 | Conditional blocking. Scans one trustworthy whole line, then turns on Tirith-owned extdebug only for a block and restores it at the next prompt. Existing string/array PROMPT_COMMAND entries keep their order and run outside scanning. Enforcement refuses or downgrades when history is filtered or an alias / command substitution / eval makes the typed line drift from BASH_COMMAND; unsafe prompt/DEBUG ownership or user-owned extdebug lea explicitly off rather than mutating user state. |
| bash preexec (no enforce flag) | Warn-only. Prints a DETECTED banner on risky commands; does not block. The fallback when the enter-mode self-test has not pro works, or when enter mode is otherwise una. |
| zsh, fish | Reliable blocking in their Enter/accept-line handlers, before the nati handoff. Notification-only preexec events are not treated as authorization gates. |
| PowerShell | Reliable PSReadLine preflight blocking; no strict execution receipt. |
| nushell | Warn-only (does not currently support command interception). |
For line-le on bash, run tirith doctor --simulate-enter; if delivery works, enter mode is enabled. Where it does not, use preexec enforce for "blocks when possible; tells you honestly when it can't."
Interactive bash, zsh, and fish use a 协议-v3 execution receipt after the
preflight decision. At hook load, they resolve and pin one absolute Tirith
executable and register a one-time capability bound to the li process,
shell family, session, user, and executable identity. A receipt then moves
through Prepared, Armed, Consuming, and a terminalCommitted/Conflict/Discarded state. This impro and replay
resistance, but shell e is deliberately recorded as unresol
than proof that every command component executed. Tirith itself owns any
approval or warning-acknowledgement prompt before returning an armed receipt;
the hook cannot attach those facts later. Zsh and fish consume the armed
receipt synchronously in the same line-acceptance handler and hand the command
to the nati only after that transition succeeds. PowerShell has
preflight blocking without this strict receipt 协议.
A nested shell receives its own process-bound capability even when it inherits
the session ID. Re-sourcing the hook in the same process ne another
bearer. If exec replaces a li without changing its PID/start identity,
the replacement cannot recover the deliberately non-exported bearer and runs in
degraded legacy mode; start a fresh terminal or child shell to restore
strict receipts. exec "$SHELL" is not a receipt-协议 restart because it
preserves that process identity.
Nix / Home-Manager: tirith must be in your $PATH when the hook is sourced.
Bash, zsh, and fish then pin that resol for the shell session;
restart the shell after replacing or upgrading the binary. Adding it toinitContent alone is not enough.
tirith can its own integrity and update itself. Both commands reach the 网络 only when you run them.
tirith confirms the running binary is the genuine, unmodified binary from an official release. It re-downloads the release archive for your and target, it against the signed release checksums.txt, the cosign signature over checksums.txt when cosign is installed, and confirms the running binary is byte-identical to the official one. If full is not possible, a local de, no 网络, an install tirith cannot identify, it says so honestly rather than reporting a false "". With cosign absent the checksum is still (reported as ); install cosign for full signature ().
tirith update is package-manager-aware:
brew upgrade tirith. Updating through the package manager keeps its 数据库 consistent.install.sh tarball, a standalone binary, or a securely owned Tirith release cached under a Hermes root (HERMES_HOME, or ~/.hermes when that is unset; Unix only)) are updated in place: tirith downloads the latest release, it, then atomically swaps the binary, keeping the previous one as a tirith.tirith-previous sidecar. The cosign signature is by default: if it cannot be (cosign missing, or the release published no signature) the update aborts. Pass --allow-unsigned to fall back to checksum-only ; a checksum mismatch always aborts regardless. tirith update --rollback reverts to the previous binary; --dry-run shows what would happen without changing anything. Updates remain explicit: Tirith ne for or installs a new binary in the background.> [!NOTE]
> The install scripts (scripts/install.sh and the Windows install.ps1) also the release's cosign signature by default and abort if cosign is missing or the signature cannot be . Install cosign first, or set TIRITH_ALLOW_UNSIGNED=1 to install with checksum-only (not recommended). A checksum or signature mismatch always aborts regardless of this opt-out.
Oh-My-Zsh:
Use tirith setup <tool> for one-command configuration. This is the complete
named setup surface, including both the earlier integrations and the additions
released in 0.4.0:
| Host | Setup | Protection layer installed by setup | Scope |
|---|---|---|---|
| Claude Code | tirith setup claude-code --with-mcp | Blocking PreToolUse; MCP optional | Project default or user |
| Cline | tirith setup cline | Blocking PreToolUse on POSIX and PowerShell, plus MCP; host runs the 工具 if the hook process fails | User only; hooks must be enabled in Cline |
| OpenAI Codex | tirith setup codex | MCP gateway; optional non-interactive zsh guard with --install-zshenv | User only |
| GitHub Copilot CLI | tirith setup copilot-cli | Blocking preToolUse hook | Project only; launch from repo root |
| Continue | tirith setup continue | MCP only | Project only |
| Cursor | tirith setup cursor | beforeShellExecution hook plus MCP gateway; optional zsh guard | Project default or user |
| Labs fx | tirith setup fx | MCP only | Trusted user profile only |
| Gemini CLI | tirith setup gemini-cli --with-mcp | Blocking BeforeTool; MCP optional | Project default or user |
| Grok Build | tirith setup grok-build | POSIX PreToolUse plus MCP; host can fail open on hook error/timeout | Project default or user |
| Kiro CLI | tirith setup kiro | Blocking agent-scoped preToolUse hook | Project default or user; the Tirith-enabled agent must be loaded |
| OMP / Oh My Pi | tirith setup omp | Blocking tool_call guard plus MCP | User/profile only |
| OpenClaw | tirith setup openclaw | Blocking before_tool_call plugin | Project default or user |
| OpenCode | tirith setup opencode | MCP only | Project default or user |
| OpenHands CLI | tirith setup openhands | POSIX pre_tool_use hook plus user MCP; host can fail open on hook error | User default; project hook also supported |
| Pi CLI | tirith setup pi-cli | Blocking tool_call extension | Project default or user |
| Prime Agent | tirith setup prime-agent | Blocking bash/IPython guard plus MCP | User only |
| Roo Code | tirith setup roo-code | MCP only | Project only |
| VS Code | tirith setup | Workspace hook plus MCP gateway; optional zsh guard | Project only |
| Windsurf | tirith setup windsurf | pre_run_command hook plus MCP gateway; optional zsh guard | User only |
An MCP-only row exposes Tirith's tools but does not force the host to call them.
A hook row is automatic only after the host has loaded the generated artifact
and still honors its refusal contract. Run tirith doctor, restart the host,
and perform the host-shaped allow/block check after setup and every upgrade.
Full config paths, precedence rules, fail-open behavior, and steps
are in the
agent integration and trust matrix.
See mcp/clients/ for the host-specific guides that are a.
GitHub Action with SARIF upload to GitHub Security tab:
The action's pinned dependencies use the Node 24 action runtime. Self-hosted
runners must use Actions Runner v2.327.1 or newer;
GitHub-hosted runners already satisfy this requirement.
Also a as a pre-commit hook: see .pre-commit-hooks.yaml in this repo.
Scan supports --include, --exclude, --profile (loads named profiles from policy), and --ignore filters for targeted CI scanning.
Every finding carries a per-rule remediation: a short, accurate "how to make
this safe" line, shown under each finding (Fix:) and in --format json.tirith explain --rule <id> --fix prints that remediation on its own.
When a command is blocked or warned, tirith check --suggest additionally
prints remediation for the *actual* command. It includes a concrete executable
rewrite only for a narrow mechanical transform whose final command is
under the same effecti:
On x86_64 Linux, when Tirith is installed at a fixed root-managed system path
and the command's URL, shell, arguments, and stdin behavior can be decoded
exactly, the rewrite routes pipe-to-shell through Tirith's bounded, re,
hash-, fail-closed capsule runner. The absolute Tirith path prevents a
later PATH shadow from changing what runs. At execution, the runner also
requires the selected interpreter's first PATH hit to be root-managed, binds
its bytes before downloading, and preserves that shell instead of trusting the
remote shebang. Other architectures, platforms, and user-owned Tirith
installations keep this remediation as guidance. For curl, executable rewrites
additionally require both
fail-on-HTTP-error and redirect-following semantics (-f and -L, including a
bundle such as -fsSL). Dynamic or malformed URL tokens, unsupported
interpreter arguments, PowerShell, Cmd, and ambiguous pipelines remain
guidance-only. Executable suggestions are limited to the , fail-closed
pipe runner. Archi, TLS-flag removal, HTTP-to-HTTPS changes, sudo
narrowing, en scrubbing, and package-name corrections
are guidance-only because their exact shell, 网络, pri, en,
or registry semantics are not mechanically provable. For any finding without a
safe mechanical rewrite, Tirith says so plainly and shows the remediation
instead; it ne a guessed command. The flag is ad: it changes
neither the nor the exit code.
Optional background process for sub-millisecond latency and 网络-aware enrichment (shortened URL resolution, DNS blocklist checks):
> [!NOTE]
> Daemon mode is Unix-only today.
---
The e commands:
| Command | What it does |
|---------|-------------|
| tirith check -- <cmd> | Analyze a command without executing it (--suggest adds remediation and, when , a narrow mechanical rewrite) |
| tirith paste | Check pasted content (called automatically by shell hooks) |
| tirith scan [path] | Scan files, directories, and configs (--profile, --format sarif, --ci) |
| tirith run [--capsule] <url> | Inspect a remote script (--no-exec on Unix); Linux li is contained and fail-closed by default, using the exact re bytes from a sealed anonymous descriptor (--capsule is a legacy compatibility spelling) |
| tirith fix -- <cmd> | Interactively apply a fail-closed pipe-runner rewrite when a; otherwise show guidance |
| tirith score <url> / diff <url> | Break down a URL's trust signals, or show where suspicious characters hide |
| tirith explain --rule <id> / why | Rule docs and remediation, or explain the last trigger |
| tirith status / doctor | Are you protected? Diagnose install, hooks, and policy (--fix, --quick) |
| tirith setup <tool> / init | One-command AI-工具 setup, or print the shell hook |
| tirith policy {init,,test} | Scaffold, , and dry-run your policy |
| tirith trust {add,list,remove} | Manage trusted patterns (narrow scope, 30-day TTL by default) |
| tirith threat-db update | Download and the signed threat 数据库 |
| tirith package risk <eco> <name> | Score a package's supply-chain risk |
| tirith ecosystem scan [path] | Score every declared dependency in a project |
| tirith package inspect --artifact <wheel> | Inspect exact Python artifact bytes, startup hooks, native 代码, RECORD integrity, and cross-wheel execution chains |
| tirith pkg {appro,} | Appro, contain, install, and Python packages on supported x86_64 Linux hosts |
| tirith mcp {lock,} | Pin and gate a repo's MCP servers |
| tirith gateway run | Proxy an upstream MCP 服务端 and enforce configured request/output boundaries |
| tirith daemon start | Background daemon for faster checks (Unix) |
Explicit, opt-in surfaces. None of these run implicitly, and none has a daemon or a background monitor:
| Command | What it does |
|---------|-------------|
| tirith task check | Preview. Assess an untrusted task en (issue body, PDF, web page) and report which effects it would be allowed. Executes nothing and stops nothing |
| tirith capsule run --preset untrusted-project | Copy an untrusted project into a held ephemeral directory and run an exact argv in a fail-closed capsule. Enforceable on x86_64 Linux only; every other host refuses before anything is copied or spawned |
| tirith browser audit | Read-only integrity audit of installed Chromium-family extension 源码 trees, with drift against a signed baseline |
| tirith pkg attest-npm | Ask the project's own npm to its installed packages' registry signatures, bound to the exact lockfile and install tree |
| tirith attest {build,,deployment,} | Point-in-time receipts over two trees and o routes. Not a reproducible-build claim, and not continuous monitoring |
That is the daily-driver set. tirith ships 78 top-le in all, in 8 groups: scan & analyze, status & health, setup, policy & trust, shell & system guards (hygiene, persistence, exec, path, context, ssh, sudo, iac), supply-chain, AI-agent integrations, and forensics & response. Run tirith --help for the categorized list, or see the full command reference. The global --quiet flag (or TIRITH_QUIET=1) silences ad output without hiding errors, , or security notices.
---
paste, score, diff, and why make zerotirith check can query configured OSV/deps.de.ms,tirith check --offline (or TIRITH_OFFLINE=1) suppresses alltirith check and the shell hooksthreat_intel.auto_update_hours), to keep the signed database fresh.auto_update_hours: 0 to disable it, or--offline / TIRITH_OFFLINE=1 to suppress it per in. tirith paste--suggest and explain --fix print a separate command for you to run; they ne one.tirith daemon
start is the only resident process, and it is opt-in.run, fetch, and audit report --upload reach the network only on explicitcheck uses the configured runtime threat sources and the--offline / TIRITH_OFFLINE=1 disablescheck hot-path 网络 producer in both daemon and inline modes.tirith run, fetch --save, and command-cardTIRITH_PRI to a comma-separatedregistry.internal,10.42.0.0/24). The legacy broadTIRITH_ALLOW_PRI=1 switch is not honored. Link-local, special-use,127.0.0.1---
tirith policy init accepts --template <name> for a curated starter policy:
Each template is a well-commented, schema-valid policy you can edit further.
With no --template, tirith policy init writes the full default policy.
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
Use allowlist_rules for rule-scoped suppressions when you trust a 源码 for one rule but do not want to globally allowlist it:
allowlist and allowlist_rules patterns match only URLs extracted from
the input that appear in a finding's e. They ne raw command
text, and a finding with no URL e can never be suppressed by an
allowlist, so a command-shaped pattern like launchctl list is inert. Patterns
use the same grammar as tirith trust: a pattern containing ://, /, ?,
or # is an exact match on the normalized URL (anchored, query and fragment
significant); a bare dotted host such as get.docker.com matches that domain
and its subdomains; *.example.com is an explicit wildcard; a bare token
without a dot is a substring match against the URL text, unless that token is a
public suffix such as com or dev, in which case it is treated as a domain
match against the URL's host and matches every host under it. Inspect what a
policy resolves to with tirith policy effective, and check a specific command
with tirith policy test '<command>'.
tirith trust manages trusted patterns without hand-editing policy YAML. Trust
is narrow and expiring by default: trust the most specific thing that
works, and entries expire after 30 days unless you opt out.
Each entry's scope is classified as exact, substring, domain,wildcard, or bare-TLD. Every non-exact scope (substring / domain /wildcard / bare-TLD) requires --broad, so a sweeping allow is always a
deliberate choice. Exact URLs use normalized URL equality (including scheme,
host, effective port, path, query, and fragment), ne matching. All
subcommands support --format json. Trust stores written by older of
tirith keep working unchanged, an entry with no TTL is treated as permanent.
Warnings are tracked per session. If the same rule fires repeatedly, escalation rules can upgrade to a block:
Review accumulated warnings at any time:
On shell exit, a one-line summary is printed if any warnings were recorded during the session.
More examples in docs/cookbook.md.
Author your own rules in .tirith/policy.yaml under custom_rules:. Each rule is either a pattern: (regex) or a when: semantic predicate tree, plus a context: (exec, paste, or file), a se:, and a title:.
The when: DSL combines all: / any: / not: o like command.has_pipeline_to, command.uses_sudo, url.host, url.host_matches, url.reputation, url.domain_not_in, package.ecosystem, package.name_matches, package.reputation, and file.path_matches. Reputation predicates read the local signed threat 数据库, so a custom rule still makes no 网络 call on the hot path. and dry-run before committing:
Other policy keys, all with safe defaults (tirith policy init writes the fully-commented set):
package_policy: thresholds turn supply-chain signals into block or warn (block_typosquat_distance, warn_low_downloads_below, block_newer_than_days, block_not_found).agent_rules: allow: / deny: match a command's caller origin ({ kind, name }); a deny match forces a block. scan.trusted_mcp_servers and scan.mcp_allowed_tools accept specific MCP servers and per-ser.en, exec_guard_enabled, hooks_guard_enabled, baseline_enabled, plus iac_require_plan_before_apply, sudo_require_reason, and allowed_install_domains.Repo-scoped .tirith/policy.yaml files can only tighten, ne: a repo policy that tries to widen an allowlist, lower a se, or disable a guard is neutralized, and tirith policy effective shows which fields were dropped. Only user-level and org-level (TIRITH_POLICY_ROOT) policies can relax a default.
With strict_warn: true (or --strict-warn on the CLI), medium-risk findings prompt for explicit acknowledgement in interacti instead of silently warning:
Shell hooks use exit 代码 3 for the warn-ack 协议. Old hooks that don't know about exit 代码 3 fall through to fail-open behavior.
> [!NOTE]
> Exit 代码 3 is the warn-ack hook 协议 path, not the normal direct-CLI contract. Non-hook callers should not normally see exit 代码 3; if they do, it indicates acknowledgement is required.
For the rare case you know exactly what you're doing:
This is a standard shell per-command prefix; the exists only for that single command and does not persist in your session. Organizations can disable it entirely with allow_bypass_en in policy.
> [!CAUTION]
> TIRITH=0 is per-command. Do not export it in shell profiles, dotfiles, or CI config; a permanent bypass defeats the entire protection model. If you find yourself reaching for it often, add the trusted 源码 to allowlist in your policy file instead.
---
Local JSONL audit log at ~/.local/share/tirith/log.jsonl:
raw_action, raw_rule_ids) preser enforced action for co auditing~/.local/state/tirith/sessions/Disable: export TIRITH_LOG=0
---
Feature guides:
web3_guard policy, the three Web3 rules, and command-card v2 bindings)task_gate policy, and the preview MCP tool)pkg attest-npm, and exactly what it does not bind)agent_rules)--quick, and the JSON snapshot schema)paste_source_mismatch rule)tirith prompt-status into your shell prompt)Core security co ships in the open-源码 tree. All 244 detection rules and the MCP 服务端 are a from 源码. The repository still contains legacy licensing and policy-服务端 代码 paths, so avoid assuming that every runtime path is already tier-free.
tirith is dual-licensed:
Third-party data attributions in NOTICE.
![Star History Chart]()
开发语言:Rust
Star 数:2751
该项目属于黑客技术方向的开源资源,代码结构清晰,适合安全研究人员学习参考。项目围绕Terminal security for de and AI agents. Intercepts homograph URLs, pipe-to-shell, ANSI injection, obfuscated payloads, data exfiltration, and malicious AI skills/configs before they execute.展开,提供了可运行的实现,对理解相关安全机制、工具原理与攻防技术具有较高的研究价值。
从技术实现角度看,此类项目的核心在于对底层原理的深入理解与工程化落地。开发者在研究过程中可以结合官方文档与社区讨论,梳理项目的初始化流程、核心模块职责与数据流转,逐步掌握其中的技术细节与设计思路。
1. 下载源码压缩包,解压到本地环境。
2. 根据项目类型准备对应的运行环境与依赖。
3. 按照 README 中的步骤完成配置与部署。
4. 在隔离的测试环境中运行验证,避免影响生产系统。
本资源来源于开源社区,仅供安全研究、技术学习与合法授权范围内的攻防演练使用,请勿用于任何非法或侵害他人权益的用途。使用本项目产生的一切后果由使用者自行承担。
| 资源标题 | tirith - Terminal security for developers and AI agents. In |
| 所属分类 | 黑客技术 |
| 发布作者 | admin |
| 发布时间 | 2026-10-05 22:11:44 |
| 下载权限 | 免费下载 |
| 附件数量 | 2 个下载链接 |
| 浏览次数 | 256 |
| 讨论数量 | 0 |
| 主题状态 | 正常讨论中 |