Echo vs Printf for CLI Secrets

When piping a value to a CLI tool that stores secrets, `echo` adds a trailing newline that becomes part of the stored value. This silently breaks any credential that's compared byte-for-byte — OAuth client IDs, API tokens, webhook secrets. Always use `printf` instead.

Tags

Echo vs Printf for CLI Secrets

The Lesson

When piping a value to a CLI tool that stores secrets, echo adds a trailing newline that becomes part of the stored value. This silently breaks any credential that's compared byte-for-byte — OAuth client IDs, API tokens, webhook secrets. Always use printf instead.

Context

MyReachBand stores Twilio credentials and Google OAuth keys as Cloudflare Worker secrets via wrangler secret put. The secrets are used in HTTP requests where the exact string must match what the remote service expects. A single invisible character at the end breaks the match.

What Happened

  1. Set Google OAuth client ID with echo "790486..." | npx wrangler secret put GOOGLE_CLIENT_ID.
  2. Google OAuth returned redirect_uri_mismatch on every login attempt.
  3. Verified the redirect URI was correct in the Google console. Verified the Worker code constructed the URL properly.
  4. Spent significant time debugging the OAuth callback handler, adding try/catch blocks, checking state cookies.
  5. Eventually realized echo adds \n to the value. The stored client ID was 790486...\n, which got URL-encoded into the redirect URI, which didn't match what Google had registered.
  6. Fixed with printf "790486..." | npx wrangler secret put GOOGLE_CLIENT_ID. No newline, exact match, OAuth worked immediately.

Key Insights

Examples

Bad

echo "GOCSPX-abc123" | npx wrangler secret put GOOGLE_CLIENT_SECRET
# Stores: "GOCSPX-abc123\n" — broken

Good

printf "GOCSPX-abc123" | npx wrangler secret put GOOGLE_CLIENT_SECRET
# Stores: "GOCSPX-abc123" — correct

Safest (Node.js)

node -e "process.stdout.write('GOCSPX-abc123')" | npx wrangler secret put GOOGLE_CLIENT_SECRET

Implementation Guide

Step 1: Replace echo with printf in all secret-setting commands

Search your scripts, documentation, and shell history for echo piped to secret-setting commands:

# Find echo-piped secrets in scripts and docs
grep -rn "echo.*|.*secret\|echo.*| .*put\|echo.*| .*ssm" scripts/ docs/ *.md

Replace every instance with printf:

# Before (broken — stores "value\n")
echo "$SECRET_VALUE" | npx wrangler secret put MY_SECRET

# After (correct — stores "value")
printf "%s" "$SECRET_VALUE" | npx wrangler secret put MY_SECRET

Use printf "%s" with a format string when the value is in a variable — this prevents printf from interpreting backslash sequences in the value itself.

Step 2: Re-set any secrets that may have been set with echo

Since most secret managers don't let you read back stored values, re-set every secret using printf to be safe:

# Re-set all secrets with printf
printf "%s" "your-client-id" | npx wrangler secret put GOOGLE_CLIENT_ID
printf "%s" "your-client-secret" | npx wrangler secret put GOOGLE_CLIENT_SECRET
printf "%s" "your-api-key" | npx wrangler secret put TWILIO_AUTH_TOKEN

If you're unsure whether a secret was set correctly, the fastest test is to use it — a trailing newline will cause authentication failures, and re-setting with printf is a one-command fix.

Step 3: Add a shell alias or wrapper to prevent recurrence

Add a helper function to your shell profile that makes it impossible to accidentally use echo:

# Add to ~/.bashrc or ~/.zshrc
secret-put() {
  if [ -z "$1" ] || [ -z "$2" ]; then
    echo "Usage: secret-put NAME VALUE"
    return 1
  fi
  printf "%s" "$2" | npx wrangler secret put "$1"
}

Usage: secret-put GOOGLE_CLIENT_ID "790486..." — always uses printf, never adds a newline.

Applicability

This applies to any CLI that reads secrets from stdin: wrangler secret put, aws ssm put-parameter, vault kv put, kubectl create secret. The newline problem is universal. It does NOT apply when the CLI reads from a file or prompts interactively — those typically trim whitespace.

Related Lessons

Related Lessons