├── .editorconfig ├── README.md └── zsh-mask.plugin.zsh /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | [*.zsh] 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zsh-mask 2 | 3 | 4 | 5 | 6 | 7 | - [Installation](#installation) 8 | - [Customization](#customization) 9 | 10 | 11 | 12 | Masks secrets in your zsh history 13 | 14 | Example: 15 | 16 | ```bash 17 | ❯ curl -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5c' https://example.com 18 | ❯ curl -u foo:bar https://example.com 19 | ❯ curl https://foo:bar@example.com 20 | ❯ AWS_SECRET_ACCESS_KEY=key aws s3 ls s3://example 21 | ❯ export DB_PASSWORD=foo 22 | ❯ history | tail 23 | 1 curl -H 'Authorization: ...' https://example.com > /dev/null 24 | 2 curl -u ... https://example.com > /dev/null 25 | 3 curl https://...@example.com > /dev/null 26 | 4 AWS_SECRET_ACCESS_KEY=... aws s3 ls s3://example 27 | 5 export DB_PASSWORD=... 28 | ``` 29 | 30 | ZSH will let you recover the previous command with secrets immediately after execution: 31 | 32 | ```bash 33 | > curl http://username:password@github.com/foo 34 | > # recovers command with password immediately after execution 35 | > # recovers masked command here 36 | ``` 37 | 38 | To exclude _any_ command from history, prefix with a space. It behaves the same as above: 39 | ```bash 40 | > echo "prefixed with space" 41 | > # available here on 42 | > # Not available here 43 | ``` 44 | 45 | Control what secrets are catched by setting the environment variable `HISTORY_EXCLUDE_PATTERN`. If the pattern includes a single group, that group is substituted with `...`. The default `HISTORY_EXCLUDE_PATTERN` is 46 | 47 | ```bash 48 | ❯ echo $HISTORY_EXCLUDE_PATTERN 49 | ^ |//([^/]+:[^/]+)@|KEY[=:] *([^ ]+)|TOKEN[=:] *([^ ]+)|BEARER[=:] *([^ ]+)|PASSWO?R?D?[=:] *([^ ]+)|Authorization[=:] *([^'\"]+)|-us?e?r? ([^:]+:[^:]+) 50 | ``` 51 | 52 | it is not case sensitive 53 | 54 | ## Installation 55 | 56 | Using **Plain zsh** 57 | 58 | Download [zsh-mask.zsh](zsh-mask.zsh), then add to `.zshrc` 59 | 60 | ``` 61 | source /path/to/zsh-mask.zsh 62 | ``` 63 | 64 | Using [**Antibody**](https://getantibody.github.io) 65 | 66 | ``` 67 | antibody bundle jgogstad/zsh-mask 68 | ``` 69 | 70 | Using [**ZInit**](https://github.com/zdharma/zinit) 71 | 72 | Add the following to `.zshrc` 73 | 74 | ```bash 75 | zinit light jgogstad/zsh-mask 76 | ``` 77 | 78 | Using [**ZPlug**](https://github.com/zplug/zplug) 79 | 80 | Add the following to `.zshrc` 81 | 82 | ```bash 83 | zplug 'jgogstad/zsh-mask' 84 | ``` 85 | 86 | Using [**Oh My Zsh**](https://ohmyz.sh/) 87 | 88 | `git clone https://github.com/jgogstad/zsh-mask ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-mask` 89 | 90 | Add the following to `.zshrc` 91 | 92 | ```bash 93 | plugins=(zsh-mask) 94 | ``` 95 | 96 | 97 | ## Customization 98 | 99 | Overwrite `HISTORY_EXCLUDE_PATTERN` to customize: 100 | 101 | ```bash 102 | ❯ export HISTORY_EXCLUDE_PATTERN="^ykchalresp|$HISTORY_EXCLUDE_PATTERN" 103 | ``` 104 | 105 | The pattern is a regex and it's evaluated with zsh's `=~` operator using case insensitive evaluation. 106 | -------------------------------------------------------------------------------- /zsh-mask.plugin.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/env zsh 2 | 3 | HISTORY_EXCLUDE_PATTERN='^ |//([^/]+:[^/]+)@|KEY[=:] *([^ ]+)|TOKEN[=:] *([^ ]+)|BEARER[=:] *([^ ]+)|PASSWO?R?D?[=:] *([^ ]+)|Authorization[=:] *([^'"'"'\"]+)|-us?e?r? ([^:]+:[^:]+) ' 4 | 5 | # See 6 | # - https://zsh.sourceforge.io/Doc/Release/Functions.html for docs on zshaddhistory 7 | # - https://zsh.sourceforge.io/Doc/Release/Shell-Builtin-Commands.html for docs on print 8 | function zshaddhistory() { 9 | emulate -L zsh 10 | unsetopt case_match 11 | 12 | input="${1%%$'\n'}" 13 | if ! [[ "$input" =~ "$HISTORY_EXCLUDE_PATTERN" ]]; then 14 | print -Sr -- "$input" 15 | else 16 | nonempty=($match) 17 | 18 | if [[ $#nonempty -gt 0 ]]; then 19 | for m in "$nonempty[@]"; do 20 | n="${m##[\"\']}" 21 | input="${input//${n%%[\"\']}/...}" 22 | done 23 | 24 | print -Sr -- "$input" 25 | fi 26 | unset match 27 | return 1 28 | fi 29 | } 30 | --------------------------------------------------------------------------------