feat(security): add tirith pre-exec command scanning#904
Closed
sheeki03 wants to merge 1 commit into
Closed
Conversation
Integrate tirith as a pre-execution security scanner that detects homograph URLs, pipe-to-interpreter patterns, terminal injection, zero-width Unicode, and environment variable manipulation — threats the existing 50-pattern dangerous command detector doesn't cover. Architecture: gather-then-decide — both tirith and the dangerous command detector run before any approval prompt, preventing gateway force=True replay from bypassing one check when only the other was shown to the user. New files: - tools/tirith_security.py: subprocess wrapper with auto-installer, mandatory cosign provenance verification, non-blocking background download, disk-persistent failure markers with retryable-cause tracking (cosign_missing auto-clears when cosign appears on PATH) - tests/tools/test_tirith_security.py: 62 tests covering exit code mapping, fail_open, cosign verification, background install, HERMES_HOME isolation, and failure recovery - tests/tools/test_command_guards.py: 21 integration tests for the combined guard orchestration Modified files: - tools/approval.py: add check_all_command_guards() orchestrator, add allow_permanent parameter to prompt_dangerous_approval() - tools/terminal_tool.py: replace _check_dangerous_command with consolidated check_all_command_guards - cli.py: update _approval_callback for allow_permanent kwarg, call ensure_installed() at startup - gateway/run.py: iterate pattern_keys list on replay approval, call ensure_installed() at startup - hermes_cli/config.py: add security config defaults, split commented sections for independent fallback - cli-config.yaml.example: document tirith security config
Contributor
|
Merged via PR #1256. Your substantive work was cherry-picked onto current main with authorship preserved, then adapted for current-main compatibility (approval UX, yolo/noninteractive semantics, and gateway replay integration). Thank you for the contribution. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Integrates tirith as an optional pre-execution security scanner that detects content-level threats the existing 50-pattern dangerous command detector does not cover: homograph URLs, pipe-to-interpreter variants, ANSI terminal injection, zero-width Unicode, and environment variable manipulation (66 rules across 11 categories).
The existing
approval.pydangerous command detector catches destructive ops (rm -rf, DROP TABLE, fork bombs) but cannot inspect command content — URL payloads, Unicode tricks, or escape sequences. tirith fills that gap with a Rust binary that runs as a subprocess, adding sub-millisecond scanning before each command execution.Architecture: gather-then-decide. Both tirith and the dangerous command detector run before any approval prompt, and their findings are combined into a single approval request. This prevents the gateway
force=Truereplay from bypassing one check when only the other was shown to the user.Auto-install with mandatory provenance verification. When tirith is not found on PATH, the wrapper automatically downloads the latest release from GitHub and verifies it via cosign (Sigstore) before installation. SHA-256 alone only proves self-consistency; cosign verifies the release was produced by the expected GitHub Actions workflow. If cosign is unavailable, auto-install is skipped (users can install tirith manually).
Non-blocking startup. The network download runs in a background daemon thread so CLI/gateway startup is never blocked. Commands fail-open until tirith becomes available.
Failure recovery. Failed installs are cached to disk with a reason tag. The
cosign_missingreason auto-clears when cosign later appears on PATH — both in-memory and on disk — so a long-lived gateway picks up the change without restart.Related Issue
N/A — new security feature
Type of Change
Changes Made
New files
tools/tirith_security.py(~665 lines) — Subprocess wrapper + auto-installercheck_command_security(): main API, returns{action, findings, summary}ensure_installed(): non-blocking startup hook_verify_cosign,_install_tirith)_mark_install_failed,_read_failure_reason)_get_hermes_home)fail_openconfig; programming errors propagatetests/tools/test_tirith_security.py(~958 lines, 62 tests) — Full coverage of the subprocess wrappertests/tools/test_command_guards.py(~312 lines, 21 tests) — Integration tests for combined guard orchestrationModified files
tools/approval.py— Newcheck_all_command_guards()orchestrator (~80 lines added)allow_permanent: boolparameter onprompt_dangerous_approval()— hides[a]lwayswhen any tirith warning present (tirith keys are session-scoped, too broad for permanent allowlisting)pattern_keyslist in pending approval dict for gateway replaytools/terminal_tool.py— Replace direct_check_dangerous_commandcall withcheck_all_command_guardscli.py— Update_approval_callbacksignature forallow_permanentkwarg; callensure_installed()at startupgateway/run.py— Iteratepattern_keyslist on replay approval (backward compatible viapattern_keyfallback); callensure_installed()at startuphermes_cli/config.py— Addsecurityconfig defaults toDEFAULT_CONFIG(tirith_enabled, tirith_path, tirith_timeout, tirith_fail_open); split_COMMENTED_SECTIONSinto independent blocks so unrelated defaults don't clobber each othercli-config.yaml.example— Document tirith security config section with install instructionsHow to Test
Run the new test suites:
Verify no regression in existing approval tests:
pytest tests/tools/test_approval.py -v # 53 testsFull suite:
Manual testing (requires tirith installed —
brew install sheeki03/tap/tirith):echo helloshould pass through[a]lwaysapproval option should be hidden when tirith warnings are presentChecklist
Code
feat(security): add tirith pre-exec command scanning)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A