{"id":49733072,"url":"https://github.com/vrypan/shg","last_synced_at":"2026-05-09T07:05:22.630Z","repository":{"id":356579888,"uuid":"1232323758","full_name":"vrypan/shg","owner":"vrypan","description":"Shell Guard","archived":false,"fork":false,"pushed_at":"2026-05-08T18:29:18.000Z","size":137,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-08T19:21:38.402Z","etag":null,"topics":["security-tools","shell","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vrypan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-07T20:19:21.000Z","updated_at":"2026-05-08T18:29:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vrypan/shg","commit_stats":null,"previous_names":["vrypan/shg"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/vrypan/shg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vrypan%2Fshg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vrypan%2Fshg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vrypan%2Fshg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vrypan%2Fshg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vrypan","download_url":"https://codeload.github.com/vrypan/shg/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vrypan%2Fshg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32810382,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["security-tools","shell","zig"],"created_at":"2026-05-09T07:05:16.431Z","updated_at":"2026-05-09T07:05:22.624Z","avatar_url":"https://github.com/vrypan.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# shg — Shell Guard\n\nScan shell history files for accidentally persisted secrets.\n\n![](screenshot.png)\n\n\u003e [!IMPORTANT]\n\u003e `shg` will not make you 100% safe.  \n\u003e But it will make you **safer**, and help you build some good shell habits.\n\n`shg` reads your shell history and flags entries that look like API keys,\npasswords, bearer tokens, credential URLs, and private keys. Secrets are\n**redacted in all output by default** — the full value is never printed.\n\n```\n$ shg scan\n\n[!!!] export OPENAI_API_KEY=s*************...**************5\n      ~/.zsh_history:148 [inline_assign]\n\n[!!!] curl -H \"Authorization: Bearer g*************...**************5...\n      ~/.zsh_history:576 [auth_header]\n\n2 finding(s) detected (2 high, 0 medium, 0 low).\nRemove flagged history entries and rotate affected credentials.\n```\n\n## Features\n\n- Detects secrets across five categories (see [Detection](#detection))\n- Redacts secrets in output — safe to share or log\n- Auto-discovers bash, zsh, fish, and common REPL histories\n- Offline and local — no network access, no telemetry\n- Single static binary, ~220 KB\n\n## Installation\n\n**Homebrew:**\n\n```sh\nbrew install vrypan/tap/shg\n```\n\n**Pre-built binaries:**\n\nDownload the latest release for your platform from the\n[Releases page](https://github.com/vrypan/shg/releases), extract, and place\nboth `shg` and `shg-config` somewhere on your `$PATH`.\n\n```sh\ntar xzf shg-v*.tar.gz\nsudo mv shg shg-config /usr/local/bin/\n```\n\n**Build from source** (requires Zig 0.16):\n\n```sh\ngit clone https://github.com/vrypan/shg\ncd shg\nzig build\n# binaries at zig-out/bin/shg and zig-out/bin/shg-config\n```\n\n## Usage\n\n```\nshg \u003ccommand\u003e [options]\n\nCommands:\n  scan      Scan history files for secrets (default)\n  version   Print version\n```\n\n### scan\n\n```\nshg scan [options]\n\nOptions:\n  -p, --path \u003cFILE\u003e            History file to scan [repeatable]\n      --env[=BOOL]             Scan environment variables [default: true]\n      --hist[=BOOL]            Scan history files [default: true]\n      --level \u003cLEVEL\u003e          low|medium|high [default: high]\n      --entropy-threshold \u003cN\u003e  Shannon entropy cutoff [default: 3.5]\n      --redacted[=BOOL]        Redact secrets in output [default: true]\n      --json[=BOOL]            Output findings as NDJSON [default: false]\n      --summary[=BOOL]         Print H M L counts and exit [default: false]\n      --one-line[=BOOL]        One line per finding [default: false]\n  -h, --help                   Print help\n```\n\nBy default, `shg scan` checks both environment variables and history files.\nWith no `--path` flags, `shg` scans existing paths from `paths.*.shg` plus the\nhistory file named by `HISTFILE`, when set. Use `--env false` or `--hist false`\nto disable a source.\n\nWhen history is piped via stdin, only stdin is scanned — env and history files\nare skipped unless explicitly requested with `--env=true` or `--hist=true`.\n\n**Exit codes:**\n\n| Code | Meaning |\n|------|---------|\n| 0 | No findings at or above `--level` |\n| 1 | One or more findings detected |\n| 2 | Error (bad arguments, unreadable file) |\n\nThis makes `shg` scriptable:\n\n```sh\nshg scan --level high \u0026\u0026 echo \"clean\"\n```\n\n\u003e [!TIP]\n\u003e Zsh may keep recent commands in memory before writing them to `$HISTFILE`.  \n\u003e Use a shell helper if you want scans to include the latest interactive history\n\u003e without forcing zsh to write the history file:  \n\u003e\n\u003e `shg-scan() { fc -l 1 | shg scan \"$@\" }`\n\u003e\n\u003e [INTEGRATIONS.md](INTEGRATIONS.md) also provides a snippet that will prevent\n\u003e sensitive info from being written to history in the first place.\n\nFor shell startup scans and pre-history hooks see [INTEGRATIONS.md](INTEGRATIONS.md).\n\n## Detection\n\n`shg` combines pattern matching, Shannon entropy analysis, and heuristic\nscoring. Each candidate is scored on several signals; low-scoring results\nare silently dropped to reduce false positives.\n\n| Detector | What it matches |\n|---|---|\n| `inline_assign` | `VAR=value` with sensitive keywords |\n| `auth_header` | `Authorization: Bearer \u003ctoken\u003e`, `--password \u003cval\u003e` |\n| `credential_url` | `scheme://user:pass@host` |\n| `config_check` | compiled `match.*.shg` pattern match |\n| `private_key` | `-----BEGIN * KEY-----` and `AGE-SECRET-KEY-1` markers |\n| `ssh_key` | `ssh-rsa`, `ssh-ed25519`, `ecdsa-sha2-*`, FIDO2/sk public keys |\n\nRun `shg-config status` to list active detection patterns and rules.\n\n### Default history paths\n\nThe default `paths.default.shg` created by `shg-config defaults` includes:\n\n| Shell / tool | Path |\n|---|---|\n| Zsh | `~/.zsh_history` |\n| Bash | `~/.bash_history` |\n| Fish | `~/.local/share/fish/fish_history` |\n| Fish | `~/.config/fish/fish_history` |\n| Python REPL | `~/.python_history` |\n| psql | `~/.psql_history` |\n| MySQL | `~/.mysql_history` |\n| SQLite | `~/.sqlite_history` |\n| Redis CLI | `~/.rediscli_history` |\n| Node.js REPL | `~/.node_repl_history` |\n| Ruby IRB | `~/.irb_history` |\n| Ruby Pry | `~/.pry_history` |\n| R | `~/.Rhistory` |\n\n### Redaction format\n\nThe number of plaintext characters shown depends on token length:\n\n| Token length | Visible chars | Example |\n|---|---|---|\n| ≤ 8 chars | 1 each side | `ghp_*bcde` |\n| 9–32 chars | 4 each side | `ghp_******ghij` |\n| \u003e 32 chars | 4 each side, capped at 32 with `...` in the middle | `ghp_**********...***********2345` |\n\nIf the detected secret appears before the end of the command, the rest of the\nline is replaced with `...` to avoid exposing any second secret that may follow:\n\n```\ncurl -H \"Authorization: Bearer ghp_**********...***********2345...\n```\n\nUse `--redacted=false` to disable redaction (not recommended for shared output).\n\nWhen writing to a terminal, `shg` colours the severity badge. Set `NO_COLOR`\nin the environment to disable terminal styling.\n\n### Severity badges\n\n| Badge | Severity |\n|---|---|\n| `[!!!]` | High |\n| `[!! ]` | Medium |\n| `[!  ]` | Low |\n\n## Scoring\n\nEach detection candidate is scored against a set of signals:\n\n| Signal | Score |\n|---|---|\n| Sensitive keyword in variable name | +3 |\n| High Shannon entropy (≥ 3.5 bits/char) | +3 |\n| Token length ≥ 20 chars | +2 |\n| Authorization header | +2 |\n| Credential URL | +2 |\n| Known provider token format | +4 |\n| Private key marker | +6 |\n| Placeholder / test value | −3 |\n| Search command (grep, sed, …) | −2 |\n\n| Score | Severity |\n|---|---|\n| 0–2 | Ignored |\n| 3–4 | Low |\n| 5–6 | Medium |\n| 7+ | High |\n\nThe entropy threshold is configurable with `--entropy-threshold`.\n\n## Configuration\n\nConfiguration files live in `shg`'s config directory:\n\n- `$XDG_CONFIG_HOME/shg` when `XDG_CONFIG_HOME` is set\n- `$HOME/.config/shg` otherwise\n\nThe config directory contains editable `.shg` files:\n\n- `ignore.*.shg` — patterns that suppress findings\n- `match.*.shg` — additional patterns to flag\n- `paths.*.shg` — history paths to scan when `--path` is not used\n\nDefault config templates are maintained as plain text files in `src/defaults/`\nand embedded into `shg-config` at build time.\n\nPut local changes in separate files such as `ignore.my.shg`, `match.work.shg`,\nor `paths.local.shg`. Avoid editing `*.default.shg` directly; future default\nupdates may overwrite those files.\n\nRun `shg-config compile` to write `rules.bin`, the binary cache loaded by\n`shg scan`. Rules are line-based; blank lines and `#` comments are ignored.\nUse `exact:`, `prefix:`, or `substr:` prefixes to choose the match type. Lines\nwithout a prefix are substring matches. `paths.*.shg` files are one path per\nline, and a leading `~/` expands to the user's home directory.\n\nIgnore rules take precedence over match rules. If the same text appears in both\n`match.default.shg` and `ignore.my.shg`, the matching command is suppressed and\ndoes not produce a finding.\n\n\u003e [!NOTE]\n\u003e `match.default.shg` is based on https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml\n\n### shg-config commands\n\n```\nshg-config compile\n```\nCompile all `*.shg` files in the config directory into `rules.bin`. Must be\nre-run after any config change.\n\n```\nshg-config defaults [-y]\n```\nWrite the three default `.shg` files (`ignore.default.shg`, `match.default.shg`,\n`paths.default.shg`). Existing files prompt before overwrite; use `-y` to\noverwrite without prompting.\n\n```\nshg-config discover\n```\nScan your home directory for `.*history` files not yet in your configuration\nand offer to append them to `paths.local.shg`. Run `shg-config compile`\nafterwards to apply the changes.\n\n## Security\n\n- **No network access.** `shg` never connects to the internet.\n- **No telemetry.** Nothing is collected or sent.\n- **Redaction on by default.** Secrets are never printed in full unless\n  `--redacted=false` is explicitly passed.\n- **Read-only.** The current release only scans; it does not modify history\n  files. A `fix` subcommand (with atomic writes and automatic backups) is\n  planned for a future release.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvrypan%2Fshg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvrypan%2Fshg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvrypan%2Fshg/lists"}