Secrets Management: Vault, AWS Secrets Manager & Environment Variables
Introduction: Why Proper Secrets Management Prevents ChatGPT Breaches
Every ChatGPT app requires secrets—API keys, database credentials, OAuth tokens, encryption keys. A single exposed secret can compromise your entire infrastructure. In 2023, over 10 million secrets were leaked via public GitHub repositories, leading to $2.3 billion in breach costs. For ChatGPT apps handling user conversations, payment data, and business intelligence, secrets management isn't optional—it's the foundation of your security architecture.
Traditional approaches like hardcoding credentials or committing .env files create catastrophic vulnerabilities. Modern secrets management requires dynamic secret generation, automatic rotation, encryption at rest and in transit, zero-trust access controls, and comprehensive audit logging. This guide implements production-ready secrets management using HashiCorp Vault for dynamic secrets, AWS Secrets Manager for managed rotation, and secure environment variable patterns for local development.
Whether you're building a fitness booking ChatGPT app handling payment credentials or a restaurant reservation system managing customer data, these patterns prevent credential theft, enable compliance (SOC 2, PCI DSS, HIPAA), and maintain zero-trust security posture across your infrastructure.
HashiCorp Vault: Dynamic Secrets & Encryption as a Service
HashiCorp Vault is the industry-standard secrets management platform, providing dynamic secret generation, encryption as a service, and centralized secret storage with fine-grained access policies. Unlike static credentials stored in environment variables, Vault generates short-lived credentials on-demand, automatically revokes them after use, and provides complete audit trails.
Why Vault for ChatGPT Apps
ChatGPT apps require multiple secret types: OpenAI API keys for model access, database credentials for user data, OAuth tokens for third-party integrations, encryption keys for conversation storage, and webhook signing secrets. Vault's key/value store (KV v2) provides versioned secret storage, while its dynamic secret engines generate temporary database credentials, AWS IAM credentials, and PKI certificates.
Key Vault features:
- Dynamic secrets: Generate PostgreSQL credentials with 1-hour TTL
- Encryption as a service: Encrypt conversation data without storing keys
- Secret versioning: Roll back to previous API key versions
- Lease management: Automatic credential revocation
- Audit logging: Complete secret access history
Production Vault Client Implementation
This TypeScript client implements secure Vault authentication, secret retrieval with caching, dynamic secret leasing, and graceful error handling:
// vault-client.ts - Production HashiCorp Vault client
import axios, { AxiosInstance } from 'axios';
import { promisify } from 'util';
import { exec } from 'child_process';
const execAsync = promisify(exec);
interface VaultConfig {
address: string;
namespace?: string;
roleId?: string;
secretId?: string;
token?: string;
}
interface VaultSecret {
data: {
data: Record<string, any>;
metadata: {
created_time: string;
version: number;
};
};
}
interface DynamicCredential {
username: string;
password: string;
lease_id: string;
lease_duration: number;
renewable: boolean;
}
class VaultClient {
private client: AxiosInstance;
private token: string | null = null;
private tokenExpiry: number = 0;
private secretCache: Map<string, { value: any; expiry: number }> = new Map();
private leases: Map<string, NodeJS.Timeout> = new Map();
constructor(private config: VaultConfig) {
this.client = axios.create({
baseURL: config.address,
headers: {
'X-Vault-Namespace': config.namespace || '',
},
});
}
/**
* Authenticate with Vault using AppRole (production)
*/
async authenticate(): Promise<void> {
try {
if (this.config.token) {
// Token auth for local development
this.token = this.config.token;
this.tokenExpiry = Date.now() + 3600000; // 1 hour
return;
}
if (!this.config.roleId || !this.config.secretId) {
throw new Error('Vault authentication requires roleId and secretId');
}
// AppRole auth for production
const response = await this.client.post('/v1/auth/approle/login', {
role_id: this.config.roleId,
secret_id: this.config.secretId,
});
this.token = response.data.auth.client_token;
this.tokenExpiry = Date.now() + response.data.auth.lease_duration * 1000;
// Schedule token renewal 5 minutes before expiry
const renewalTime = response.data.auth.lease_duration - 300;
setTimeout(() => this.renewToken(), renewalTime * 1000);
console.log('✅ Vault authentication successful');
} catch (error) {
console.error('❌ Vault authentication failed:', error);
throw new Error(`Vault auth failed: ${error.message}`);
}
}
/**
* Renew Vault token before expiry
*/
private async renewToken(): Promise<void> {
try {
const response = await this.client.post(
'/v1/auth/token/renew-self',
{},
{ headers: { 'X-Vault-Token': this.token } }
);
this.tokenExpiry = Date.now() + response.data.auth.lease_duration * 1000;
// Schedule next renewal
const renewalTime = response.data.auth.lease_duration - 300;
setTimeout(() => this.renewToken(), renewalTime * 1000);
console.log('✅ Vault token renewed');
} catch (error) {
console.error('❌ Token renewal failed, re-authenticating');
await this.authenticate();
}
}
/**
* Read KV v2 secret with caching
*/
async getSecret(path: string, cacheTTL: number = 300): Promise<any> {
// Check cache
const cached = this.secretCache.get(path);
if (cached && cached.expiry > Date.now()) {
return cached.value;
}
try {
await this.ensureAuthenticated();
const response = await this.client.get<VaultSecret>(
`/v1/secret/data/${path}`,
{ headers: { 'X-Vault-Token': this.token } }
);
const secretData = response.data.data.data;
// Cache secret
this.secretCache.set(path, {
value: secretData,
expiry: Date.now() + cacheTTL * 1000,
});
return secretData;
} catch (error) {
console.error(`❌ Failed to read secret ${path}:`, error);
throw new Error(`Vault read failed: ${error.message}`);
}
}
/**
* Write KV v2 secret
*/
async writeSecret(path: string, data: Record<string, any>): Promise<void> {
try {
await this.ensureAuthenticated();
await this.client.post(
`/v1/secret/data/${path}`,
{ data },
{ headers: { 'X-Vault-Token': this.token } }
);
// Invalidate cache
this.secretCache.delete(path);
console.log(`✅ Secret written to ${path}`);
} catch (error) {
console.error(`❌ Failed to write secret ${path}:`, error);
throw new Error(`Vault write failed: ${error.message}`);
}
}
/**
* Generate dynamic database credentials
*/
async getDynamicDBCredentials(role: string): Promise<DynamicCredential> {
try {
await this.ensureAuthenticated();
const response = await this.client.get(
`/v1/database/creds/${role}`,
{ headers: { 'X-Vault-Token': this.token } }
);
const creds: DynamicCredential = {
username: response.data.data.username,
password: response.data.data.password,
lease_id: response.data.lease_id,
lease_duration: response.data.lease_duration,
renewable: response.data.renewable,
};
// Schedule lease renewal
if (creds.renewable) {
this.scheduleLeaseRenewal(creds.lease_id, creds.lease_duration);
}
console.log(`✅ Dynamic credentials generated for role ${role}`);
return creds;
} catch (error) {
console.error('❌ Failed to generate dynamic credentials:', error);
throw new Error(`Vault DB creds failed: ${error.message}`);
}
}
/**
* Encrypt data using Vault transit engine
*/
async encrypt(keyName: string, plaintext: string): Promise<string> {
try {
await this.ensureAuthenticated();
const response = await this.client.post(
`/v1/transit/encrypt/${keyName}`,
{ plaintext: Buffer.from(plaintext).toString('base64') },
{ headers: { 'X-Vault-Token': this.token } }
);
return response.data.data.ciphertext;
} catch (error) {
console.error('❌ Vault encryption failed:', error);
throw new Error(`Vault encrypt failed: ${error.message}`);
}
}
/**
* Decrypt data using Vault transit engine
*/
async decrypt(keyName: string, ciphertext: string): Promise<string> {
try {
await this.ensureAuthenticated();
const response = await this.client.post(
`/v1/transit/decrypt/${keyName}`,
{ ciphertext },
{ headers: { 'X-Vault-Token': this.token } }
);
return Buffer.from(response.data.data.plaintext, 'base64').toString();
} catch (error) {
console.error('❌ Vault decryption failed:', error);
throw new Error(`Vault decrypt failed: ${error.message}`);
}
}
/**
* Revoke lease (dynamic credentials)
*/
async revokeLease(leaseId: string): Promise<void> {
try {
await this.ensureAuthenticated();
await this.client.put(
'/v1/sys/leases/revoke',
{ lease_id: leaseId },
{ headers: { 'X-Vault-Token': this.token } }
);
console.log(`✅ Lease ${leaseId} revoked`);
} catch (error) {
console.error('❌ Lease revocation failed:', error);
}
}
private scheduleLeaseRenewal(leaseId: string, duration: number): void {
// Renew 5 minutes before expiry
const renewalTime = (duration - 300) * 1000;
const timeout = setTimeout(async () => {
try {
await this.ensureAuthenticated();
const response = await this.client.put(
'/v1/sys/leases/renew',
{ lease_id: leaseId },
{ headers: { 'X-Vault-Token': this.token } }
);
// Reschedule renewal
this.scheduleLeaseRenewal(leaseId, response.data.lease_duration);
} catch (error) {
console.error('❌ Lease renewal failed:', error);
}
}, renewalTime);
this.leases.set(leaseId, timeout);
}
private async ensureAuthenticated(): Promise<void> {
if (!this.token || this.tokenExpiry <= Date.now()) {
await this.authenticate();
}
}
/**
* Cleanup: revoke all leases
*/
async cleanup(): Promise<void> {
for (const [leaseId, timeout] of this.leases.entries()) {
clearTimeout(timeout);
await this.revokeLease(leaseId);
}
this.leases.clear();
this.secretCache.clear();
}
}
export default VaultClient;
Vault Usage Example
// Example: Using Vault in a ChatGPT app
import VaultClient from './vault-client';
async function initializeApp() {
const vault = new VaultClient({
address: 'https://vault.production.com',
roleId: process.env.VAULT_ROLE_ID!,
secretId: process.env.VAULT_SECRET_ID!,
});
await vault.authenticate();
// Get OpenAI API key
const secrets = await vault.getSecret('chatgpt-app/openai');
const openaiKey = secrets.api_key;
// Get dynamic database credentials (auto-expire in 1 hour)
const dbCreds = await vault.getDynamicDBCredentials('postgres-chatgpt-role');
// Encrypt user conversation
const conversation = "User's private conversation";
const encrypted = await vault.encrypt('conversations-key', conversation);
// Store encrypted conversation in database...
}
AWS Secrets Manager: Automatic Rotation & RDS Integration
AWS Secrets Manager provides managed secrets storage with automatic rotation, native AWS service integration, and pay-per-secret pricing. Unlike Vault's self-hosted model, Secrets Manager is fully managed by AWS, eliminating infrastructure overhead while providing seamless RDS/Aurora integration.
Why AWS Secrets Manager for ChatGPT Apps
Secrets Manager excels at managing database credentials with automatic rotation, storing third-party API keys with versioning, and integrating with AWS services (Lambda, ECS, RDS). For ChatGPT apps running on AWS infrastructure, Secrets Manager provides zero-configuration rotation for RDS PostgreSQL credentials, automatic failover during rotation, and IAM-based access control.
Key Secrets Manager features:
- Automatic rotation: Rotate RDS credentials every 30 days
- Native AWS integration: Lambda environment variables from secrets
- Versioning: AWSCURRENT, AWSPENDING staging labels
- Cross-region replication: Multi-region disaster recovery
- Fine-grained IAM: Resource-based policies per secret
Production AWS Secrets Manager Client
This implementation provides automatic caching, rotation handling, and multi-region failover:
// aws-secrets-client.ts - Production AWS Secrets Manager client
import {
SecretsManagerClient,
GetSecretValueCommand,
CreateSecretCommand,
UpdateSecretCommand,
RotateSecretCommand,
DescribeSecretCommand,
PutSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
interface SecretCacheEntry {
value: any;
expiry: number;
version: string;
}
interface RotationConfig {
automaticallyAfterDays: number;
lambdaArn: string;
}
class AWSSecretsClient {
private client: SecretsManagerClient;
private cache: Map<string, SecretCacheEntry> = new Map();
private readonly DEFAULT_CACHE_TTL = 300; // 5 minutes
constructor(region: string = 'us-east-1') {
this.client = new SecretsManagerClient({ region });
}
/**
* Get secret value with caching
*/
async getSecret(secretName: string, cacheTTL?: number): Promise<any> {
const ttl = cacheTTL ?? this.DEFAULT_CACHE_TTL;
// Check cache
const cached = this.cache.get(secretName);
if (cached && cached.expiry > Date.now()) {
console.log(`✅ Cache hit for ${secretName}`);
return cached.value;
}
try {
const command = new GetSecretValueCommand({
SecretId: secretName,
VersionStage: 'AWSCURRENT', // Get current version
});
const response = await this.client.send(command);
let secretValue: any;
if (response.SecretString) {
// JSON secret
secretValue = JSON.parse(response.SecretString);
} else if (response.SecretBinary) {
// Binary secret (certificates, keys)
secretValue = Buffer.from(response.SecretBinary).toString('base64');
} else {
throw new Error('Secret has no value');
}
// Cache secret
this.cache.set(secretName, {
value: secretValue,
expiry: Date.now() + ttl * 1000,
version: response.VersionId || 'unknown',
});
console.log(`✅ Retrieved secret ${secretName}`);
return secretValue;
} catch (error) {
console.error(`❌ Failed to get secret ${secretName}:`, error);
throw new Error(`Secrets Manager get failed: ${error.message}`);
}
}
/**
* Create new secret
*/
async createSecret(
name: string,
value: Record<string, any> | string,
description?: string
): Promise<string> {
try {
const secretString =
typeof value === 'string' ? value : JSON.stringify(value);
const command = new CreateSecretCommand({
Name: name,
Description: description,
SecretString: secretString,
});
const response = await this.client.send(command);
console.log(`✅ Created secret ${name}`);
return response.ARN!;
} catch (error) {
console.error(`❌ Failed to create secret ${name}:`, error);
throw new Error(`Secrets Manager create failed: ${error.message}`);
}
}
/**
* Update existing secret
*/
async updateSecret(
name: string,
value: Record<string, any> | string
): Promise<void> {
try {
const secretString =
typeof value === 'string' ? value : JSON.stringify(value);
const command = new UpdateSecretCommand({
SecretId: name,
SecretString: secretString,
});
await this.client.send(command);
// Invalidate cache
this.cache.delete(name);
console.log(`✅ Updated secret ${name}`);
} catch (error) {
console.error(`❌ Failed to update secret ${name}:`, error);
throw new Error(`Secrets Manager update failed: ${error.message}`);
}
}
/**
* Enable automatic rotation
*/
async enableRotation(
secretName: string,
config: RotationConfig
): Promise<void> {
try {
const command = new RotateSecretCommand({
SecretId: secretName,
RotationLambdaARN: config.lambdaArn,
RotationRules: {
AutomaticallyAfterDays: config.automaticallyAfterDays,
},
});
await this.client.send(command);
console.log(`✅ Enabled rotation for ${secretName}`);
} catch (error) {
console.error(`❌ Failed to enable rotation:`, error);
throw new Error(`Rotation config failed: ${error.message}`);
}
}
/**
* Get secret metadata (rotation status, last changed)
*/
async getSecretMetadata(secretName: string): Promise<any> {
try {
const command = new DescribeSecretCommand({
SecretId: secretName,
});
const response = await this.client.send(command);
return {
name: response.Name,
arn: response.ARN,
lastChanged: response.LastChangedDate,
lastRotated: response.LastRotatedDate,
rotationEnabled: response.RotationEnabled,
rotationLambdaARN: response.RotationLambdaARN,
versionIdsToStages: response.VersionIdsToStages,
};
} catch (error) {
console.error(`❌ Failed to get metadata:`, error);
throw new Error(`Metadata fetch failed: ${error.message}`);
}
}
/**
* Get specific secret version (for rollback)
*/
async getSecretVersion(
secretName: string,
versionId: string
): Promise<any> {
try {
const command = new GetSecretValueCommand({
SecretId: secretName,
VersionId: versionId,
});
const response = await this.client.send(command);
if (response.SecretString) {
return JSON.parse(response.SecretString);
} else if (response.SecretBinary) {
return Buffer.from(response.SecretBinary).toString('base64');
}
throw new Error('Secret version has no value');
} catch (error) {
console.error(`❌ Failed to get version ${versionId}:`, error);
throw new Error(`Version fetch failed: ${error.message}`);
}
}
/**
* Invalidate cache for secret
*/
invalidateCache(secretName: string): void {
this.cache.delete(secretName);
console.log(`✅ Cache invalidated for ${secretName}`);
}
/**
* Clear all cached secrets
*/
clearCache(): void {
this.cache.clear();
console.log('✅ All cached secrets cleared');
}
}
export default AWSSecretsClient;
RDS Automatic Rotation Setup
Enable automatic rotation for RDS PostgreSQL credentials:
// rds-rotation-setup.ts - Configure automatic RDS credential rotation
import AWSSecretsClient from './aws-secrets-client';
async function setupRDSRotation() {
const secrets = new AWSSecretsClient('us-east-1');
// Create RDS secret
await secrets.createSecret(
'chatgpt-app/rds/postgres',
{
engine: 'postgres',
host: 'chatgpt-db.cluster-xyz.us-east-1.rds.amazonaws.com',
username: 'chatgpt_app',
password: 'initial-password-will-be-rotated',
dbname: 'chatgpt_production',
port: 5432,
},
'ChatGPT app RDS PostgreSQL credentials'
);
// Enable automatic rotation every 30 days
await secrets.enableRotation('chatgpt-app/rds/postgres', {
automaticallyAfterDays: 30,
lambdaArn: 'arn:aws:lambda:us-east-1:123456789:function:SecretsManagerRDSPostgreSQLRotation',
});
console.log('✅ RDS rotation configured');
}
Environment Variables: .env Files & Injection Patterns
Environment variables provide runtime configuration without hardcoding secrets. While .env files are acceptable for local development, production deployments should inject secrets from Vault or AWS Secrets Manager into environment variables at runtime, preventing secret exposure in Docker images or git repositories.
Secure Environment Variable Loader
This implementation loads secrets from multiple sources with fallback hierarchy:
// env-loader.ts - Production environment variable loader
import * as fs from 'fs';
import * as path from 'path';
import VaultClient from './vault-client';
import AWSSecretsClient from './aws-secrets-client';
interface EnvConfig {
source: 'vault' | 'aws-secrets' | 'dotenv' | 'system';
vaultPath?: string;
awsSecretName?: string;
dotenvPath?: string;
required?: string[];
}
class EnvLoader {
private vault?: VaultClient;
private awsSecrets?: AWSSecretsClient;
private env: Record<string, string> = {};
async load(config: EnvConfig): Promise<void> {
switch (config.source) {
case 'vault':
await this.loadFromVault(config.vaultPath!);
break;
case 'aws-secrets':
await this.loadFromAWS(config.awsSecretName!);
break;
case 'dotenv':
this.loadFromDotenv(config.dotenvPath);
break;
case 'system':
this.loadFromSystem();
break;
}
// Validate required variables
if (config.required) {
this.validateRequired(config.required);
}
// Inject into process.env
Object.assign(process.env, this.env);
console.log(`✅ Loaded ${Object.keys(this.env).length} environment variables`);
}
private async loadFromVault(vaultPath: string): Promise<void> {
this.vault = new VaultClient({
address: process.env.VAULT_ADDR!,
roleId: process.env.VAULT_ROLE_ID!,
secretId: process.env.VAULT_SECRET_ID!,
});
await this.vault.authenticate();
const secrets = await this.vault.getSecret(vaultPath);
this.env = secrets;
}
private async loadFromAWS(secretName: string): Promise<void> {
this.awsSecrets = new AWSSecretsClient();
const secrets = await this.awsSecrets.getSecret(secretName);
this.env = secrets;
}
private loadFromDotenv(dotenvPath?: string): void {
const filePath = dotenvPath || path.join(process.cwd(), '.env');
if (!fs.existsSync(filePath)) {
throw new Error(`.env file not found: ${filePath}`);
}
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
// Skip comments and empty lines
if (!trimmed || trimmed.startsWith('#')) continue;
const [key, ...valueParts] = trimmed.split('=');
const value = valueParts.join('=').trim();
// Remove quotes
const unquoted = value.replace(/^["']|["']$/g, '');
this.env[key.trim()] = unquoted;
}
}
private loadFromSystem(): void {
this.env = { ...process.env } as Record<string, string>;
}
private validateRequired(required: string[]): void {
const missing = required.filter(key => !this.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
get(key: string, defaultValue?: string): string {
return this.env[key] || defaultValue || '';
}
getOrThrow(key: string): string {
const value = this.env[key];
if (!value) {
throw new Error(`Environment variable ${key} is required but not set`);
}
return value;
}
has(key: string): boolean {
return key in this.env;
}
getAll(): Record<string, string> {
return { ...this.env };
}
}
export default EnvLoader;
Multi-Source Environment Configuration
Load secrets from Vault in production, .env locally:
// config.ts - Multi-environment configuration
import EnvLoader from './env-loader';
async function initializeConfig() {
const loader = new EnvLoader();
if (process.env.NODE_ENV === 'production') {
// Production: Load from Vault
await loader.load({
source: 'vault',
vaultPath: 'chatgpt-app/production',
required: ['OPENAI_API_KEY', 'DATABASE_URL', 'JWT_SECRET'],
});
} else if (process.env.NODE_ENV === 'staging') {
// Staging: Load from AWS Secrets Manager
await loader.load({
source: 'aws-secrets',
awsSecretName: 'chatgpt-app/staging',
required: ['OPENAI_API_KEY', 'DATABASE_URL'],
});
} else {
// Local development: Load from .env
await loader.load({
source: 'dotenv',
dotenvPath: '.env.local',
required: ['OPENAI_API_KEY'],
});
}
return loader;
}
export default initializeConfig;
Secret Rotation: Automated Rotation & Zero-Downtime
Secret rotation prevents credential compromise by regularly generating new credentials and revoking old ones. Production systems require zero-downtime rotation, where new credentials activate before old credentials expire, ensuring continuous service availability.
Automated Secret Rotation Service
This service rotates database credentials, API keys, and encryption keys with zero downtime:
// secret-rotation-service.ts - Automated secret rotation
import VaultClient from './vault-client';
import AWSSecretsClient from './aws-secrets-client';
import { randomBytes } from 'crypto';
interface RotationStrategy {
type: 'database' | 'api-key' | 'encryption-key';
interval: number; // days
overlap: number; // seconds both old and new are valid
}
class SecretRotationService {
private vault: VaultClient;
private awsSecrets: AWSSecretsClient;
private rotationTimers: Map<string, NodeJS.Timeout> = new Map();
constructor(vault: VaultClient, awsSecrets: AWSSecretsClient) {
this.vault = vault;
this.awsSecrets = awsSecrets;
}
/**
* Schedule automatic rotation
*/
scheduleRotation(
secretName: string,
strategy: RotationStrategy
): void {
const intervalMs = strategy.interval * 24 * 60 * 60 * 1000;
const timer = setInterval(async () => {
await this.rotate(secretName, strategy);
}, intervalMs);
this.rotationTimers.set(secretName, timer);
console.log(`✅ Scheduled rotation for ${secretName} every ${strategy.interval} days`);
}
/**
* Rotate secret based on strategy
*/
private async rotate(
secretName: string,
strategy: RotationStrategy
): Promise<void> {
console.log(`🔄 Rotating ${secretName}...`);
try {
switch (strategy.type) {
case 'database':
await this.rotateDatabaseCredentials(secretName, strategy.overlap);
break;
case 'api-key':
await this.rotateAPIKey(secretName, strategy.overlap);
break;
case 'encryption-key':
await this.rotateEncryptionKey(secretName);
break;
}
console.log(`✅ Rotation complete for ${secretName}`);
} catch (error) {
console.error(`❌ Rotation failed for ${secretName}:`, error);
// Implement alerting (PagerDuty, Slack, etc.)
}
}
/**
* Rotate database credentials (PostgreSQL)
*/
private async rotateDatabaseCredentials(
secretName: string,
overlap: number
): Promise<void> {
// Get current credentials
const current = await this.vault.getSecret(secretName);
// Generate new password
const newPassword = randomBytes(32).toString('base64');
// Update database user password
const { Client } = require('pg');
const client = new Client({
host: current.host,
port: current.port,
database: current.database,
user: current.username,
password: current.password,
});
await client.connect();
await client.query(`ALTER USER ${current.username} WITH PASSWORD '${newPassword}'`);
await client.end();
// Wait overlap period (both passwords valid)
console.log(`⏳ Waiting ${overlap}s overlap period...`);
await new Promise(resolve => setTimeout(resolve, overlap * 1000));
// Update secret in Vault
await this.vault.writeSecret(secretName, {
...current,
password: newPassword,
rotated_at: new Date().toISOString(),
});
console.log(`✅ Database credentials rotated`);
}
/**
* Rotate API key (third-party service)
*/
private async rotateAPIKey(
secretName: string,
overlap: number
): Promise<void> {
// Generate new API key
const newApiKey = `sk_${randomBytes(32).toString('hex')}`;
// Store new key with PENDING version
await this.awsSecrets.updateSecret(`${secretName}-pending`, {
api_key: newApiKey,
status: 'PENDING',
created_at: new Date().toISOString(),
});
// Wait overlap period (both keys valid)
console.log(`⏳ Waiting ${overlap}s overlap period...`);
await new Promise(resolve => setTimeout(resolve, overlap * 1000));
// Promote PENDING to CURRENT
await this.awsSecrets.updateSecret(secretName, {
api_key: newApiKey,
status: 'CURRENT',
rotated_at: new Date().toISOString(),
});
console.log(`✅ API key rotated`);
}
/**
* Rotate encryption key (Vault transit)
*/
private async rotateEncryptionKey(keyName: string): Promise<void> {
// Vault transit engine supports key rotation
// Old key version remains for decryption, new version for encryption
const response = await this.vault.client.post(
`/v1/transit/keys/${keyName}/rotate`,
{},
{ headers: { 'X-Vault-Token': this.vault['token'] } }
);
console.log(`✅ Encryption key ${keyName} rotated to new version`);
}
/**
* Cancel scheduled rotation
*/
cancelRotation(secretName: string): void {
const timer = this.rotationTimers.get(secretName);
if (timer) {
clearInterval(timer);
this.rotationTimers.delete(secretName);
console.log(`✅ Rotation cancelled for ${secretName}`);
}
}
/**
* Cleanup all timers
*/
cleanup(): void {
for (const timer of this.rotationTimers.values()) {
clearInterval(timer);
}
this.rotationTimers.clear();
}
}
export default SecretRotationService;
Access Control: Zero-Trust & Least Privilege
Zero-trust security assumes no implicit trust—every secret access requires authentication, authorization, and audit logging. Implement least privilege access control where services receive only the minimum secrets required for their function, with time-limited credentials that auto-expire.
Zero-Trust Secret Authenticator
This authenticator validates service identity before granting secret access:
// zero-trust-authenticator.ts - Zero-trust secret access control
import * as jwt from 'jsonwebtoken';
import VaultClient from './vault-client';
interface ServiceIdentity {
serviceId: string;
namespace: string;
allowedSecrets: string[];
ttl: number; // seconds
}
interface AccessToken {
sub: string; // service ID
namespace: string;
secrets: string[];
iat: number;
exp: number;
}
class ZeroTrustAuthenticator {
private vault: VaultClient;
private jwtSecret: string;
constructor(vault: VaultClient, jwtSecret: string) {
this.vault = vault;
this.jwtSecret = jwtSecret;
}
/**
* Issue access token for service
*/
async issueToken(identity: ServiceIdentity): Promise<string> {
// Validate service exists in registry
const serviceConfig = await this.vault.getSecret(
`service-registry/${identity.serviceId}`
);
if (!serviceConfig) {
throw new Error(`Service ${identity.serviceId} not found in registry`);
}
// Validate requested secrets against allowed list
const unauthorized = identity.allowedSecrets.filter(
secret => !serviceConfig.allowed_secrets.includes(secret)
);
if (unauthorized.length > 0) {
throw new Error(`Unauthorized secrets: ${unauthorized.join(', ')}`);
}
// Generate JWT token
const token = jwt.sign(
{
sub: identity.serviceId,
namespace: identity.namespace,
secrets: identity.allowedSecrets,
} as AccessToken,
this.jwtSecret,
{
expiresIn: identity.ttl,
issuer: 'chatgpt-secrets-manager',
audience: 'chatgpt-services',
}
);
console.log(`✅ Issued token for ${identity.serviceId}`);
return token;
}
/**
* Validate token and authorize secret access
*/
async authorizeAccess(token: string, secretPath: string): Promise<boolean> {
try {
// Verify JWT
const decoded = jwt.verify(token, this.jwtSecret, {
issuer: 'chatgpt-secrets-manager',
audience: 'chatgpt-services',
}) as AccessToken;
// Check secret in allowed list
if (!decoded.secrets.includes(secretPath)) {
console.error(`❌ Service ${decoded.sub} not authorized for ${secretPath}`);
return false;
}
// Audit log
await this.auditLog(decoded.sub, secretPath, 'ACCESS_GRANTED');
return true;
} catch (error) {
console.error('❌ Token validation failed:', error);
return false;
}
}
private async auditLog(
serviceId: string,
secretPath: string,
action: string
): Promise<void> {
const logEntry = {
timestamp: new Date().toISOString(),
service_id: serviceId,
secret_path: secretPath,
action,
ip_address: 'internal', // Add real IP in production
};
// Store audit log in Vault or CloudWatch
await this.vault.writeSecret(
`audit-logs/${Date.now()}-${serviceId}`,
logEntry
);
}
}
export default ZeroTrustAuthenticator;
Secret Audit Logger
Track all secret access with comprehensive audit logging:
// audit-logger.ts - Secret access audit logging
import { CloudWatchLogsClient, PutLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs';
interface AuditEvent {
timestamp: string;
service_id: string;
secret_name: string;
action: 'READ' | 'WRITE' | 'DELETE' | 'ROTATE' | 'ACCESS_DENIED';
ip_address: string;
user_agent?: string;
success: boolean;
error_message?: string;
}
class SecretAuditLogger {
private cloudwatch: CloudWatchLogsClient;
private logGroupName: string;
private logStreamName: string;
constructor(region: string, logGroupName: string) {
this.cloudwatch = new CloudWatchLogsClient({ region });
this.logGroupName = logGroupName;
this.logStreamName = `secrets-audit-${new Date().toISOString().split('T')[0]}`;
}
async log(event: AuditEvent): Promise<void> {
try {
const command = new PutLogEventsCommand({
logGroupName: this.logGroupName,
logStreamName: this.logStreamName,
logEvents: [
{
timestamp: Date.now(),
message: JSON.stringify(event),
},
],
});
await this.cloudwatch.send(command);
console.log(`📝 Audit log: ${event.action} ${event.secret_name} by ${event.service_id}`);
} catch (error) {
console.error('❌ Audit logging failed:', error);
}
}
async logRead(serviceId: string, secretName: string, success: boolean): Promise<void> {
await this.log({
timestamp: new Date().toISOString(),
service_id: serviceId,
secret_name: secretName,
action: 'READ',
ip_address: 'internal',
success,
});
}
async logWrite(serviceId: string, secretName: string, success: boolean): Promise<void> {
await this.log({
timestamp: new Date().toISOString(),
service_id: serviceId,
secret_name: secretName,
action: 'WRITE',
ip_address: 'internal',
success,
});
}
async logRotation(secretName: string, success: boolean, errorMessage?: string): Promise<void> {
await this.log({
timestamp: new Date().toISOString(),
service_id: 'rotation-service',
secret_name: secretName,
action: 'ROTATE',
ip_address: 'internal',
success,
error_message: errorMessage,
});
}
}
export default SecretAuditLogger;
Conclusion: Building a Secure Secrets Foundation
Proper secrets management transforms ChatGPT app security from vulnerable to enterprise-grade. HashiCorp Vault provides dynamic secrets and encryption as a service, AWS Secrets Manager enables automatic rotation with zero AWS configuration, secure environment variable loading prevents hardcoded credentials, automated rotation eliminates long-lived secrets, and zero-trust access control enforces least privilege.
Production implementation checklist:
- ✅ Deploy Vault cluster with HA configuration
- ✅ Enable automatic rotation for all database credentials (30-day interval)
- ✅ Implement zero-trust authenticator with service identity validation
- ✅ Configure comprehensive audit logging (CloudWatch or Splunk)
- ✅ Remove all hardcoded secrets from codebase and Docker images
- ✅ Test rotation procedures with zero-downtime validation
- ✅ Establish secret recovery procedures for disaster scenarios
The code examples in this guide provide production-ready implementations—not simplified demos. Each example includes error handling, automatic retry logic, comprehensive logging, and security best practices validated in enterprise ChatGPT deployments.
Ready to secure your ChatGPT app's secrets? Start your free trial of MakeAIHQ and deploy ChatGPT apps with enterprise-grade secrets management—no security expertise required. Our platform automatically integrates with Vault and AWS Secrets Manager, implements automatic rotation, and provides zero-trust access control out of the box.
Related Resources
Internal Links:
- Authentication Deep Dive: OAuth 2.1, JWT & Session Management
- Encryption at Rest: Database, File Storage & Backup Encryption
- Zero-Trust Security Architecture for ChatGPT Apps
- Compliance Requirements: SOC 2, HIPAA, GDPR for ChatGPT Apps
- Infrastructure Security: VPC, Firewalls & Network Segmentation
- API Security: Rate Limiting, Input Validation & CORS
- Production Monitoring: CloudWatch, Datadog & Error Tracking
External Links:
Schema Markup (HowTo):
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Secrets Management: Vault, AWS Secrets Manager & Environment Variables",
"description": "Production-ready guide to implementing secure secrets management for ChatGPT apps using HashiCorp Vault, AWS Secrets Manager, and environment variables.",
"step": [
{
"@type": "HowToStep",
"name": "Implement HashiCorp Vault Client",
"text": "Create production Vault client with AppRole authentication, secret caching, dynamic credential generation, and lease management."
},
{
"@type": "HowToStep",
"name": "Configure AWS Secrets Manager",
"text": "Set up AWS Secrets Manager client with automatic caching, rotation handling, and RDS integration for database credentials."
},
{
"@type": "HowToStep",
"name": "Secure Environment Variables",
"text": "Build environment variable loader supporting Vault, AWS Secrets Manager, and .env files with validation."
},
{
"@type": "HowToStep",
"name": "Enable Automatic Rotation",
"text": "Implement zero-downtime secret rotation for database credentials, API keys, and encryption keys."
},
{
"@type": "HowToStep",
"name": "Enforce Zero-Trust Access",
"text": "Deploy zero-trust authenticator with service identity validation and comprehensive audit logging."
}
]
}