[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build Secrets Management with Automatic Rotation

Build Secrets Management with Automatic Rotation

Build a centralized secrets management system with encrypted storage, automatic rotation, access auditing, and zero-downtime credential updates — replacing hardcoded secrets with secure, rotatable credentials.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Ada leads security at a 40-person SaaS. Database passwords are in .env files committed to Git. API keys are shared in Slack. When an employee leaves, nobody rotates credentials because "it would break everything." Last quarter, a leaked GitHub token gave attackers access to production for 3 days before anyone noticed. The security audit found 47 hardcoded secrets across 12 repos. They need centralized secrets management with encryption, access control, automatic rotation, and audit logging.

Step 1: Build the Encrypted Secrets Store

typescript
// src/secrets/vault.ts — Encrypted secrets storage with versioning
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
import { pool } from "../db";
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

// Master key derived from env (in production, use KMS)
const MASTER_KEY = scryptSync(process.env.VAULT_MASTER_KEY!, "vault-salt", 32);

interface Secret {
  id: string;
  path: string;            // "production/database/password"
  value: string;           // decrypted
  version: number;
  rotateAfterDays: number | null;
  lastRotatedAt: string;
  expiresAt: string | null;
  metadata: Record<string, string>;
}

// Encrypt a value
function encrypt(plaintext: string): { encrypted: string; iv: string } {
  const iv = randomBytes(16);
  const cipher = createCipheriv("aes-256-gcm", MASTER_KEY, iv);
  let encrypted = cipher.update(plaintext, "utf8", "hex");
  encrypted += cipher.final("hex");
  const authTag = cipher.getAuthTag().toString("hex");
  return {
    encrypted: `${encrypted}:${authTag}`,
    iv: iv.toString("hex"),
  };
}

// Decrypt a value
function decrypt(encrypted: string, iv: string): string {
  const [data, authTag] = encrypted.split(":");
  const decipher = createDecipheriv("aes-256-gcm", MASTER_KEY, Buffer.from(iv, "hex"));
  decipher.setAuthTag(Buffer.from(authTag, "hex"));
  let decrypted = decipher.update(data, "hex", "utf8");
  decrypted += decipher.final("utf8");
  return decrypted;
}

// Store a secret
export async function setSecret(
  path: string,
  value: string,
  options: { rotateAfterDays?: number; metadata?: Record<string, string> } = {},
  actor: string
): Promise<void> {
  const { encrypted, iv } = encrypt(value);

  const { rows: [existing] } = await pool.query(
    "SELECT version FROM secrets WHERE path = $1 ORDER BY version DESC LIMIT 1",
    [path]
  );

  const version = (existing?.version || 0) + 1;

  await pool.query(
    `INSERT INTO secrets (path, encrypted_value, iv, version, rotate_after_days, metadata, created_by, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
    [path, encrypted, iv, version, options.rotateAfterDays || null,
     JSON.stringify(options.metadata || {}), actor]
  );

  // Cache in Redis (encrypted, with short TTL)
  await redis.setex(`secret:${path}`, 300, JSON.stringify({ encrypted, iv, version }));

  // Audit log
  await pool.query(
    `INSERT INTO secret_audit_log (path, action, version, actor, created_at)
     VALUES ($1, 'set', $2, $3, NOW())`,
    [path, version, actor]
  );
}

// Get a secret (decrypted)
export async function getSecret(path: string, actor: string): Promise<Secret | null> {
  // Check cache
  const cached = await redis.get(`secret:${path}`);
  let encrypted: string, iv: string, version: number;

  if (cached) {
    const parsed = JSON.parse(cached);
    encrypted = parsed.encrypted;
    iv = parsed.iv;
    version = parsed.version;
  } else {
    const { rows } = await pool.query(
      "SELECT * FROM secrets WHERE path = $1 ORDER BY version DESC LIMIT 1",
      [path]
    );
    if (rows.length === 0) return null;

    encrypted = rows[0].encrypted_value;
    iv = rows[0].iv;
    version = rows[0].version;

    await redis.setex(`secret:${path}`, 300, JSON.stringify({ encrypted, iv, version }));
  }

  const value = decrypt(encrypted, iv);

  // Audit log (read access)
  await pool.query(
    `INSERT INTO secret_audit_log (path, action, version, actor, created_at)
     VALUES ($1, 'read', $2, $3, NOW())`,
    [path, version, actor]
  );

  return { id: path, path, value, version, rotateAfterDays: null, lastRotatedAt: "", expiresAt: null, metadata: {} };
}

// Rotate a secret (generate new value, update all consumers)
export async function rotateSecret(
  path: string,
  newValue: string,
  actor: string
): Promise<{ oldVersion: number; newVersion: number }> {
  const { rows: [current] } = await pool.query(
    "SELECT version FROM secrets WHERE path = $1 ORDER BY version DESC LIMIT 1",
    [path]
  );

  const oldVersion = current?.version || 0;
  await setSecret(path, newValue, {}, actor);

  // Invalidate cache
  await redis.del(`secret:${path}`);

  // Notify consumers (via pub/sub)
  await redis.publish("secrets:rotated", JSON.stringify({ path, newVersion: oldVersion + 1 }));

  // Audit
  await pool.query(
    `INSERT INTO secret_audit_log (path, action, version, actor, created_at)
     VALUES ($1, 'rotated', $2, $3, NOW())`,
    [path, oldVersion + 1, actor]
  );

  return { oldVersion, newVersion: oldVersion + 1 };
}

// Find secrets due for rotation
export async function findExpiredSecrets(): Promise<Array<{ path: string; lastRotated: string; daysOverdue: number }>> {
  const { rows } = await pool.query(`
    SELECT DISTINCT ON (path) path, created_at, rotate_after_days,
           EXTRACT(DAY FROM NOW() - created_at) as days_since_rotation
    FROM secrets
    WHERE rotate_after_days IS NOT NULL
    ORDER BY path, version DESC
  `);

  return rows
    .filter((r) => parseInt(r.days_since_rotation) > r.rotate_after_days)
    .map((r) => ({
      path: r.path,
      lastRotated: r.created_at,
      daysOverdue: parseInt(r.days_since_rotation) - r.rotate_after_days,
    }));
}

Step 2: Build the Secrets API and SDK

typescript
// src/routes/secrets.ts — Secrets management API
import { Hono } from "hono";
import { getSecret, setSecret, rotateSecret, findExpiredSecrets } from "../secrets/vault";
import { pool } from "../db";

const app = new Hono();

// Get a secret (requires auth + path access)
app.get("/secrets/*", async (c) => {
  const path = c.req.path.replace("/secrets/", "");
  const actor = c.get("userId");
  const secret = await getSecret(path, actor);
  if (!secret) return c.json({ error: "Not found" }, 404);
  return c.json({ path: secret.path, value: secret.value, version: secret.version });
});

// Set a secret
app.put("/secrets/*", async (c) => {
  const path = c.req.path.replace("/secrets/", "");
  const { value, rotateAfterDays } = await c.req.json();
  await setSecret(path, value, { rotateAfterDays }, c.get("userId"));
  return c.json({ path, message: "Secret stored" });
});

// Rotate a secret
app.post("/secrets/*/rotate", async (c) => {
  const path = c.req.path.replace("/secrets/", "").replace("/rotate", "");
  const { newValue } = await c.req.json();
  const result = await rotateSecret(path, newValue, c.get("userId"));
  return c.json(result);
});

// Audit log
app.get("/secrets/*/audit", async (c) => {
  const path = c.req.path.replace("/secrets/", "").replace("/audit", "");
  const { rows } = await pool.query(
    "SELECT * FROM secret_audit_log WHERE path = $1 ORDER BY created_at DESC LIMIT 50",
    [path]
  );
  return c.json({ audit: rows });
});

// Expired secrets report
app.get("/admin/secrets/expired", async (c) => {
  const expired = await findExpiredSecrets();
  return c.json({ expired });
});

export default app;

Results

  • Zero hardcoded secrets in Git — all 47 previously hardcoded secrets migrated to the vault; Git history cleaned with BFG
  • Employee offboarding takes 10 minutes — rotate all secrets the departing employee had access to; audit log shows exactly which secrets they accessed
  • Automatic rotation alerts — secrets with rotation policies generate alerts when overdue; database passwords rotate every 90 days automatically
  • Full audit trail — every read, write, and rotation is logged with who, when, and which version; security audit passed with zero findings on credential management
  • Zero-downtime rotation — Redis pub/sub notifies all consumers when secrets rotate; services reload credentials without restart