Two Proton Pass CLI PAT Gotchas: --pat Argv Exposure and Rotation Limits
pass-cli login --pat exposes the token in argv and shell history, PAT-authenticated sessions cannot manage PATs, and a rotated token that never reaches the Keychain silently breaks auto-renewal.
I audited whether the Keychain-backed PAT renewal I set up in August still worked after weeks of re-login prompts. The shell plumbing was intact, but its Keychain credential was stale: an earlier pat renew had invalidated the stored token without a matching Keychain update. The helper kept reading a dead credential while I pasted the live one by hand.
Note
Tested with Proton Pass CLI 2.4.1 on macOS on 2026-09-20.
I had typed pass-cli login --pat <token> manually 19 times, and zsh had kept every token in plaintext.
Usage
--pat places the token in the command’s argv, visible to ps while the login runs, and in shell history permanently:
pass-cli login --pat 'pst_…::…'
The credential-provider variable avoids argv and durable history exposure. This helper briefly holds the token in a non-exported local shell variable, copies it into the login process’s environment, then unsets it:
proton_pass_login() {
local pat status
pat="$(security find-generic-password -a "$USER" -s "dev-mamini-pat" -w)" || return
PROTON_PASS_PERSONAL_ACCESS_TOKEN="$pat" pass-cli login
status=$?
unset pat
return "$status"
}
unset limits the variable’s lifetime but does not guarantee that the shell erases its process memory. Same-user processes can also inspect the login child’s environment while it runs.
pass-cli login provides no interactive prompt for tokens: --pat requires the value as a command-line argument (--pat with no value is an argument-parsing error), and login --interactive covers username and password, not tokens. The credential-provider variable is the supported prompt-free channel.
To stop hitting the two-hour server-side session expiry outside my wrapper functions, I wrapped the CLI itself (trimmed; the real one guards on command -v and warns when it sees --pat). login, logout, and pat pass through so an expired session never blocks the password login that rotation needs:
# ~/.config/shell/common.d/40-proton-pass.sh
pass-cli() {
case "${1:-}" in
login | logout | pat) ;;
*) proton_pass_login_ensure ;;
esac
command pass-cli "$@"
}
The second gotcha: PAT sessions cannot manage PATs.
$ pass-cli pat list
Error: Failed to list personal access tokens
Caused by:
Cannot manage or act on personal access tokens while logged in with a
personal access token or agent session
pat renew is the shortest rotation path: it prints a new token string, keeps the name and every granted vault access, and the old token stops working immediately. Rotating after a leak means pass-cli logout, one interactive password login (PAT sessions cannot manage PATs), pass-cli pat renew --pat-name "dev-mamini-pat" --expiration 6m, then security add-generic-password -U … -w to refresh the Keychain item. That last step is the one that matters: a renewed token that never reaches the Keychain leaves the helper reading a dead credential, which is exactly how I ended up pasting tokens into argv.
Run interactively, security prompts for the value on the terminal, hidden, with a retype (it only accepts a piped value when no controlling TTY exists). So I wrapped the update in a small proton_pass_pat_store helper: paste at security’s own prompts, then it checks that the stored value looks like pst_…::…, stops the SSH agent when necessary, ends the password session, authenticates with the stored PAT, and only then reports success. The pat delete + pat create path also works but re-grants access from scratch.
Notes
-
Purging history requires replacing the file, not printing filtered lines. I closed every other shell, then used a same-directory temporary file for an atomic replacement with owner-only permissions:
tmp="$(mktemp "$HOME/.zsh_history.clean.XXXXXX")" grep -av -- "pass-cli login --pat" "$HOME/.zsh_history" >"$tmp" && chmod 600 "$tmp" && mv "$tmp" "$HOME/.zsh_history" unset tmp exec zsh -
By cleanup time the server rejected both leaked values (“invalid, expired or has been deleted”), so the purge was hygiene rather than an emergency. A backup still contains the values, even when they are dead, and should be deleted after verification.
-
Environment variables stay readable by same-user processes while the login runs (
ps -Eon macOS). Argv and history are the durable leaks. -
Wrapper internals must call
command pass-cli, or the wrapper recurses into itself.
References
- Renewing Proton Pass CLI PAT Sessions with macOS Keychain: the setup this audit re-checked
- pass-cli login reference: documents
--pat <PAT>(pst_<token>::<key>) - pass-cli personal access tokens: PAT lifetime and the two-hour session
- ps(1) macOS manual page: process argv visibility
This post was written with AI assistance.