Leaking a credential through a shell script is a common, silent failure. Proper bash environment variables api key safety means never writing secrets inline, loading them from restricted files, and keeping them out of process listings and logs. The following steps give you a repeatable pattern for production-grade bash automation.
Step 1: Remove hardcoded keys from your scripts
The fastest way to burn a key is to paste it into a .sh file. Once it lands in a repo, a backup, or a CI log, it is compromised.
# leak.sh — never do this
curl -H "Authorization: Bearer sk-1234567890abcdef" \
https://api.example.com/v1/models
Replace the literal with a variable reference and fail fast if it is missing:
#!/usr/bin/env bash
set -euo pipefail
API_KEY="${OPENAI_API_KEY:?Set OPENAI_API_KEY in the environment}"
curl -sS -H "Authorization: Bearer $API_KEY" \
https://api.example.com/v1/models
The ${VAR:?message} construct exits with a clear error instead of sending an empty bearer token. That single line removes the temptation to hardcode a fallback secret.
Step 2: Store secrets in a mode-600 env file
A dedicated env file keeps credentials out of source control and lets you restrict permissions. For bash environment variables api key safety, the file permissions are non-negotiable.
touch ./secrets.env
chmod 600 ./secrets.env
chown "$(id -un):$(id -gn)" ./secrets.env
Write only exports, no executable logic:
# secrets.env
export LLM_GATEWAY_KEY="sk-..."
export WEBHOOK_TOKEN="wht_..."
Add secrets.env to .gitignore immediately. If you need team sharing, use a secrets manager and write the file at deploy time, not in the repo.
Step 3: Source the file without leaking contents
Do not use cat secrets.env | xargs or env $(cat secrets.env) cmd. Those patterns briefly expose the key in the process table. Source it directly with auto-export:
set -a
source ./secrets.env
set +a
set -a marks every assigned variable for export; set +a turns that off so later shell variables are not leaked to child processes. Avoid eval on file contents—source is safer because it respects shell quoting.
If you run this in an interactive shell, the variables persist after the script ends. That is fine for a dedicated automation user, but for shared login shells, prefer a subshell:
( set -a; source ./secrets.env; set +a; exec ./run_pipeline.sh )
Step 4: Validate keys before use
A missing or malformed key should fail before you make a network call. Check presence and shape:
if [[ -z "${LLM_GATEWAY_KEY:-}" ]]; then
echo "LLM_GATEWAY_KEY is missing" >&2
exit 1
fi
if [[ ! "$LLM_GATEWAY_KEY" =~ ^sk- ]]; then
echo "LLM_GATEWAY_KEY has unexpected format" >&2
exit 1
fi
Use [[ -v VAR ]] (bash 4.3+) to test existence without expanding the value. Never print the value during validation—echo "$LLM_GATEWAY_KEY" writes it to stdout and possibly to a log aggregator.
Step 5: Pass keys to commands without exposing them
Command-line arguments are visible in ps aux and often captured by process monitors. Environment variables are not shown in default process listings, so pass the key through the environment or a header that reads from it.
curl -sS -X POST "https://api.example.com/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"ping"}]}'
If you must use set -x for debugging, disable tracing around the secret call:
set +x
curl -sS -H "Authorization: Bearer $LLM_GATEWAY_KEY" https://api.example.com/v1/models
set -x
For bash environment variables api key safety, never interpolate the key into a URL query string—proxy logs and server access logs will store it.
Step 6: Unset keys after use
In a short-lived script, exiting clears the environment. In an interactive session or a long-running daemon, explicitly drop the variables:
unset LLM_GATEWAY_KEY WEBHOOK_TOKEN
If the script sourced the file into the current shell (not a subshell), add a trap to clean up on any exit:
cleanup() { unset LLM_GATEWAY_KEY WEBHOOK_TOKEN; }
trap cleanup EXIT
Step 7: Call an LLM gateway with safe key handling
When you hit an OpenAI-compatible inference gateway, the same rules apply. Load the key from env, send it only in the Authorization header, and let the gateway handle routing.
#!/usr/bin/env bash
set -euo pipefail
set -a
source ./secrets.env
set +a
GATEWAY_KEY="${N4N_API_KEY:?Missing N4N_API_KEY}"
# n4n.ai exposes one OpenAI-compatible endpoint across 240+ models and applies
# automatic fallback when a provider is rate-limited. The key is forwarded only
# via the header below; the gateway honors client routing directives separately.
curl -sS "https://api.n4n.ai/v1/models" \
-H "Authorization: Bearer $GATEWAY_KEY"
This keeps the secret out of the URL, out of ps, and out of your script body. Per-token metering on the gateway side means you do not need to log the key to track usage.
Step 8: Verify your setup
Follow these checks to confirm the pattern holds:
- File permissions
[[ $(stat -c '%a' secrets.env) == "600" ]] || echo "BAD PERMS" - Variable presence without disclosure
[[ -v LLM_GATEWAY_KEY ]] && echo "Key loaded (value not shown)" - No key in shell history — never type the secret at a prompt. Source only.
- Process table scan — run the script and check
ps aux | grep -i bearer; you should see nothing. - Syntax check —
bash -n yourscript.shcatches errors before execution. - Dry-run with a fake key — set
LLM_GATEWAY_KEY=sk-testand confirm the script fails validation or returns a 401 without printing the token.
A final note on bash environment variables api key safety: rotate any key that has been echoed, committed, or passed as an argument. Detection is rarely immediate, but assuming compromise is the only safe default.
Common pitfalls
Exporting in .bashrc for all sessions
Putting export LLM_GATEWAY_KEY=... in a shared .bashrc leaks the key to every child process, including random make invocations and editor plugins. Scope it to the script or a dedicated user.
Using printenv in logs
printenv dumps all variables. If a log line captures stdout, the key goes with it. Filter explicitly:
env | grep -v 'KEY\|TOKEN' > /tmp/safe_env.txt
Trusting .env libraries that call eval
Some third-party dotenv helpers run eval on each line. A stray backtick in a secret breaks the script or executes arbitrary code. Stick to source with a strict file format.
Following these steps gives you a defensible baseline. The pattern is boring on purpose—boring is what keeps credentials out of GitHub issues.