Skip to main content

Secrets Management

Stratix provides SecretsManager in @stratix/runtime for managing application secrets with built-in caching and provider abstraction.

Overview

SecretsManager is a production-ready secrets management solution that:

  • Caches secrets - Reduces environment variable lookups with configurable TTL
  • Provider abstraction - Designed to support multiple backends (currently environment variables)
  • Type-safe - Full TypeScript support with strict typing
  • Zero dependencies - Built into runtime, no additional packages needed
  • Performance optimized - In-memory caching for high-throughput applications

Installation

SecretsManager is included in @stratix/runtime:

npm install @stratix/runtime

Basic Usage

Creating a SecretsManager

import { SecretsManager } from '@stratix/runtime';

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'APP_',
cache: true,
cacheTTL: 300000 // 5 minutes in milliseconds
});

Reading Secrets

// Get a secret (returns undefined if not found)
const dbUrl = await secrets.get('DATABASE_URL');
// Reads from process.env.APP_DATABASE_URL

if (dbUrl) {
console.log('Database URL:', dbUrl);
}

// Get a required secret (throws if not found)
try {
const apiKey = await secrets.getRequired('API_KEY');
console.log('API Key loaded');
} catch (error) {
console.error('Missing required secret:', error.message);
}

// Check if a secret exists
const hasRedisUrl = await secrets.has('REDIS_URL');
if (hasRedisUrl) {
console.log('Redis is configured');
}

Configuration

SecretsManagerConfig

interface SecretsManagerConfig {
provider: 'environment'; // Provider type (currently only 'environment')
prefix: string; // Prefix for environment variables
cache: boolean; // Enable/disable caching
cacheTTL: number; // Cache time-to-live in milliseconds
}

Configuration Examples

Development (No Caching)

const secrets = new SecretsManager({
provider: 'environment',
prefix: '',
cache: false,
cacheTTL: 0
});

Production (With Caching)

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'PROD_',
cache: true,
cacheTTL: 600000 // 10 minutes
});

Multi-Environment

const env = process.env.NODE_ENV || 'development';

const secrets = new SecretsManager({
provider: 'environment',
prefix: env === 'production' ? 'PROD_' : 'DEV_',
cache: env === 'production',
cacheTTL: env === 'production' ? 300000 : 0
});

Cache Management

Clear Cache

// Clear specific secret from cache
secrets.clearCache('DATABASE_URL');

// Clear entire cache
secrets.clearCache();

Monitor Cache

// Get current cache size
const cacheSize = secrets.getCacheSize();
console.log(`Cached secrets: ${cacheSize}`);

Common Patterns

Database Configuration

import { SecretsManager } from '@stratix/runtime';

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'DB_',
cache: true,
cacheTTL: 300000
});

const dbConfig = {
host: await secrets.getRequired('HOST'),
port: parseInt(await secrets.getRequired('PORT')),
database: await secrets.getRequired('NAME'),
user: await secrets.getRequired('USER'),
password: await secrets.getRequired('PASSWORD')
};

// Reads from: DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD

API Keys

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'API_',
cache: true,
cacheTTL: 600000 // 10 minutes
});

const openaiKey = await secrets.getRequired('OPENAI_KEY');
const stripeKey = await secrets.getRequired('STRIPE_KEY');
const twilioKey = await secrets.get('TWILIO_KEY'); // Optional

Feature Flags

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'FEATURE_',
cache: true,
cacheTTL: 60000 // 1 minute for quick updates
});

const enableNewUI = (await secrets.get('NEW_UI')) === 'true';
const enableBetaFeatures = (await secrets.get('BETA')) === 'true';

Singleton Pattern

// src/infrastructure/secrets.ts
import { SecretsManager } from '@stratix/runtime';

let secretsInstance: SecretsManager | null = null;

export function getSecretsManager(): SecretsManager {
if (!secretsInstance) {
secretsInstance = new SecretsManager({
provider: 'environment',
prefix: process.env.SECRET_PREFIX || '',
cache: process.env.NODE_ENV === 'production',
cacheTTL: 300000
});
}
return secretsInstance;
}
// Usage in other files
import { getSecretsManager } from './infrastructure/secrets';

