How to Pass Secrets to Scripts and AI Agents Securely
Published:2026-11-08
Keep passwords out of chat and command-line arguments. Supply them at run time through an environment variable or standard input, from a read -s prompt, a password manager CLI such as 1Password op run, a cloud secret manager, or the OS keychain. When a person has to type the value right then, nodd ask --secret --env takes it from a masked field on an iPhone. The value arrives encrypted to a key that exists only for that run, and with --env it never reaches an AI agent's model.
How secrets leak
Passwords and API tokens usually leak through what happens around their use, not the use itself. These are the common paths.
Pasting into chat or an AI agent
A value pasted into chat becomes part of the conversation and goes to the model provider. Some tools also keep the conversation on disk. Claude Code, for example, stores session history under ~/.claude. If the agent puts the value into a command, that command also ends up in logs and on screen.
Command-line arguments
An argument such as mysql -pPASSWORD can be visible to other users through ps while the command runs. On Linux, /proc/<pid>/cmdline is readable by other users by default, unless procfs is mounted with hidepid. The command also lands in your shell history. Bash's HISTCONTROL=ignorespace keeps commands that start with a space out of history, but it does not hide arguments while they run.
Committing a plain .env file
Forget to add .env to .gitignore and the values stay in the repository history. GitHub push protection blocks pushes that contain secrets. Push protection for users is on by default on GitHub.com and covers pushes to public repositories. Push protection for repositories is off by default, and an administrator has to turn it on.
Common safe options
Prompt with read -s
Bash's read -s does not echo input from a terminal. For a script you run once by hand, this is enough.
read -rsp "DB password: " PGPASSWORD
echo
export PGPASSWORD
psql -h db.example.com app
It needs a person at the terminal, so it does not work in CI or in commands an AI agent runs.
Password manager CLIs
With the 1Password CLI, you write op://vault/item/field references in a file and pass it to op run --env-file. The real values go only into the environment of the command it runs, and if that command prints a value, it is masked by default. The file holds only references, so it can be committed. Use op read for a single value.
# .env (commit-safe: it holds only references)
PGPASSWORD="op://Production/app-db/password"
op run --env-file=.env -- psql -h db.example.com app
Cloud secret managers
Services such as AWS Secrets Manager keep values out of source code and hand them out through an API at run time, with IAM access control, automatic rotation, and CloudTrail logging. This is the right fit for servers and CI.
The OS keychain
On macOS, security find-generic-password with -w prints only the password of a keychain item.
PGPASSWORD="$(security find-generic-password -s app-db -a deploy -w)" psql -h db.example.com app
With AI agents, also stop them from reading secret files. In Claude Code you can add rules such as Read(./.env) to permissions.deny in settings.
Choosing an approach
| Approach | Good fit when |
|---|---|
read -s | You run the script yourself at a terminal |
A password manager CLI (op run and others) | The value is already in a vault and you use it repeatedly, locally or in CI |
| A cloud secret manager | Servers or batch jobs that need access control and rotation |
| The OS keychain | A value only you use on one Mac |
Nodd --secret | A person has to type the value right then and is not at the terminal, or an AI agent runs the command and the model must not see the value |
If the value is in a vault and can be fetched automatically, a password manager CLI or a secret manager is less work. Nodd fits when the value lives in someone's head or somewhere else, and you want a person to decide each time.
One-time input from an iPhone
nodd ask --secret sends an approval request to an iPhone with a masked field for a password or other secret. The person types the value and taps Send with Face ID. Answering needs Nodd 1.1 or later.
Use --env with AI agents
nodd ask "Production DB password" --secret --env PGPASSWORD -- psql -h db.example.com app
With --env, the command after -- runs only on approval, and the value goes only into that command's PGPASSWORD environment variable. Nothing is printed to standard output. Have the agent write the command this way and the value never reaches the model. The exit code is the command's. On rejection or expiry, no value is returned and the command does not run.
Piping the value
nodd ask "API token" --secret | ./import-token.sh
Without --env, only the value goes to standard output. It refuses to print to a terminal, so pipe it or use --env.
What happens to the value
- The value arrives encrypted to a key that exists only for this run. Our servers cannot read it
- Only the sealed value is on the server. The CLI deletes it after opening it, and it is removed after 10 minutes either way
- The value is never in
response, events, or callbacks - It is not kept on the device either. The app's history shows only "the value you entered"
Responders should check the send key name and channel on the screen, and reject the request if they do not recognize them. See the Quickstart to set up and the CLI page for every option. For Claude Code, see this guide.
Using the API
To ask from your own program without the CLI, put a Base64 32-byte X25519 public key in secret.public_key on an approval. The device seals the value to this key. After approval, read {"sealed": "…"} with GET /v1/messages/{id}/secret, then delete it with DELETE.
curl https://api.nodd.blipdrop.net/v1/messages \
-H "Authorization: Bearer $NODD_KEY" \
-H "Content-Type: application/json" \
-d '{"kind":"approval","title":"Production DB password","secret":{"public_key":"<Base64 X25519 public key>"}}'
# After approval, fetch the sealed value, then delete it
curl https://api.nodd.blipdrop.net/v1/messages/msg_.../secret -H "Authorization: Bearer $NODD_KEY"
curl -X DELETE https://api.nodd.blipdrop.net/v1/messages/msg_.../secret -H "Authorization: Bearer $NODD_KEY"
It uses HPKE (DHKEM X25519 / HKDF-SHA256 / ChaCha20-Poly1305), with nod-secret-v1 as info and the message ID as additional data. response.secret_digest is the SHA-256 of the Base64 text and is covered by the signature. See the API reference.
Caveats and limits
- Answering needs Nodd 1.1 or later. Version 1.0 of the app can only reject a secret request
- MCP has no equivalent of
--secret. Have agents run it as a CLI command secretcannot be combined withinput,dedupe_key,confirm: false, or more than one approval. In the CLI it cannot be used with--option,--input,--approvals,--no-wait, or--json- The public key passes through the server, so this does not protect against a compromised server
- A value passed with
--envis still visible to the agent if the command prints it. Pass it only to commands that do not - Text from
--inputis not encrypted to devices. Never use it for secrets
FAQ
- Is it safe to give an AI agent my password in chat?
- A value pasted into chat goes to the model provider as part of the conversation and stays in its history. Pass the value to the command as an environment variable without showing it to the model.
- What is wrong with passwords in command-line arguments?
- While the command runs, other users may see it in the process list, and it stays in your shell history. Use an environment variable, standard input, or a prompt instead.
- Can Nodd's servers read a value entered with --secret?
- No. The value is encrypted to a key that exists only for that run. The public key passes through the server, though, so this does not protect against a compromised server.
- Can I ask for a secret through MCP?
- No. MCP has no equivalent of --secret. Have the agent run nodd ask --secret --env as a CLI command.
- Which version of the app do I need?
- Nodd 1.1 or later. Version 1.0 of the app can only reject the request.
Nodd delivers questions from scripts, CI, and AI agents to your iPhone, and returns your Face ID approval to the program that asked. There is a free plan.