| name | env-secrets-manager |
| description | Manages environment variables and secrets securely with encryption, rotation, and provider integration. Use when users request "secrets management", "environment variables", "API keys", "credentials storage", or "secret rotation". |
Environment Secrets Manager
Securely manage secrets and environment variables across environments.
Core Workflow
- Identify secrets: Classify sensitive data
- Choose provider: Select secrets manager
- Configure storage: Encrypted storage
- Implement access: Secure retrieval
- Setup rotation: Automatic key rotation
- Audit access: Monitor usage
Local Development
Environment Files
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
API_KEY=your-api-key-here
JWT_SECRET=your-jwt-secret-here
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
DATABASE_URL=postgresql://user:actualpassword@localhost:5432/mydb
JWT_SECRET=super-secret-jwt-key-that-is-long-enough
STRIPE_SECRET_KEY=sk_test_actual_key
# .gitignore
.env
.env.local
.env.*.local
.env.production
*.pem
*.key
secrets/
Environment Validation
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
JWT_SECRET: z.string().min(32),
JWT_EXPIRES_IN: z.string().default('7d'),
AWS_ACCESS_KEY_ID: z.string().optional(),
AWS_SECRET_ACCESS_KEY: z.string().optional(),
AWS_REGION: z.string().default('us-east-1'),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
});
export type Env = z.infer<typeof envSchema>;
function validateEnv(): Env {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('❌ Invalid environment variables:');
console.error(result.error.format());
throw new Error('Invalid environment configuration');
}
return result.data;
}
export const env = validateEnv();
T3 Env Pattern
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_KEY: z.string().startsWith('pk_'),
},
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
JWT_SECRET: process.env.JWT_SECRET,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_STRIPE_KEY: process.env.NEXT_PUBLIC_STRIPE_KEY,
},
});
AWS Secrets Manager
import {
SecretsManagerClient,
GetSecretValueCommand,
CreateSecretCommand,
UpdateSecretCommand,
RotateSecretCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({
region: process.env.AWS_REGION,
});
const secretsCache = new Map<string, { value: any; expiresAt: number }>();
const CACHE_TTL = 5 * 60 * 1000;
export async function getSecret<T = Record<string, string>>(
secretName: string
): Promise<T> {
const cached = secretsCache.get(secretName);
if (cached && cached.expiresAt > Date.now()) {
return cached.value as T;
}
try {
command = ({
: secretName,
});
response = client.(command);
: T;
(response.) {
secretValue = .(response.);
} (response.) {
buff = .(response.);
secretValue = .(buff.());
} {
();
}
secretsCache.(secretName, {
: secretValue,
: .() + ,
});
secretValue;
} (error) {
.(, error);
error;
}
}
(): <> {
command = ({
: secretName,
: .(secretValue),
: [
{ : , : process.. || },
{ : , : },
],
});
client.(command);
}
(): <> {
command = ({
: secretName,
: .(secretValue),
});
client.(command);
secretsCache.(secretName);
}
(): <> {
command = ({
: secretName,
: ,
});
client.(command);
secretsCache.(secretName);
}
Initialize Secrets on Startup
import { getSecret } from './aws';
interface AppSecrets {
database: {
url: string;
readUrl?: string;
};
jwt: {
secret: string;
refreshSecret: string;
};
stripe: {
secretKey: string;
webhookSecret: string;
};
}
let appSecrets: AppSecrets | null = null;
export async function initializeSecrets(): Promise<AppSecrets> {
if (appSecrets) return appSecrets;
const [dbSecrets, jwtSecrets, stripeSecrets] = await Promise.all([
getSecret<{ url: string; readUrl?: string }>('myapp/production/database'),
getSecret<{ secret: string; refreshSecret: string }>('myapp/production/jwt'),
getSecret<{ secretKey: string; webhookSecret: }>(),
]);
appSecrets = {
: dbSecrets,
: jwtSecrets,
: stripeSecrets,
};
appSecrets;
}
(): {
(!appSecrets) {
();
}
appSecrets;
}
() {
();
}
HashiCorp Vault
import vault from 'node-vault';
const vaultClient = vault({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
export async function getSecretFromVault<T>(path: string): Promise<T> {
try {
const result = await vaultClient.read(`secret/data/${path}`);
return result.data.data as T;
} catch (error) {
console.error(`Failed to read secret from path ${path}:`, error);
throw error;
}
}
export async function writeSecretToVault(
path: string,
data: Record<string, string>
): Promise<void> {
await vaultClient.write(`secret/data/`, { data });
}
(): <{
: ;
: ;
}> {
result = vaultClient.();
{
: result..,
: result..,
};
}
Doppler Integration
import { DopplerSDK } from '@dopplerhq/node-sdk';
const doppler = new DopplerSDK({
accessToken: process.env.DOPPLER_TOKEN,
});
export async function fetchSecrets(
project: string,
config: string
): Promise<Record<string, string>> {
const response = await doppler.secrets.download({
project,
config,
format: 'json',
});
return JSON.parse(response);
}
Local Encryption
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from 'crypto';
import { promisify } from 'util';
import fs from 'fs/promises';
const scryptAsync = promisify(scrypt);
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32;
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
const SALT_LENGTH = 32;
export async function encrypt(plaintext: string, password: string): Promise<string> {
const salt = randomBytes(SALT_LENGTH);
const key = (await scryptAsync(password, salt, KEY_LENGTH)) as Buffer;
const iv = randomBytes(IV_LENGTH);
const cipher = (, key, iv);
encrypted = .([
cipher.(plaintext, ),
cipher.(),
]);
authTag = cipher.();
combined = .([salt, iv, authTag, encrypted]);
combined.();
}
(): <> {
combined = .(ciphertext, );
salt = combined.(, );
iv = combined.(, + );
authTag = combined.(
+ ,
+ +
);
encrypted = combined.( + + );
key = ( (password, salt, )) ;
decipher = (, key, iv);
decipher.(authTag);
decrypted = .([
decipher.(encrypted),
decipher.(),
]);
decrypted.();
}
(): <> {
encrypted = fs.(filepath, );
decrypted = (encrypted, password);
lines = decrypted.();
( line lines) {
[key, ...valueParts] = line.();
(key && valueParts. > ) {
process.[key.()] = valueParts.().();
}
}
}
Kubernetes Secrets
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
stringData:
DATABASE_URL: postgresql://user:password@host:5432/db
JWT_SECRET: your-jwt-secret
STRIPE_SECRET_KEY: sk_live_...
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: DATABASE_URL
remoteRef:
key: production/myapp/database
property: url
- secretKey:
import fs from 'fs';
function getSecretFromVolume(secretName: string): string {
const secretPath = `/var/run/secrets/${secretName}`;
return fs.readFileSync(secretPath, 'utf8').trim();
}
Secret Rotation
interface RotationConfig {
secretName: string;
rotationInterval: number;
onRotation: (newSecret: string) => Promise<void>;
}
class SecretRotator {
private intervals: Map<string, NodeJS.Timeout> = new Map();
async startRotation(config: RotationConfig): Promise<void> {
await this.rotate(config);
const interval = setInterval(
() => this.rotate(config),
config.rotationInterval
);
this.intervals.set(config.secretName, interval);
}
private async rotate(config: RotationConfig): <> {
{
newSecret = .();
(config., { : newSecret });
config.(newSecret);
.();
} (error) {
.(, error);
}
}
(): {
().();
}
(: ): {
interval = ..(secretName);
(interval) {
(interval);
..(secretName);
}
}
}
Best Practices
- Never commit secrets: Use .gitignore
- Validate on startup: Fail fast on missing secrets
- Use secret managers: AWS, Vault, Doppler
- Rotate regularly: Automate key rotation
- Encrypt at rest: Even in env files
- Audit access: Log secret retrieval
- Limit scope: Minimal permissions
- Different per environment: Never share production secrets
Output Checklist
Every secrets implementation should include: