Skip to main content

Managing Secrets

Secrets provide secure handling of sensitive data like API keys, passwords, and tokens. QIT validates, injects, and redacts secrets automatically.

How Secrets Work

  1. Declaration: Packages declare required secrets in manifest
  2. Validation: QIT checks all secrets exist before execution
  3. Injection: Secrets passed as environment variables
  4. Redaction: Values automatically hidden from output

Declaring Secrets

In qit-test.json:

{
"requires": {
"secrets": [
"STRIPE_TEST_KEY",
"STRIPE_TEST_SECRET",
"WEBHOOK_SIGNING_SECRET"
]
}
}

Setting Secrets

Environment Variables

# Set individually
export STRIPE_TEST_KEY="sk_test_..."
export STRIPE_TEST_SECRET="..."
export WEBHOOK_SIGNING_SECRET="whsec_..."

# Run tests
qit run:e2e woocommerce --config=test.json

From .env File

QIT has built-in support for .env files via the --env_file flag:

qit run:e2e woocommerce --env_file=.env

.env file:

STRIPE_TEST_KEY=sk_test_...
STRIPE_TEST_SECRET=...
WEBHOOK_SIGNING_SECRET=whsec_...

CI/CD Systems

GitHub Actions

- name: Run Tests
env:
STRIPE_TEST_KEY: ${{ secrets.STRIPE_TEST_KEY }}
STRIPE_TEST_SECRET: ${{ secrets.STRIPE_TEST_SECRET }}
run: qit run:e2e woocommerce

GitLab CI

test:
variables:
STRIPE_TEST_KEY: $STRIPE_TEST_KEY
STRIPE_TEST_SECRET: $STRIPE_TEST_SECRET
script:
- qit run:e2e woocommerce

Jenkins

withCredentials([
string(credentialsId: 'stripe-key', variable: 'STRIPE_TEST_KEY'),
string(credentialsId: 'stripe-secret', variable: 'STRIPE_TEST_SECRET')
]) {
sh 'qit run:e2e woocommerce'
}

Validation

Early Validation

Secrets are validated before any execution:

Missing required secrets:
- STRIPE_TEST_KEY (required by: payment-tests)
- WEBHOOK_SECRET (required by: webhook-tests)

Set these environment variables:
export STRIPE_TEST_KEY='your-key'
export WEBHOOK_SECRET='your-secret'

Cross-Package Validation

QIT collects secrets from ALL packages:

Package A declares API_KEY, Package B declares API_SECRET:

{ "requires": { "secrets": ["API_KEY"] } }
{ "requires": { "secrets": ["API_SECRET"] } }

Both must be set before execution starts.

Using Secrets

In Commands

Secrets are available as environment variables:

{
"run": [
"API_KEY=$API_KEY npm test"
]
}

In Test Code

JavaScript:

const apiKey = process.env.STRIPE_TEST_KEY;
const apiSecret = process.env.STRIPE_TEST_SECRET;

test('process payment', async () => {
const stripe = new Stripe(apiKey);
// Use stripe client
});

PHP:

$apiKey = getenv('STRIPE_TEST_KEY');
$apiSecret = getenv('STRIPE_TEST_SECRET');

$stripe = new \Stripe\StripeClient($apiKey);

Python:

import os

api_key = os.environ['STRIPE_TEST_KEY']
api_secret = os.environ['STRIPE_TEST_SECRET']

Automatic Redaction

Secret values are automatically hidden from output:

Before Redaction

Setting up Stripe with key sk_test_51234567890abcdef...
Connection established to webhook whsec_abcdef123456...

After Redaction

Setting up Stripe with key [REDACTED:STRIPE_TEST_KEY]...
Connection established to webhook [REDACTED:WEBHOOK_SECRET]...

Redaction Rules

  • Only actual secret values are redacted
  • Secrets shorter than 4 characters are not redacted
  • Secret names are preserved for debugging
  • Redaction happens in real-time

Secret Patterns

API Keys

{
"requires": {
"secrets": [
"API_KEY",
"API_SECRET",
"API_ENDPOINT"
]
}
}

Usage:

const client = new APIClient({
key: process.env.API_KEY,
secret: process.env.API_SECRET,
endpoint: process.env.API_ENDPOINT
});

Database Credentials

{
"requires": {
"secrets": [
"TEST_DB_HOST",
"TEST_DB_USER",
"TEST_DB_PASS"
]
}
}

