xiaohongshu
xiaohongshu
env-secrets-manager
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".
Full skill instructions
Securely manage secrets and environment variables across environments.
# .env.example (commit this)
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
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
# Third-party services
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# .env.local (never commit)
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/
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
// Database
DATABASE_URL: z.string().url(),
// Redis
REDIS_URL: z.string().url().optional(),
// Authentication
JWT_SECRET: z.string().min(32),
JWT_EXPIRES_IN: z.string().default('7d'),
// AWS (optional in development)
AWS_ACCESS_KEY_ID: z.string().optional(),
AWS_SECRET_ACCESS_KEY: z.string().optional(),
AWS_REGION: z.string().default('us-east-1'),
// External Services
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();
// env.mjs (for Next.js)
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,
},
});
// lib/secrets/aws.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
CreateSecretCommand,
UpdateSecretCommand,
RotateSecretCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({
region: process.env.AWS_REGION,
});
// Cache for secrets
const secretsCache = new Map<string, { value: any; expiresAt: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
export async function getSecret<T = Record<string, string>>(
secretName: string
): Promise<T> {
// Check cache
const cached = secretsCache.get(secretName);
if (cached && cached.expiresAt > Date.now()) {
return cached.value as T;
}
try {
const command = new GetSecretValueCommand({
SecretId: secretName,
});
const response = await client.send(command);
let secretValue: T;
if (response.SecretString) {
secretValue = JSON.parse(response.SecretString);
} else if (response.SecretBinary) {
const buff = Buffer.from(response.SecretBinary);
secretValue = JSON.parse(buff.toString('utf-8'));
} else {
throw new Error('Secret has no value');
}
// Update cache
secretsCache.set(secretName, {
value: secretValue,
expiresAt: Date.now() + CACHE_TTL,
});
return secretValue;
} catch (error) {
console.error(`Failed to retrieve secret ${secretName}:`, error);
throw error;
}
}
export async function createSecret(
secretName: string,
secretValue: Record<string, string>
): Promise<void> {
const command = new CreateSecretCommand({
Name: secretName,
SecretString: JSON.stringify(secretValue),
Tags: [
{ Key: 'Environment', Value: process.env.NODE_ENV || 'development' },
{ Key: 'Application', Value: 'my-app' },
],
});
await client.send(command);
}
export async function updateSecret(
secretName: string,
secretValue: Record<string, string>
): Promise<void> {
const command = new UpdateSecretCommand({
SecretId: secretName,
SecretString: JSON.stringify(secretValue),
});
await client.send(command);
// Invalidate cache
secretsCache.delete(secretName);
}
export async function rotateSecret(secretName: string): Promise<void> {
const command = new RotateSecretCommand({
SecretId: secretName,
RotateImmediately: true,
});
await client.send(command);
// Invalidate cache
secretsCache.delete(secretName);
}
// lib/secrets/init.ts
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: string }>('myapp/production/stripe'),
]);
appSecrets = {
database: dbSecrets,
jwt: jwtSecrets,
stripe: stripeSecrets,
};
return appSecrets;
}
export function getSecrets(): AppSecrets {
if (!appSecrets) {
throw new Error('Secrets not initialized. Call initializeSecrets() first.');
}
return appSecrets;
}
// Usage in app startup
async function startApp() {
await initializeSecrets();
// ... start server
}
// lib/secrets/vault.ts
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/${path}`, { data });
}
// Dynamic database credentials
export async function getDatabaseCredentials(): Promise<{
username: string;
password: string;
}> {
const result = await vaultClient.read('database/creds/my-role');
return {
username: result.data.username,
password: result.data.password,
};
}
// lib/secrets/doppler.ts
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);
}
// CLI usage
// doppler run -- npm start
// lib/secrets/local.ts
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 = createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
// Combine: salt + iv + authTag + encrypted
const combined = Buffer.concat([salt, iv, authTag, encrypted]);
return combined.toString('base64');
}
export async function decrypt(ciphertext: string, password: string): Promise<string> {
const combined = Buffer.from(ciphertext, 'base64');
const salt = combined.subarray(0, SALT_LENGTH);
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
const authTag = combined.subarray(
SALT_LENGTH + IV_LENGTH,
SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
);
const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
const key = (await scryptAsync(password, salt, KEY_LENGTH)) as Buffer;
const decipher = createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]);
return decrypted.toString('utf8');
}
// Encrypted env file
export async function loadEncryptedEnv(filepath: string, password: string): Promise<void> {
const encrypted = await fs.readFile(filepath, 'utf8');
const decrypted = await decrypt(encrypted, password);
const lines = decrypted.split('\n');
for (const line of lines) {
const [key, ...valueParts] = line.split('=');
if (key && valueParts.length > 0) {
process.env[key.trim()] = valueParts.join('=').trim();
}
}
}
# k8s/secrets.yaml
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_...
---
# Using External Secrets Operator
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: JWT_SECRET
remoteRef:
key: production/myapp/auth
property: jwtSecret
// Reading K8s secrets in Node.js
import fs from 'fs';
function getSecretFromVolume(secretName: string): string {
const secretPath = `/var/run/secrets/${secretName}`;
return fs.readFileSync(secretPath, 'utf8').trim();
}
// lib/secrets/rotation.ts
interface RotationConfig {
secretName: string;
rotationInterval: number; // milliseconds
onRotation: (newSecret: string) => Promise<void>;
}
class SecretRotator {
private intervals: Map<string, NodeJS.Timeout> = new Map();
async startRotation(config: RotationConfig): Promise<void> {
// Immediate rotation
await this.rotate(config);
// Schedule recurring rotation
const interval = setInterval(
() => this.rotate(config),
config.rotationInterval
);
this.intervals.set(config.secretName, interval);
}
private async rotate(config: RotationConfig): Promise<void> {
try {
// Generate new secret
const newSecret = this.generateSecret();
// Update in secrets manager
await updateSecret(config.secretName, { value: newSecret });
// Notify application
await config.onRotation(newSecret);
console.log(`Rotated secret: ${config.secretName}`);
} catch (error) {
console.error(`Failed to rotate ${config.secretName}:`, error);
}
}
private generateSecret(): string {
return randomBytes(32).toString('base64');
}
stopRotation(secretName: string): void {
const interval = this.intervals.get(secretName);
if (interval) {
clearInterval(interval);
this.intervals.delete(secretName);
}
}
}
Every secrets implementation should include:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience