Skip to content
mk24x7Public

About

Find the secrets you typed into your terminal, and redact them before they leak. Offline macOS app and CLI.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Histclean logo

Histclean

Find the secrets you typed into your terminal, and redact them before they leak.

Latest release CI Platform No network access License


Every export OPENAI_API_KEY=..., mysql -pSecret, curl -H "Authorization: Bearer ..." and git clone https://user:token@... you ever typed is still sitting in a plain-text history file in your home folder, readable by every process you run and copied into every backup and dotfile sync. Histclean finds those secrets across shell and REPL history files, shows each one masked, and on request replaces exactly the secret bytes with <redacted:detector-id> (or removes the whole entry), leaving every other byte of the file untouched.

It is a native macOS app and a zero-dependency command-line tool built from one Swift package. Histclean is completely offline: it has no networking code, never uploads, checks or validates a secret against any service, and collects no telemetry. Scanning is read-only; nothing is written until you confirm a redaction.

Histclean results screen

Install

Histclean is open source and signed with an ad-hoc signature, not an Apple Developer ID. Pick the install path that suits you.

1. Homebrew, built from source (no Gatekeeper prompt)

brew install mk24x7/tap/histclean
histclean                                   # the CLI is on your PATH
ln -sfn "$(brew --prefix histclean)/Histclean.app" /Applications/Histclean.app

The CLI and the app are compiled on your Mac, so they carry no quarantine flag and open without any security dialog. Needs Xcode 15 or later.

2. Direct download

Download Histclean-<version>-macos-universal.dmg or .zip (the app) or histclean-<version>-macos-universal.tar.gz (the CLI) from the latest release, verify it against SHA256SUMS.txt, and copy Histclean.app to /Applications or histclean to a folder on your PATH. Then either:

  • open it once, click Done in the "Apple could not verify" dialog, go to System Settings > Privacy & Security, scroll to Security and click Open Anyway (macOS 15 and 26 no longer offer the Control-click shortcut), or
  • clear the quarantine flag from Terminal:
xattr -d com.apple.quarantine /Applications/Histclean.app
xattr -d com.apple.quarantine /usr/local/bin/histclean

3. Homebrew cask (prebuilt app, quarantined)

brew install --cask mk24x7/tap/histclean-app

Homebrew no longer strips quarantine, so this path shows the same first-launch dialog as the direct download. Use it if you manage your Mac with brew bundle.

4. Build from source

git clone https://github.com/mk24x7/histclean.git && cd histclean
./build.sh          # dist/Histclean.app and dist/histclean, ad-hoc signed
./package.sh        # optional: zip, dmg, CLI tarball and checksums

Quick start

histclean                      # scan every known history file, read-only
histclean --redact             # asks once per file, then redacts
histclean --redact --backup    # keep a 0600 copy in ~/.histclean/backups first
cat build.log | histclean --stdin --min-severity high   # CI: exit 3 on findings

What it scans

Source Path Format
zsh ~/.zsh_history plain and extended (: <start>:<elapsed>;<command>), continuations, meta-escaped bytes
bash ~/.bash_history one command per line, optional #<epoch> lines
fish ~/.local/share/fish/fish_history (honours XDG_DATA_HOME) - cmd: records with when: and paths:
Python REPL ~/.python_history lines
Node.js REPL ~/.node_repl_history lines
MySQL client ~/.mysql_history lines
psql ~/.psql_history lines
Ruby irb ~/.irb_history lines
SQLite shell ~/.sqlite_history lines
redis-cli ~/.rediscli_history lines
less ~/.lesshst lines
$HISTFILE wherever it points, when set detected from name and content

Any other file can be passed on the command line or added in the app, and --stdin scans piped text (build logs, CI output).

Detectors

Specific detectors run first; when two detectors match the same bytes the earlier one wins, so an export GITHUB_TOKEN=ghp_... is reported once, as a GitHub token.

