Inject Secrets Into Deployment Scripts Safely
Stop hardcoding credentials in scripts and environment variables. Use kh kv run to inject secrets directly into commands without exposing them to the shell.
The Problem With Environment Variables in Scripts
Deployment scripts need configuration. A script that deploys to AWS needs an access key and the AWS region. A script that migrates a database needs a password and the database hostname. A script that triggers a CI/CD pipeline needs an API token and the API endpoint.
Some of these are secrets that must be protected. Others are configuration values that are different for each environment but not sensitive.
A script that works for staging should work for production with only the environment variables changed. The staging database is in us-east-1. The production database is in ca-central-1. The staging API endpoint is https://api-staging.example.com. The production endpoint is https://api.example.com.
Teams typically handle this in one of three unsafe ways.
First, hardcode variables in the script. The script is checked into git. Sensitive values are visible to anyone with repository access. When values change, the script is updated and old values remain in git history. Updating the script for a new environment means creating a copy of the script, which drifts over time.
Second, export variables before running the script. Sensitive values are visible in your shell history. The script inherits the variables. If the script crashes or logs its environment, values appear in error output. The variables stay in memory until you close the terminal. Managing different sets of variables for different environments means creating shell profiles or export files, which you must remember to source before running the script.
Third, pass variables as command-line arguments. Sensitive values are visible in the process listing. They appear in your shell history. They show up in logs of what commands were executed. This approach does not scale when a script needs ten or twenty variables.
All three approaches create either security holes or operational complexity.
A Safer Pattern
KeyHarbour offers a different approach. Store all your environment variables as key-value pairs in a workspace. Create separate workspaces for each environment: staging, production, development. When you need to run a script, use the kh kv run command to inject the entire environment into the script without exposing variables to your parent shell.
The script receives all variables it needs. Your shell does not. Your shell history does not. The process listing does not. The variables are scoped to the environment where they belong.
Real Example: Environment-Scoped Deployment
Your team maintains an application that deploys to three environments: development, staging, and production. The same deployment script runs in all three, but the configuration changes.
You create three workspaces in KeyHarbour: one for development, one for staging, one for production.
In the development workspace, you store these variables:
kh kv set APP_ENV "development" --workspace dev
kh kv set AWS_REGION "us-east-1" --workspace dev
kh kv set DATABASE_HOST "db-dev.internal" --workspace dev
kh kv set DATABASE_NAME "app_dev" --workspace dev
kh kv set DATABASE_USER "app_user" --workspace dev
kh kv set DATABASE_PASSWORD "dev-password" --workspace dev --encrypt
kh kv set API_ENDPOINT "https://api-dev.internal" --workspace dev
kh kv set LOG_LEVEL "debug" --workspace dev
kh kv set ENABLE_DEBUG_MODE "true" --workspace dev
kh kv set SLACK_WEBHOOK_URL "https://hooks.slack.com/dev/..." --workspace devIn the production workspace, the same variables but with production values:
kh kv set APP_ENV "production" --workspace prod
kh kv set AWS_REGION "ca-central-1" --workspace prod
kh kv set DATABASE_HOST "db-prod.internal" --workspace prod
kh kv set DATABASE_NAME "app_prod" --workspace prod
kh kv set DATABASE_USER "app_prod_user" --workspace prod
kh kv set DATABASE_PASSWORD "prod-password" --workspace prod --encrypt
kh kv set API_ENDPOINT "https://api.example.com" --workspace prod
kh kv set LOG_LEVEL "warning" --workspace prod
kh kv set ENABLE_DEBUG_MODE "false" --workspace prod
kh kv set SLACK_WEBHOOK_URL "https://hooks.slack.com/prod/..." --workspace prodThe same deployment script works for both environments. You only change which workspace you use.
# Deploy to development
kh kv run --workspace dev -- ./deploy.sh
# Deploy to production
kh kv run --workspace prod -- ./deploy.shInside deploy.sh, all variables are available. The script adapts to the environment automatically.
#!/bin/bash
# All variables are available here
echo "Deploying to $APP_ENV in region $AWS_REGION"
# Connect to the correct database for this environment
psql -h "$DATABASE_HOST" -d "$DATABASE_NAME" -U "$DATABASE_USER" < schema.sql
# Configure the application with environment-specific settings
aws configure set region "$AWS_REGION"
export API_ENDPOINT
export LOG_LEVEL
# Run the application
./app/bin/start
# Notify the right Slack channel for this environment
curl -X POST "$SLACK_WEBHOOK_URL" \
-d "{\"text\":\"Deployment complete in $APP_ENV\"}"When the script finishes, no variables remain in your shell. They do not appear in your history. Each environment is completely isolated.
Filter Secrets by Prefix
Not every secret in a workspace applies to every script. Your workspace might contain secrets for database migrations, infrastructure provisioning, and application deployment. Each script needs only the secrets relevant to it.
Use the --prefix flag to inject only secrets that start with a specific prefix. The prefix is stripped, so the script receives clean variable names.
kh kv set DEPLOY_AWS_KEY "AKIA..." --workspace prod
kh kv set DEPLOY_DB_URL "postgres://..." --workspace prod
kh kv set APP_API_TOKEN "secret" --workspace prod
# This script gets only DEPLOY_AWS_KEY and DEPLOY_DB_URL
# They are renamed to AWS_KEY and DB_URL
kh kv run --workspace prod --prefix DEPLOY_ -- ./deploy.shInside the script, the variables are AWS_KEY and DB_URL. The DEPLOY_ prefix is automatically removed. The APP_API_TOKEN is not injected because it does not match the prefix.
Export to .env for Local Development
In development, you might want to load all workspace secrets into a .env file that your local tools read.
kh kv env --workspace staging --format dotenv > .envThis creates a .env file with all secrets from the staging workspace in standard format. Load it using direnv or your framework of choice.
For production, use kh kv run instead. Never write production secrets to disk.
Use With CI/CD Pipelines
The same pattern works in CI/CD. Your pipeline runner has access to KH_TOKEN and KH_ENDPOINT. Everything else comes from the workspace.
A GitLab CI pipeline that deploys to multiple environments:
deploy_staging:
script:
- kh kv run --workspace staging -- ./scripts/deploy.sh
deploy_production:
script:
- kh kv run --workspace prod -- ./scripts/deploy.shEach environment has its own workspace with its own secrets. The pipeline does not store individual credentials. It stores one token that has permission to read from both workspaces.
Why This Matters
Secrets leak when they are visible. The fewer places a secret appears, the fewer opportunities for it to escape. By injecting secrets directly into a command’s environment without exposing them to the parent shell, you reduce the attack surface.
The secret lives in KeyHarbour, encrypted. It moves from KeyHarbour directly into the script’s memory. It never touches your shell, your history, or the process listing. When the script finishes, the secret is gone.
This is the same pattern used by password managers and secrets engines in production infrastructure. Use it for your local deployments too.
The full CLI reference is at docs.keyharbour.ca. Reach out at info@keyharbour.ca with questions.