Policy Cookbook
May 22, 2026 · View on GitHub
0. Start From a Template
tirith policy init --template <name> writes a curated, well-commented,
schema-valid starter policy. It is the fastest way to a sensible baseline you
can then edit:
tirith policy init --template individual # solo developer defaults
tirith policy init --template ci-strict # fail-closed CI, no bypass
tirith policy init --template ai-agent-heavy # heavy AI-agent environments
individual—fail_mode: open,paranoia: 1, the noisyshortened_urlrule escalated, an emptyallowlistready to fill in.ci-strict—fail_mode: closed, theTIRITH=0bypass disabled (interactive and non-interactive),strict_warn: true, remote-execution rules escalated to CRITICAL, andscan.fail_on: highsotirith scanfails the build.ai-agent-heavy—fail_mode: open(so an internal error cannot wedge an agent),paranoia: 3, the non-interactive bypass disabled,approval_rulesfor the highest-risk pipe-to-shell rules, andescalationrules that block on repeated warnings.
tirith policy init with no --template writes the full default policy.
The recipes below show hand-tuned variations on these baselines.
0b. Tune From Your Audit Log
Once tirith has some history, tirith policy tune --from-audit reads your
audit log and suggests conservative policy adjustments:
tirith policy tune --from-audit
tirith policy tune --from-audit --format json # machine-readable
It is suggest-only — it never edits your policy. The headline suggestion
is a rule you allowed or bypassed every time and never blocked: that rule
is probably firing on something you trust, so an allowlist entry or a
severity_overrides downgrade may be warranted. A rule you sometimes block
on is never suggested for a downgrade — it is doing its job. Every suggestion
is a plain count from the log, not an inference; when the log is too small to
be meaningful, policy tune says so rather than guessing. Review each
suggestion, then apply it by hand to .tirith/policy.yaml.
1. Strict Organization (Fail Closed, No Bypass)
# .tirith/policy.yaml (repo root)
fail_mode: closed
allow_bypass_env: false
severity_overrides:
shortened_url: HIGH
plain_http_to_sink: CRITICAL
All findings block execution. No bypass mechanism. Shortened URLs and plain HTTP are escalated.
2. Personal Developer (Defaults + Allowlist)
# ~/.config/tirith/policy.yaml
fail_mode: open
allow_bypass_env: true
With allowlist at ~/.config/tirith/allowlist:
raw.githubusercontent.com
homebrew.bintray.com
get.docker.com
Default severity mappings. Allowlisted URLs skip analysis.
3. CI Safe Mode (Non-Interactive, JSON Output)
# In CI pipeline
tirith check --non-interactive --format json -- curl https://example.com/setup.sh | bash
EXIT=$?
if [ $EXIT -eq 1 ]; then
echo "BLOCKED by tirith" >&2
exit 1
fi
Non-interactive mode never prompts. JSON output for machine parsing.
4. Docker-Focused (Escalate Docker Rules)
# .tirith/policy.yaml
severity_overrides:
docker_untrusted_registry: CRITICAL
docker_tag_latest: HIGH
All Docker-related findings are escalated. Other rules use default severity.
5. Learning Mode (All Low Severity)
# ~/.config/tirith/policy.yaml
fail_mode: open
allow_bypass_env: true
severity_overrides:
curl_pipe_shell: LOW
wget_pipe_shell: LOW
pipe_to_interpreter: LOW
punycode_domain: LOW
confusable_domain: LOW
Everything becomes a LOW-severity warning. Nothing blocks. Useful for onboarding.
6. cargo-vet (Rust Supply-Chain Audit)
tirith detects when cargo install or cargo add is run in a project that
hasn't configured cargo-vet. The
vet_not_configured rule fires at LOW severity by default. To escalate:
# .tirith/policy.yaml
severity_overrides:
vet_not_configured: HIGH
To suppress it (e.g. for non-Rust repos):
# ~/.config/tirith/allowlist
# or .tirith/allowlist
vet_not_configured
7. vet (getvet.sh) — Safe Pipe-to-Shell
When tirith blocks a curl | bash pattern, the safest alternatives are:
Ask tirith for the rewrite
tirith check --suggest-safe-command prints a concrete safer version of the
exact command you ran:
tirith check --suggest-safe-command -- 'curl https://example.com/install.sh | bash'
# tirith: safer alternative
# curl_pipe_shell
# try: curl -fsSL -o /tmp/tirith-review.sh https://example.com/install.sh \
# && less /tmp/tirith-review.sh && bash /tmp/tirith-review.sh
It also drops insecure-TLS flags and upgrades http:// to https://. For
findings with no safe mechanical rewrite it says so plainly instead of guessing.
The flag is advisory — it never changes the verdict or exit code. Use
tirith explain --rule curl_pipe_shell --fix to see a rule's remediation on its
own.
Using tirith run (built-in, Unix only)
tirith run downloads, inspects, and prompts before executing:
# Instead of: curl -fsSL https://example.com/install.sh | bash
tirith run https://example.com/install.sh
Download and inspect only (no execution):
tirith run --no-exec https://example.com/install.sh
Pin to a known hash:
tirith run --sha256 abc123... https://example.com/install.sh
Using tirith install (recorded install transaction)
tirith install wraps a real package install with pre-execution
supply-chain risk analysis and records the transaction. It analyzes first,
presents a verdict, takes a working-directory checkpoint and an audit entry,
then runs the real npm install / pip install / cargo install (or the
downloaded script for the url form) only after the analysis and your
go-ahead:
# Instead of: npm install left-pad
tirith install npm left-pad
A block refuses the install (bypassable per policy with TIRITH=0); a warn
asks for acknowledgement; an allow proceeds. tirith's own flags go before
the source — everything after the source is passed verbatim to the package
manager:
# --online adds registry-API provenance signals; --save-dev goes to npm
tirith install --online npm some-pkg --save-dev
# Analyze and record only — do not run the real install
tirith install --no-exec pip requests
# Proceed past warnings without the interactive prompt
tirith install --yes cargo ripgrep
# The url form delegates to `tirith run`'s safe download-and-run machinery
tirith install url https://get.example-tool.sh
tirith install is pre-execution install-risk analysis plus a recorded
transaction. It does not sandbox or isolate the install — the real
install runs with your full privileges. The checkpoint is a before/after
record (tirith checkpoint diff <id>), not an automatic rollback. Runtime
sandboxing is an explicit tirith non-goal.
Using vet (external, cross-platform)
vet is an external tool for safer remote-script workflows (see getvet.sh for details):
# Instead of: curl -fsSL https://example.com/install.sh | bash
vet https://example.com/install.sh
Both approaches ensure you can inspect the script before it runs.
Policy: suppress pipe-to-shell for trusted sources
If you routinely install from trusted URLs, allowlist them instead of bypassing:
# .tirith/policy.yaml
allowlist:
- "get.docker.com"
- "raw.githubusercontent.com/org/repo"
CLI: manage trust without editing YAML
tirith trust does the same thing from the command line, and steers you
toward the narrowest scope that works. Trusting a specific path is accepted
as-is; trusting a whole domain is broad and must be opted into with --broad.
Entries expire after 30 days by default, so a temporary allow does not linger.
# Narrow: trust one exact resource. Expires in 30 days.
tirith trust add raw.githubusercontent.com/org/repo/main/get.sh
# Broad: trust a whole domain for one rule only. --broad is required.
tirith trust add get.docker.com --broad --rule curl_pipe_shell
tirith trust list # see every entry, its scope, and its TTL
tirith trust explain get.docker.com
tirith trust diff # what changed since last time
tirith trust gc --expired # remove entries whose TTL has passed
Use --permanent if an entry genuinely should never expire, and --reason
to record why it was added — tirith trust explain shows it back to you.