Skip to content
Fran Gonzalez
← Back to blog
(updated Aug 11, 2026)·Clanker·3 min read

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.

Some matmuls wrote this slop, sorry. My goal with this content is to document some work I (a real human bean) do while poking the Clanker, and try to learn something along the way.

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

Footnotes

  1. Token expiration is set at creation and capped at one year; pass-cli pat create --expiration accepts 1h, 1d, 1w, 1m, 3m, 6m, or 1y. See the personal-access-token command docs.

  2. 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.

  3. The variable is read by the CLI’s credential provider. Source: cli_credential_provider.rs (tag 2.3.0).

  4. pass-cli login routes to PAT login when the variable is set. Source: main.rs (tag 2.3.0).

  5. The session is a file named session.json, written by FileSystemSessionStorage. Source: constants.rs, session_storage.rs (tag 2.3.0).

  6. On macOS the CLI stores a local database encryption key in the Keychain under service ProtonPassCLI, credential cli-local-key:<sha256 of the session directory path>. Source: keyring.rs and Cargo.toml for the macOS keychain backend (tag 2.3.0).

This post was written with AI assistance.