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
  • individualfail_mode: open, paranoia: 1, the noisy shortened_url rule escalated, an empty allowlist ready to fill in.
  • ci-strictfail_mode: closed, the TIRITH=0 bypass disabled (interactive and non-interactive), strict_warn: true, remote-execution rules escalated to CRITICAL, and scan.fail_on: high so tirith scan fails the build.
  • ai-agent-heavyfail_mode: open (so an internal error cannot wedge an agent), paranoia: 3, the non-interactive bypass disabled, approval_rules for the highest-risk pipe-to-shell rules, and escalation rules 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.