Claude
Skills
Sign in
Back

env-secrets-manager

Included with Lifetime
$97 forever

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".

Backend & APIs

What this skill does


# Environment Secrets Manager

Securely manage secrets and environment variables across environments.

## Core Workflow

1. **Identify secrets**: Classify sensitive data
2. **Choose provider**: Select secrets manager
3. **Configure storage**: Encrypted storage
4. **Implement access**: Secure retrieval
5. **Setup rotation**: Automatic key rotation
6. **Audit access**: Monitor usage

## Local Development

### Environment Files

```bash
# .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_...
```

```bash
# .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
# .gitignore
.env
.env.local
.env.*.local
.env.production
*.pem
*.key
secrets/
```

### Environment Validation

```typescript
// 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();
```

### T3 Env Pattern

```typescript
// 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,
  },
});
```

## AWS Secrets Manager

```typescript
// 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);
}
```

### Initialize Secrets on Startup

```typescript
// 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
}
```

## HashiCorp Vault

```typescript
// 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,
  };
}
```

## Doppler Integration

```typescript
// 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
// d

Related in Backend & APIs