Renewing Proton Pass CLI PAT Sessions with macOS Keychain
Proton Pass PAT sessions expire after two hours, but a scoped PAT in the macOS login Keychain can renew them without exposing it in shell history.
A Proton Pass personal access token and the CLI session it creates have separate lifetimes. The Proton Pass CLI documentation lets a token remain valid for up to one year1, but every PAT-authenticated session expires after two hours2. The CLI reads the token from PROTON_PASS_PERSONAL_ACCESS_TOKEN3 and routes pass-cli login to PAT login when that variable is set4.
Note
Fact-checked against the Proton Pass CLI docs and source at tag 2.3.0 on 2026-08-11.
I kept the restricted-vault PAT in the macOS login Keychain and renewed the session only when pass-cli info failed.
Usage
I stored the PAT as a generic Keychain password. Placing -w last makes security prompt for it instead of exposing it in shell history or process arguments:
security add-generic-password -U -a "$USER" -s "dev-mamini-pat" \
-l "dev-mamini-pat" -j "Limited Proton Pass shell vault PAT" -w
This Bash/Zsh-compatible helper reuses a valid session and retrieves the PAT only when login is required:
# ~/.config/shell/common.d/40-proton-pass.sh
proton_pass_login_ensure() {
command -v pass-cli >/dev/null 2>&1 || return 127
pass-cli info >/dev/null 2>&1 && return 0
local pat status
pat="$(security find-generic-password \
-a "$USER" -s "dev-mamini-pat" -w)" || return 1
if PROTON_PASS_PERSONAL_ACCESS_TOKEN="$pat" pass-cli login >/dev/null; then
status=0
else
status=$?
fi
unset pat
return "$status"
}
I call it immediately before commands that need Proton Pass, rather than during every shell startup.
Notes
The Keychain item holds a bearer credential. The login Keychain is readable by any process running as my user once it is unlocked, so I restricted the PAT to one vault with the minimum permissions that workflow needs. Storing it there removes repeated copy-and-paste, but it does not extend the server-enforced two-hour session. The CLI uses the macOS Keychain too, but for a different item: it writes its session to a file (session.json)5 and keeps a local encryption key in the Keychain under the ProtonPassCLI service (cli-local-key:…)6, separate from the PAT I add.
I used a generic item in the login Keychain, not the Passwords app. Passwords is designed around AutoFill credentials, passkeys, verification codes, and iCloud synchronization. The security find-generic-password workflow targets a script-addressable Keychain item.
References
- Proton Pass CLI personal access tokens. PAT expiration options and the two-hour session lifetime.
- Proton Pass CLI configuration. Session directory and the macOS Keychain key provider.
- Proton Pass CLI
infocommand. Reports the current session and fails when it has expired. - Proton Pass CLI source (tag 2.3.0). Rust implementation of the PAT credential provider, file-based session storage, and the macOS keychain key provider.
- Apple Passwords app. Intended Passwords app use cases.
- Apple Security source. Implementation of the macOS
securitytooling.
Footnotes
-
Token expiration is set at creation and capped at one year;
pass-cli pat create --expirationaccepts1h,1d,1w,1m,3m,6m, or1y. See the personal-access-token command docs. ↩ -
PAT sessions are documented as having a lifetime of 2 hours and no session lock. This is enforced server-side. See the personal-access-token docs and configuration. ↩
-
The variable is read by the CLI’s credential provider. Source:
cli_credential_provider.rs(tag 2.3.0). ↩ -
pass-cli loginroutes to PAT login when the variable is set. Source:main.rs(tag 2.3.0). ↩ -
The session is a file named
session.json, written byFileSystemSessionStorage. Source:constants.rs,session_storage.rs(tag 2.3.0). ↩ -
On macOS the CLI stores a local database encryption key in the Keychain under service
ProtonPassCLI, credentialcli-local-key:<sha256 of the session directory path>. Source:keyring.rsandCargo.tomlfor the macOS keychain backend (tag 2.3.0). ↩
This post was written with AI assistance.