Keeping secrets out of my public dotfiles draft

My dotfiles are public on GitHub, so anything I commit is out in the open. The API token lives in the macOS Keychain instead, and the repo holds nothing but a comment saying where to find it.

I store it once. The token never goes on the command line — the shell reads it into a variable first, so ~/.zsh_history only ever sees $token:

printf 'Atlassian API token: '
read -rs token
echo

security add-generic-password -U -s atlassian-token -a "$USER" -T /usr/bin/security \
  -w "$(printf %s "me@example.com:$token" | base64 | tr -d '\n')"
unset token

-U lets me re-run this to rotate the token instead of failing with “item already exists”. -T /usr/bin/security names the security binary as allowed to read the item back, which is what stops macOS raising an “allow access?” dialog on every later lookup. The stored value is base64 of email:token, because the thing consuming it wants HTTP Basic auth.

Nothing is exported at startup

My first version had .zshrc export it, so every shell carried the token in an env var. That’s the wrong shape twice over: it hands the credential to every process I launch whether or not it has any business with it, and it makes shell startup depend on a lookup succeeding.

Anything that needs the token can read the Keychain itself. And when I want it in my own shell — for a quick curl — a function fetches it on demand:

atlassian-env() {
  local creds decoded
  if ! creds="$(security find-generic-password -w -s atlassian-token 2>/dev/null)"; then
    echo "❌ atlassian-token not found in login keychain" >&2
    return 1
  fi

  decoded="$(printf %s "$creds" | base64 -d)"
  export ATLASSIAN_USER_EMAIL="${decoded%%:*}"
  export ATLASSIAN_API_TOKEN="${decoded#*:}"

  echo "✅ Atlassian credentials exported for this shell ($ATLASSIAN_USER_EMAIL)"
}

Two details in there earn their place. The if ! tests the assignment itself, because export VAR="$(...)" hands back export’s own exit status rather than the command’s — a failed lookup would sail straight through and leave an empty credential behind, which shows up an hour later as an unexplained 401 instead of a clear error. And ${decoded%%:*} / ${decoded#*:} split on the first colon using shell parameter expansion, no cut subprocess needed.

So the dotfiles end up holding no secret and no startup lookup — just this function, and a comment recording where the token lives and how to rotate it.

Why not 1Password

I did try it. op read 'op://Personal/atlassian-token/credential' is a clean one-line swap for the security call. What killed it wasn’t the extra latency, it was the prompting: with the lookup running per shell, 1Password kept re-asking to authorise CLI access, so every new terminal was another chance for a dialog. The Keychain is already unlocked at login and never asks. 1Password keeps a copy for the day I need this on a second machine.

Did you enjoy this post?