Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
#+title: guard-sh #+author: Berdan Akyürek A shell safety layer powered by LLMs. Every command you type is checked before it runs — risky ones require confirmation. #+begin_example $ rm -rf /var/log/* guard-sh: Deletes all files in /var/log recursively. Are you sure? [Y/n] #+end_example * How it works :PROPERTIES: :CUSTOM_ID: how-it-works :END: 1. You press Enter on a command. 2. guard-sh intercepts it and sends it to an LLM. 3. If the LLM considers it safe, the command runs normally. 4. If not, you see a one-line warning and a ~[Y/n]~ prompt. 5. Press ~Y~ to proceed, anything else to cancel. Commands in your *whitelist* skip the LLM entirely. * Supported LLMs :PROPERTIES: :CUSTOM_ID: supported-llms :END: - Gemini (free tier available at [[https://aistudio.google.com]]) - DeepSeek ([[https://platform.deepseek.com]]) - OpenAI ([[https://platform.openai.com]]) - Claude ([[https://console.anthropic.com]]) - Ollama (local, no API key required — [[https://ollama.com]]) * Requirements :PROPERTIES: :CUSTOM_ID: requirements :END: - Go 1.22+ - bash, zsh, or fish - API key(s) for one or more of the cloud LLMs listed above, *or* a running [[https://ollama.com][Ollama]] instance for fully local operation. * Installation :PROPERTIES: :CUSTOM_ID: installation :END: ** From source #+begin_src bash git clone https://github.com/berdanakyurek/guard-sh.git cd guard-sh bash install.sh #+end_src This will: - Build the ~guard-sh~ binary and install it to =~/.local/bin/= - Copy the default config to =~/.config/guard-sh/config.yaml= - Copy the default prompt to =~/.config/guard-sh/prompt.txt= - Add shell integration to the rc file of your *active* shell (bash → =.bashrc=, zsh → =.zshrc=, fish → =config.fish=) ** From binary Download the binary for your platform from the [[https://github.com/berdanakyurek/guard-sh/releases][releases page]], place it in =~/.local/bin/=, then run: #+begin_src bash chmod +x ~/.local/bin/guard-sh guard-sh setup #+end_src This will create the config dir, write the shell integration scripts, and add them to the rc file of your active shell. ** Install without shell integration #+begin_src bash bash install.sh --without-shell #+end_src ** Multiple shells ~install.sh~ and ~guard-sh setup~ only add integration for your *active shell* (detected via the parent process). If you use multiple shells, run the command once from each: #+begin_src bash # In bash bash install.sh # In zsh bash install.sh # In fish bash install.sh #+end_src Or use ~guard-sh setup~ after installing the binary: #+begin_src bash # In bash guard-sh setup # Switch to zsh, then: guard-sh setup #+end_src * Uninstallation :PROPERTIES: :CUSTOM_ID: uninstallation :END: ** Using the shell script #+begin_src bash bash uninstall.sh #+end_src This removes the binary, shell integration from all rc files, and the config dir. To keep your config (providers, whitelist, cache): #+begin_src bash bash uninstall.sh --keep-config #+end_src ** Using the built-in command #+begin_src bash guard-sh uninstall # removes shell integration and shell scripts, keeps config guard-sh uninstall --purge # also removes the config dir (~/.config/guard-sh) #+end_src After running ~guard-sh uninstall~, the binary itself is not removed (a running program cannot delete itself reliably). The command prints the path to remove manually: #+begin_example next remove the binary manually: rm ~/.local/bin/guard-sh #+end_example * Configuration :PROPERTIES: :CUSTOM_ID: configuration :END: Config file: =~/.config/guard-sh/config.yaml= - Providers can be added interactively with =guard-sh provider add= — see [[#providers][Providers]]. - The whitelist can also be managed with =guard-sh whitelist= commands — see [[#whitelist][Whitelist]]. - Cache settings can also be managed with =guard-sh cache= — see [[#cache][Cache]]. Or you can edit the config file manually. An example config file is as follows: #+begin_src yaml # Providers are tried in order. If one fails, the next is used. provider_order: - ollama - gemini - deepseek - openai - claude providers: ollama: host: http://localhost:11434/api/chat # optional, this is the default model: llama3.2 # optional, this is the default gemini: api_key: YOUR_GEMINI_KEY model: gemini-3.1-flash-lite-preview # optional, this is the default deepseek: api_key: YOUR_DEEPSEEK_KEY model: deepseek-chat # optional, this is the default openai: api_key: YOUR_OPENAI_KEY model: gpt-4o-mini # optional, this is the default claude: api_key: YOUR_ANTHROPIC_KEY model: claude-haiku-4-5-20251001 # optional, this is the default # Commands always considered safe — LLM is never called. # Only the base command name is needed: "ls" covers "ls -la", "ls /tmp", etc. # For chained commands (&&, ||, ;, |), ALL parts must be whitelisted. command_whitelist: - ls - cat - echo - pwd - cd - clear # How long to wait for an LLM response, in seconds. Defaults to 10 if not set. timeout_seconds: 10 # Include the working directory in the command sent to the LLM. # Allows the LLM to assess risk based on context (e.g. chmod in /etc vs ~/projects). # Cache keys include the working directory when this is enabled. # Set to false to send only the command string. send_working_directory: true # Cache LLM responses to avoid repeated API calls for the same command. cache_enabled: true cache_max_size: 1000 #+end_src ** Custom prompt The LLM prompt is stored at =~/.config/guard-sh/prompt.txt=. Edit it to adjust what guard-sh considers risky, change the output format, or add your own rules. Changes take effect immediately — no rebuild needed. ** Working directory context By default, guard-sh includes the current working directory when querying the LLM. This gives the LLM context to make better decisions — for example, =chmod 777 config.yaml= in =/etc= is riskier than in =~/projects/=. The LLM receives a structured two-line query: #+begin_example Working directory: /etc Command: chmod 777 config.yaml #+end_example The working directory is also included in cache keys, so the same command in different directories is evaluated independently. To disable: #+begin_src yaml send_working_directory: false #+end_src ** Redaction :PROPERTIES: :CUSTOM_ID: redaction :END: guard-sh can redact sensitive values from commands before sending them to any LLM provider. Two methods are supported: pattern-based and Shannon entropy-based. Matches are replaced with =[REDACTED]=. Redaction is configured globally under =redaction:= in your config, and can be overridden per-provider under =providers.NAME.redaction=. #+begin_src yaml redaction: pattern_based: enabled: true patterns: - '(?i)(password|passwd|secret|api[_-]?key|token|auth)\s*[=:]\s*\S+' - '(?i)--password\s+\S+' - 'AKIA[0-9A-Z]{16}' shannon_entropy_based: enabled: false threshold: 4.5 # bits/char — typical English ~3.5, secrets typically >4.5 min_length: 20 # ignore tokens shorter than this #+end_src *** Pattern-based redaction Uses Go regular expressions. Any token matching a pattern is replaced with =[REDACTED]=. On by default for all providers. You can add your own patterns: #+begin_src yaml redaction: pattern_based: enabled: true patterns: - '(?i)(password|passwd|secret|api[_-]?key|token|auth)\s*[=:]\s*\S+' - '(?i)--password\s+\S+' - 'AKIA[0-9A-Z]{16}' - 'ghp_[a-zA-Z0-9]{36}' # GitHub personal access tokens - 'xox[baprs]-[a-zA-Z0-9-]+' # Slack tokens #+end_src Toggle per-provider: #+begin_src bash guard-sh redact list # list all global redaction patterns guard-sh redact pattern on # interactively enable for a provider guard-sh redact pattern off # interactively disable for a provider #+end_src *** Shannon entropy-based redaction Automatically detects high-entropy strings (API keys, random tokens, private key material) without requiring explicit patterns. Any whitespace-delimited token above the entropy threshold is replaced with =[REDACTED]=. Off by default. Toggle per-provider: #+begin_src bash guard-sh redact entropy on # interactively enable for a provider guard-sh redact entropy off # interactively disable for a provider #+end_src *** Per-provider overrides A provider can override any part of the global redaction config: #+begin_src yaml providers: ollama: host: http://localhost:11434/api/chat redaction: # optional — overrides global for this provider pattern_based: enabled: false # disable pattern redaction for local model gemini: api_key: YOUR_KEY redaction: shannon_entropy_based: enabled: true threshold: 4.0 # more sensitive than global default min_length: 16 #+end_src Fields not specified in the per-provider block fall back to the global value. ** Provider-specific prompts To use a different prompt for a specific provider, create =prompt_PROVIDERNAME.txt= in the same directory: #+begin_src bash ~/.config/guard-sh/prompt.txt # default, used by all providers ~/.config/guard-sh/prompt_ollama.txt # used only when ollama is queried ~/.config/guard-sh/prompt_gemini.txt # used only when gemini is queried #+end_src If a provider-specific file exists, it takes precedence over =prompt.txt= for that provider. * Usage :PROPERTIES: :CUSTOM_ID: usage :END: guard-sh is designed to run as a shell integration — it intercepts every command you type automatically. Once installed, you don't need to invoke it manually. You can test it directly with: #+begin_src bash guard-sh check "rm -rf /" #+end_src This is useful for testing your configuration, but normal usage happens transparently through the shell hook. ** Help #+begin_src bash guard-sh help #+end_src Lists all commands and their usage. ** Session control #+begin_src bash guard-sh on # enable for the current session guard-sh off # disable for the current session #+end_src ** Global control #+begin_src bash guard-sh on --global # auto-enable in every new terminal guard-sh off --global # don't auto-enable in new terminals #+end_src ~off --global~ does not remove guard-sh from your shell — it only stops it from auto-enabling. You can still use ~guard-sh on~ in any terminal at any time. ** Healthcheck #+begin_src bash guard-sh healthcheck #+end_src Checks all configured providers without spending any tokens. Reports: - API key and model validity for cloud providers; server connectivity for Ollama - Response latency per provider - Shell integration status (~.bashrc~ / ~.zshrc~ / ~config.fish~) - Config errors (e.g. unknown provider names in ~provider_order~) #+begin_example guard-sh healthcheck providers ollama llama3.2 ● ok (12ms) gemini gemini-3.1-flash-lite-preview ● ok (184ms) deepseek deepseek-chat ✗ HTTP 401: Authentication Fails openai gpt-4o-mini ● ok (759ms) claude claude-haiku-4-5-20251001 ● ok (510ms) shell bash ~/.bashrc ● present zsh ~/.zshrc ○ not found fish ~/.config/fish/config.fish ○ not found #+end_example ** Status #+begin_src bash guard-sh status #+end_src Shows session and global state, prompt path, timeout, cache stats, active providers, redaction patterns, shell integration status, and whitelist. #+begin_example guard-sh session ● on global ● on config ~/.config/guard-sh/config.yaml prompt ~/.config/guard-sh/prompt.txt timeout 10s work dir ● on cache ● on 1000 max entries 42 entries, 8.3 KB providers 1 gemini model gemini-3.1-flash-lite-preview pattern redaction ● on entropy redaction ○ off 2 ollama model llama3.2 pattern redaction ○ off entropy redaction ○ off 3 deepseek model deepseek-chat pattern redaction ● on entropy redaction ○ off redaction 1 (?i)(password|passwd|secret|api[_-]?key|token|auth)\s*[=:]\s*\S+ 2 (?i)--password\s+\S+ 3 AKIA[0-9A-Z]{16} shell bash ~/.bashrc ● present zsh ~/.zshrc ○ not found fish ~/.config/fish/config.fish ○ not found whitelist 1 ls 2 cat 3 echo 4 pwd 5 cd 6 clear 7 man 8 help 9 history 10 which +3 more (to see all, run "guard-sh whitelist") #+end_example ** Debug mode #+begin_src bash guard-sh check "<command>" --debug #+end_src Prints a step-by-step trace to stderr: whitelist result, per-provider cache check, redaction status, response time, and the final LLM response. #+begin_example command "rm -rf /" whitelist ○ miss providers openai pattern ○ off unchanged entropy ○ off unchanged ✓ ok (1315ms) result "This command will recursively delete everything from root." #+end_example With redaction enabled, the redacted form is shown before the query is sent: #+begin_example providers openai pattern ● on → "rm -rf [REDACTED]" entropy ○ off unchanged ✓ ok (891ms) #+end_example On a cache hit, the provider is not queried at all: #+begin_example providers openai cache ● hit → "This command will recursively delete everything from root." #+end_example If a provider fails, guard-sh moves on to the next and shows why: #+begin_example providers openai pattern ○ off unchanged entropy ○ off unchanged ✗ HTTP 429: Rate limit exceeded (234ms), trying next gemini pattern ○ off unchanged entropy ○ off unchanged ✓ ok (401ms) #+end_example ** Setup #+begin_src bash guard-sh setup #+end_src Creates =~/.config/guard-sh/= with the default config and prompt, writes shell integration scripts (bash, zsh, fish), and adds them to your rc file based on your current shell. Existing files are not overwritten. This is the only step needed after downloading a pre-built binary. ** Version #+begin_src bash guard-sh version #+end_src Prints the current version (e.g. ~1.0.0~). * Providers :PROPERTIES: :CUSTOM_ID: providers :END: #+begin_src bash guard-sh provider add # interactively add a provider guard-sh provider remove # interactively remove a provider guard-sh provider order # interactively reorder providers #+end_src ~provider add~ walks you through selecting a provider, picking a model, and entering an API key (or a full chat endpoint URL for Ollama, e.g. ~http://localhost:11434/api/chat~). The provider is saved to your config and added to ~provider_order~ automatically. ~provider remove~ lists your configured providers and removes the one you select. ~provider order~ opens an interactive list. Use arrow keys to navigate, =Enter= to grab a provider, arrow keys to move it, =Enter= again to place it. =s= saves and exits, =q= exits without saving. * Whitelist :PROPERTIES: :CUSTOM_ID: whitelist :END: Commands in the whitelist are always safe. The LLM is never called. #+begin_src yaml command_whitelist: - ls - git #+end_src - ~ls~, ~ls -la~, ~ls /tmp~ → all pass instantly - ~ls && rm -rf /~ → ~rm~ is not whitelisted, so the full command goes to the LLM - For chained commands, *all* parts must be whitelisted to skip the LLM You can manage the whitelist from the command line: #+begin_src bash guard-sh whitelist # list all whitelisted commands guard-sh whitelist add git # add a command guard-sh whitelist remove git # remove a command #+end_src * Cache :PROPERTIES: :CUSTOM_ID: cache :END: guard-sh caches LLM responses locally so the same command is never sent to the API twice. The cache is stored at =~/.config/guard-sh/cache.json=. When the cache is full, the least recently used entries are evicted first. #+begin_src yaml cache_enabled: true # set to false to disable caching cache_max_size: 1000 # max number of cached responses #+end_src You can also toggle caching from the command line: #+begin_src bash guard-sh cache on # enable caching guard-sh cache off # disable caching guard-sh cache size 500 # set max cache size guard-sh cache clear # delete all cached responses #+end_src * Shell support :PROPERTIES: :CUSTOM_ID: shell-support :END: | Shell | Mechanism | |-------+------------------------------------| | bash | ~shopt -s extdebug~ + ~DEBUG~ trap | | zsh | ZLE widget bound to Enter | | fish | ~bind \n~/~\r~ mapped to custom function | * License :PROPERTIES: :CUSTOM_ID: license :END: GNU General Public License v3.0 — see [[file:LICENSE][LICENSE]].