const secrets = getSecretsManager();
const apiKey = await secrets.getRequired('API_KEY');

Environment Variables

Setting Up .env Files

# .env.development
APP_DATABASE_URL=postgresql://localhost:5432/myapp_dev
APP_API_KEY=dev_key_12345
APP_REDIS_URL=redis://localhost:6379

# .env.production
APP_DATABASE_URL=postgresql://prod-db:5432/myapp
APP_API_KEY=prod_key_67890
APP_REDIS_URL=redis://prod-redis:6379

Loading Environment Variables

// Load .env file (using dotenv)
import 'dotenv/config';

import { SecretsManager } from '@stratix/runtime';

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'APP_',
cache: true,
cacheTTL: 300000
});

Best Practices

1. Use Prefixes for Organization

// Separate concerns with prefixes
const dbSecrets = new SecretsManager({
provider: 'environment',
prefix: 'DB_',
cache: true,
cacheTTL: 300000
});

const apiSecrets = new SecretsManager({
provider: 'environment',
prefix: 'API_',
cache: true,
cacheTTL: 600000
});

2. Enable Caching in Production

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'APP_',
cache: process.env.NODE_ENV === 'production',
cacheTTL: 300000
});

3. Use getRequired for Critical Secrets

// Fail fast if critical secrets are missing
const dbUrl = await secrets.getRequired('DATABASE_URL');
const apiKey = await secrets.getRequired('API_KEY');

// Use get() for optional secrets
const analyticsKey = await secrets.get('ANALYTICS_KEY');

4. Clear Cache on Configuration Changes

// After updating environment variables
secrets.clearCache();

// Or clear specific secrets
secrets.clearCache('DATABASE_URL');

5. Validate Secret Format

const dbPort = await secrets.getRequired('DB_PORT');
const port = parseInt(dbPort);

if (isNaN(port) || port < 1 || port > 65535) {
throw new Error('Invalid DB_PORT value');
}

Performance Considerations

Cache TTL Guidelines

  • High-frequency reads: 5-10 minutes (300000-600000ms)
  • Moderate reads: 1-5 minutes (60000-300000ms)
  • Dynamic configs: 30-60 seconds (30000-60000ms)
  • Development: Disable caching (cache: false)

Memory Usage

Each cached secret stores:

  • Secret key (string)
  • Secret value (string)
  • Expiration timestamp (number)

For 100 secrets averaging 50 bytes each: ~5KB memory usage.

Migration from @stratix/runtime Plugin

If you were using the old @stratix/runtime plugin:

Before (Plugin)

import { ApplicationBuilder } from '@stratix/runtime';
import { SecretsPlugin } from '@stratix/runtime';

const app = await ApplicationBuilder.create()
.usePlugin(new SecretsPlugin())
.withConfig({
'secrets': {
provider: 'environment',
prefix: 'APP_',
cache: true
}
})
.build();

const secrets = app.resolve<SecretsManager>('secrets:manager');

After (Runtime)

import { SecretsManager } from '@stratix/runtime';

const secrets = new SecretsManager({
provider: 'environment',
prefix: 'APP_',
cache: true,
cacheTTL: 300000
});

API Reference

Methods

get(key: string): Promise<string | undefined>

Retrieves a secret value. Returns undefined if not found.

const value = await secrets.get('API_KEY');

getRequired(key: string): Promise<string>

Retrieves a required secret value. Throws an error if not found.

const value = await secrets.getRequired('DATABASE_URL');

has(key: string): Promise<boolean>

Checks if a secret exists.

const exists = await secrets.has('OPTIONAL_KEY');

clearCache(key?: string): void

Clears the cache. If key is provided, clears only that secret.

secrets.clearCache('API_KEY');  // Clear specific
secrets.clearCache(); // Clear all

getCacheSize(): number

Returns the number of cached secrets.

const size = secrets.getCacheSize();

Next Steps