Detector What it finds Severity
private-key -----BEGIN ... PRIVATE KEY----- blocks high
aws-access-key AWS access key IDs (AKIA..., ASIA...) high
aws-secret-key 40-character values of aws_secret_access_key and similar high
github-token ghp_, gho_, ghu_, ghs_, ghr_ tokens high
github-pat Fine-grained github_pat_ tokens high
gitlab-token glpat- tokens high
slack-token xoxa-, xoxb-, xoxp-, xoxr- tokens high
stripe-live-key sk_live_ and rk_live_ keys high
stripe-test-key sk_test_ keys low
anthropic-key sk-ant- keys high
openai-key sk-, sk-proj-, sk-svcacct- keys (must mix case and digits) high
google-api-key AIza keys high
npm-token npm_ tokens high
pypi-token pypi- tokens high
huggingface-token hf_ tokens high
vercel-token VERCEL_TOKEN=... and vercel --token ... high
cloudflare-token CLOUDFLARE_API_TOKEN, CF_API_KEY, X-Auth-Key high
digitalocean-token dop_v1_, doo_v1_, dor_v1_ tokens high
telegram-bot-token <bot id>:AA... Bot API tokens high
sendgrid-key SG.<id>.<secret> keys high
twilio-key SK plus 32 hex characters high
jwt JSON Web Tokens (eyJ... header, payload, signature) medium
auth-bearer Authorization: Bearer ... high
auth-basic Authorization: Basic ... (must decode to user:password), curl -u user:password high
url-credentials scheme://user:password@host high
mysql-password mysql -p<password>, MYSQL_PWD= high
postgres-password PGPASSWORD= high
password-flag --password=<value>, --password <value> high
export-secret export NAME=value (and fish set -x) where NAME contains SECRET, TOKEN, KEY or PASSWORD medium
generic-assignment password, secret, token, api_key, access_key, auth followed by = or : and 8+ characters medium
high-entropy 24+ character token with Shannon entropy above 4.0 bits per character low

False-positive guards

The generic, export and entropy detectors skip values that are clearly not secrets:

  • git object ids (40 or 64 hex) in git commands, and content digests such as sha256:<hex> or sha512-<base64>
  • UUIDs
  • base64 that decodes to ordinary printable text (for example file content in echo)
  • file paths (/..., ~/..., ./..., @file, names with a file extension) and URLs that carry no credentials
  • shell references: $VAR, ${VAR}, $(command) and backticks
  • placeholders: <token>, {{ token }}, xxxx, ****, your_api_key, and values Histclean already redacted

Provider detectors with a fixed prefix and length (GitHub, AWS, Stripe and so on) do not apply the placeholder rule: ghp_ followed by 36 characters is reported even if the characters are all x, because the prefix and length alone are a strong signal. Hex strings can never exceed 4.0 bits per character, so the entropy detector never reports hashes.

Missing a provider or seeing a false positive? Open a detector request with a made-up example. If you found a real secret Histclean misses, do not paste it anywhere: see SECURITY.md.

How redaction works

  1. Re-check. The file is read and scanned again. Every selected finding must still be at the same byte range with the same detector; otherwise nothing is written and you are asked to scan again.
  2. Rewrite in memory. Only the secret bytes change, each becoming <redacted:detector-id>. zsh timestamps, meta escapes and continuation lines, bash #<epoch> lines and fish when:/paths: fields stay exactly as they were. With --delete-lines (or "Delete selected lines") the whole entry is removed instead, including its timestamp line and continuation lines.
  3. Optional backup. --backup copies the original to ~/.histclean/backups/<name>.<timestamp> with mode 0600 in a 0700 directory. The backup still contains the secrets; delete it once you have checked the result.
  4. Atomic replace. The new content is written to a temporary file in the same directory, flushed to disk, given the original permissions (0600 stays 0600) and owner, and moved over the original with rename(2). If another process changed the file in the meantime, the attempt is repeated once and then abandoned. Symbolic links are followed, so a history file linked from a dotfiles repository stays linked.
  5. Verify and log. The file is read back and compared byte for byte with the intended content. Each redaction is appended to ~/Library/Logs/Histclean/redactions.jsonl with the file, line, detector, action and time. The log never contains the secret, a preview or the command.