Usage:

const connection = mysql.createConnection({
host: process.env.TEST_DB_HOST,
user: process.env.TEST_DB_USER,
password: process.env.TEST_DB_PASS
});

OAuth Tokens

{
"requires": {
"secrets": [
"OAUTH_CLIENT_ID",
"OAUTH_CLIENT_SECRET",
"OAUTH_REDIRECT_URI"
]
}
}

Webhook Secrets

{
"requires": {
"secrets": [
"WEBHOOK_SIGNING_SECRET",
"WEBHOOK_ENDPOINT_SECRET"
]
}
}

Best Practices

1. Use Descriptive Names

Good:

"secrets": [
"STRIPE_TEST_PUBLISHABLE_KEY",
"STRIPE_TEST_SECRET_KEY",
"STRIPE_WEBHOOK_SIGNING_SECRET"
]

Bad:

"secrets": [
"KEY1",
"SECRET",
"TOKEN"
]

2. Document Required Secrets

In package README:

## Required Secrets

- `STRIPE_TEST_KEY`: Stripe test mode publishable key
- `STRIPE_TEST_SECRET`: Stripe test mode secret key
- `WEBHOOK_SECRET`: Stripe webhook signing secret

Get these from your Stripe dashboard.

3. Provide Examples

.env.example:

# Stripe Test Keys (get from https://dashboard.stripe.com/test/apikeys)
STRIPE_TEST_KEY=pk_test_...
STRIPE_TEST_SECRET=sk_test_...
WEBHOOK_SECRET=whsec_...
{
"requires": {
"secrets": [
"PAYMENT_GATEWAY_API_KEY",
"PAYMENT_GATEWAY_API_SECRET",
"PAYMENT_GATEWAY_MERCHANT_ID",
"SHIPPING_API_KEY",
"SHIPPING_API_SECRET"
]
}
}

5. Validate Format

In setup phase:

{
"setup": [
"node ./scripts/validate-secrets.js"
]
}

validate-secrets.js:

if (!process.env.STRIPE_TEST_KEY?.startsWith('pk_test_')) {
throw new Error('STRIPE_TEST_KEY must be a test mode key');
}

Security Considerations

Don't Commit Secrets

.gitignore:

.env
.env.local
.env.*.local
secrets/
*.key
*.pem

Use Test/Sandbox Credentials

Always use test mode credentials:

  • Stripe: sk_test_... not sk_live_...
  • PayPal: Sandbox not Production
  • AWS: Test account not Production

Rotate Regularly

  • Change test credentials periodically
  • Update CI/CD systems when rotated
  • Document rotation procedures

Limit Scope

Use credentials with minimal permissions:

  • Read-only where possible
  • Restricted to test resources
  • Time-limited tokens

Troubleshooting

Secret Not Found

Error:

Missing required secrets:
- API_KEY

Solution:

export API_KEY="your-key"

Secret Not Redacted

Check:

  • Secret value is longer than 3 characters
  • Secret is actually being used
  • Not using pattern-based redaction

Secret in Wrong Format

Validate format in tests:

if (!process.env.API_KEY || process.env.API_KEY.length < 10) {
throw new Error('Invalid API_KEY format');
}

Secret Not Available in Test

Ensure:

  • Declared in qit-test.json
  • Set before running tests
  • Correct variable name

Advanced Patterns

Dynamic Secrets

Load from external source:

export API_KEY=$(vault read -field=key secret/api)
qit run:e2e woocommerce

Environment-Specific Secrets

# Development
export STRIPE_TEST_KEY=$DEV_STRIPE_KEY

# Staging
export STRIPE_TEST_KEY=$STAGING_STRIPE_KEY

Secret Validation Script

validate-env.sh:

#!/bin/bash

required_secrets=(
"STRIPE_TEST_KEY"
"STRIPE_TEST_SECRET"
"WEBHOOK_SECRET"
)

missing=()
for secret in "${required_secrets[@]}"; do
if [ -z "${!secret}" ]; then
missing+=($secret)
fi
done

if [ ${#missing[@]} -gt 0 ]; then
echo "Missing secrets: ${missing[*]}"
exit 1
fi

Conditional Secrets

Only require if feature enabled:

const secrets = ['BASE_API_KEY'];

if (process.env.ENABLE_STRIPE === 'true') {
secrets.push('STRIPE_KEY', 'STRIPE_SECRET');
}

// qit-test.json would list all possible secrets