One of my most popular and successful posts has been where I discussed managing secrets locally on Linux using the GNOME Keyring and secret-tool; if you’re interested then you can find it here. I then followed it up with a look at managing secrets on Windows using the Windows Credential Manager; you can find that post here. Well, now it’s time for me to complete the trilogy with a look at managing secrets on macOS.
For context, my most recent work engagement has seen me back on macOS. Whilst at the time of writing (in late 2025) I’m working with Tahoe what I will look at here has been available since at least 10.13, High Sierra, and so should be compatible with whatever Mac you are likely using.
Finally, before we get into it all of the why this might be important and ways I’d recommend not addressing local secrets are covered in detail in Managing Secrets In Linux and they are equally applicable on macOS. That said, the point of this article is to address storing secrets such as personal access tokens (PATs) and API keys (because let’s be honest it’s 2025 and who isn’t rocking OpenAI API keys?) in a way that’s secure, encrypted, and accessible. There’s no need to be storing these kinds of sensitive values in text files (hidden or not) these days.
Introducing security
Now, other than being blindingly obvious that we’re talking about security here, in the context of macOS security is a command line tool for administering the keychain, which is the system used to store passwords, keys, certificates, and other sensitive information securely. It does way more than the few introductory commands I’ll be covering here so if you are looking for more detail I’ve found this reference on SS64 helpful.
As before I’m focusing primarily on command line usage that might be useful for disciplines like Developers and DevOps Engineers, System Administrators (SysAdmins), etc. For this we’re going to work with “generic passwords” which are passwords stored without being associated with a specific service. There are also “internet passwords” accounted for in security which are intended to be associated with a given site or service. There are 3 commands we need…
Storing a password
This might be a shock to some but the command we need to store a password (or equivalent token, key, etc.) is security add-generic-password. Here’s a basic example:
security add-generic-password -a "username" -s "service" -w "password"
-
-aspecifies the account name. -
-sspecifies the service name. -
-wspecifies the password.
This is the equivalent of secret-tool store on Linux.
We might also want to think about using the -U switch to update an existing item if it exists. It’s safe to use if the value doesn’t already exist and will update it if it does.
So, we could store a command for an account called graham on a service called gitlab like this:
security add-generic-password -a "graham" -s "gitlab" -w "your_password_here" -U
Retrieving a password
If add-generic-password adds a password, any guesses what finding one might be? You guessed it, security find-generic-password. Here’s a basic example:
security find-generic-password -a "username" -s "service" -w
-
-aspecifies the account name. -
-sspecifies the service name. -
-wtells the command to output the password.
So, to retrieve the password for the account called graham on the service called gitlab, you would use:
security find-generic-password -a "graham" -s "gitlab" -w
Where it would return the password for that account and service, e.g.
your_password_here
Removing a password
In a fit of consistency, the command to remove a password is security delete-generic-password. Here’s a basic example:
security delete-generic-password -a "username" -s "service"
-
-aspecifies the account name. -
-sspecifies the service name.
So, to remove the password for the account called graham on the service called gitlab, you would use:
security delete-generic-password -a "graham" -s "gitlab"
Using a secret
OK, great, so we now have a way to store and retrieve secrets on our local machine. But how do we use these secrets in a script?
Well the find-generic-password command from above is our friend here and we can either use it directly in a script or we can use it to set an environment variable in our shell profile. Directly in a script would minimise the exposure of the secret, but it may be practical to be shared between scripts and/or users so setting an environment variable might be something to consider.
In a script it might look like:
#!/bin/bash
local GITLAB_TOKEN=$(security find-generic-password -a "graham" -s "gitlab" -w)
In your shell profile it might look like:
# ~/.bashrc
export GITLAB_TOKEN=$(security find-generic-password -a "graham" -s "gitlab" -w)
Not leaving your password in command history
These commands alone are great, and we’ve looked at how you might use them in your scripts and shell, but I’m sure the eagle-eyed and security conscious amongst you have noticed that just typing security add-generic-password -a "graham" -s "gitlab" -w "your_password_here" on the command line is likely to leave the value in your command history unless you take steps to remove it.
Sadly, from what I’ve found at least, security does not support prompting for the password interactively like secret-tool does on Linux. So what can we do? Well… I use read to capture the password interactively in a script without echoing it to the terminal, like this:
#!/bin/bash
read -s "?Enter your password: " PASSWORD
security add-generic-password -a "graham" -s "gitlab" -w "$PASSWORD" -U
If you’re on older macOS versions predating the move to zsh for the shell and you’re still using bash then you probably want read -sp instead, like this:
#!/bin/bash
read -sp "Enter your password: " PASSWORD
security add-generic-password -a "graham" -s "gitlab" -w "$PASSWORD" -U
I actually added a few helper commands to my shell rc file to do this for me, like this:
add_secret() {
local account="$1"
local service="$2"
read -s "?Enter your password: " secret
security add-generic-password -a "$account" -s "$service" -w "$secret" -U
}
get_secret() {
local account="$1"
local service="$2"
security find-generic-password -a "$account" -s "$service" -w
}
delete_secret() {
local account="$1"
local service="$2"
security delete-generic-password -a "$account" -s "$service"
}
I also add a quick case switch to help with my Linux muscle memory by adding a function called secret-tool which accepts the standard store, lookup and clear subcommands from the Linux tool. I also took the liberty to add a couple of others in case I forget the exact subcommands, so I get something like this:
secret-tool() {
case "$1" in
store|add)
add_secret "$2" "$3"
;;
lookup|get)
get_secret "$2" "$3"
;;
clear|delete|remove)
delete_secret "$2" "$3"
;;
*)
echo "Usage: secret-tool {store|lookup|clear} account service"
return 1
;;
esac
}
With that, I think we’re done. We have all the same tools for secure storing and managing secrets on the command line in macOS as we do on Linux.
If you’re like me and work across multiple platforms, having these consistent tools can make managing your secrets much easier. To help, please do check out my posts on Managing Secrets in Linux and Managing Secrets in Windows.
If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via LinkedIn or X / Twitter. If you have any ideas for further content you might like to see please let me know too.