Limits you should know about

  • Running shells keep history in memory. An open zsh, bash or fish session writes its in-memory history back when it exits, which can restore a secret you just redacted. Close other terminal sessions first, or run this in each one after redacting:
    • zsh: fc -R; fc -W
    • bash: history -c; history -r
    • fish: history --merge
  • Copies elsewhere are untouched. Time Machine snapshots and backups, iCloud Drive or other synced dotfiles, dotfiles repositories, terminal scrollback and logs, and Histclean's own --backup copies all still hold the old content.
  • Rotate what matters. Redaction reduces exposure from now on; it cannot tell you whether the secret was already copied. Treat anything with high severity as compromised and rotate it.
  • Detection is pattern based. A password typed as a bare word with no recognisable context cannot be told apart from any other argument.

CLI reference

histclean [file ...] [options]

Input:
  file ...                 Scan these files instead of the known sources
  --stdin                  Scan text read from standard input
  --all-sources            Also scan every known source when files are given

Filtering and output:
  --min-severity <level>   low (default), medium or high
  --json                   One JSON document on stdout
  --no-color               Disable colors (NO_COLOR is also honoured)

Changing files:
  --redact                 Replace each secret with <redacted:detector-id>
  --delete-lines           Remove every entry that contains a secret
  --backup                 Copy each file to ~/.histclean/backups first (0600;
                           the copy still contains the secrets)
  -y, --yes                Do not ask for confirmation per file

  -V, --version            Show the version
  -h, --help               Show this help

--redact asks once per file and lists the counts per detector; --yes skips the question and is required when standard input is not a terminal. Standard input cannot be redacted in place. HISTCLEAN_LOG_DIR and HISTCLEAN_BACKUP_DIR move the log and the backups.

The JSON document has version, sources (path, format, entries, findings), findings (file, line, timestamp, detector, detectorName, severity, masked preview, byteStart, byteEnd), summary (total, byDetector, bySeverity), errors, redactions (with --redact or --delete-lines) and warning.

Exit codes

Code Meaning
0 No findings, or every selected finding was redacted
1 Runtime error, for example a named file does not exist or cannot be read
2 Usage error
3 Findings remain: scan mode found secrets, or a redaction was declined or failed

FAQ

Does Histclean send anything anywhere? No. There is no networking code in the app or the CLI, no update check, no analytics and no secret validation against provider APIs. You can verify with Little Snitch or by reading the source; the only files it writes are the history files you confirm, the optional backups and the redaction log.

Why is the preview masked? Previews show the first 4 and last 2 characters of a secret (only the first 2 for secrets under 12 characters, none under 8), so you can recognise it without the tool printing it in full into your terminal scrollback.

Will it break my history file? Redaction replaces only the secret bytes and is checked by reading the file back. The test suite round-trips zsh files with extended timestamps, continuation lines and meta-escaped multibyte characters byte for byte. If you want extra certainty, use --backup for the first run.

Why did my secret come back? A shell that was open during the redaction wrote its in-memory history back on exit. See the limits above.

Why the Gatekeeper dialog? Histclean is not notarized because it is a free, open-source side project. The Homebrew formula avoids the dialog entirely by compiling on your machine.

Does it need Full Disk Access? No. History files live directly in your home folder, which needs no extra permission.

Contributing

See CONTRIBUTING.md. Adding a detector is one entry in Sources/HistcleanCore/Detectors.swift, positive and negative lines in the test corpus, and a row in the table above. Security issues and detector gaps involving real secrets: SECURITY.md.

License

MIT

About

Find the secrets you typed into your terminal, and redact them before they leak. Offline macOS app and CLI.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages