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.