n4nAI

Reading API keys from environment variables in bash safely

Practical guide to bash environment variables api key safety: load secrets from restricted files, validate, use in curl, and verify no leaks in scripts.

n4n Team3 min read753 words

Audio narration

Coming soon — every post will get a voice note here.

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:

  1. File permissions
    [[ $(stat -c '%a' secrets.env) == "600" ]] || echo "BAD PERMS"
  2. Variable presence without disclosure
    [[ -v LLM_GATEWAY_KEY ]] && echo "Key loaded (value not shown)"
  3. No key in shell history — never type the secret at a prompt. Source only.
  4. Process table scan — run the script and check ps aux | grep -i bearer; you should see nothing.
  5. Syntax checkbash -n yourscript.sh catches errors before execution.
  6. Dry-run with a fake key — set LLM_GATEWAY_KEY=sk-test and 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.

Tagsbashapi-keyssecurityenvironment-variables

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All bash/shell scripting llm automation posts →