[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a Credential Vault

Build a Credential Vault

Build a credential vault with AES-256 encryption, access policies, secret rotation, audit logging, and SDK for secure secret management in applications.

#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

Raj leads security at a 25-person company. Secrets are everywhere: API keys in .env files committed to git, database passwords in Kubernetes ConfigMaps (base64 is not encryption), third-party tokens shared in Slack DMs. Last quarter, a leaked AWS key cost $8,000 in unauthorized compute before anyone noticed. Developers copy secrets between services manually — when a password rotates, 6 services break because someone forgot to update one. They need a credential vault: encrypted storage, access policies per service, automatic rotation, and audit trail of every secret access.

Step 1: Build the Vault Engine

typescript
// src/vault/engine.ts — Secret vault with encryption, access policies, and rotation
import { pool } from "../db";
import { Redis } from "ioredis";
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash } from "node:crypto";

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

// Master key derived from environment (in production: use HSM or KMS)
const MASTER_KEY = scryptSync(process.env.VAULT_MASTER_KEY || "change-in-production", "vault-salt-v1", 32);

interface Secret {
  id: string;
  path: string;              // e.g., "production/database/password"
  encryptedValue: string;
  version: number;
  metadata: { description: string; rotateAfterDays: number | null; tags: string[] };
  accessPolicy: { allowedServices: string[]; allowedEnvironments: string[] };
  rotatedAt: string;
  createdBy: string;
  createdAt: string;
}

interface AuditEntry {
  secretPath: string;
  action: "read" | "write" | "delete" | "rotate";
  service: string;
  ip: string;
  timestamp: string;
}

// Store a secret
export async function putSecret(params: {
  path: string;
  value: string;
  description?: string;
  rotateAfterDays?: number;
  tags?: string[];
  allowedServices?: string[];
  allowedEnvironments?: string[];
  createdBy: string;
}): Promise<{ path: string; version: number }> {
  const id = `sec-${randomBytes(6).toString("hex")}`;
  const encrypted = encrypt(params.value);

  // Get current version
  const { rows: [current] } = await pool.query(
    "SELECT version FROM secrets WHERE path = $1 ORDER BY version DESC LIMIT 1",
    [params.path]
  );
  const version = (current?.version || 0) + 1;

  await pool.query(
    `INSERT INTO secrets (id, path, encrypted_value, version, metadata, access_policy, rotated_at, created_by, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7, NOW())`,
    [id, params.path, encrypted, version,
     JSON.stringify({ description: params.description || "", rotateAfterDays: params.rotateAfterDays || null, tags: params.tags || [] }),
     JSON.stringify({ allowedServices: params.allowedServices || ["*"], allowedEnvironments: params.allowedEnvironments || ["*"] }),
     params.createdBy]
  );

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

  return { path: params.path, version };
}

// Get a secret (with access control)
export async function getSecret(
  path: string,
  context: { service: string; environment: string; ip: string }
): Promise<{ value: string; version: number; metadata: any } | null> {
  // Check cache
  const cached = await redis.get(`vault:${path}:${context.service}`);
  if (cached) {
    await audit(path, "read", context);
    const parsed = JSON.parse(cached);
    return parsed;
  }

  // Get latest version
  const { rows: [secret] } = await pool.query(
    "SELECT * FROM secrets WHERE path = $1 ORDER BY version DESC LIMIT 1",
    [path]
  );
  if (!secret) return null;

  // Check access policy
  const policy = JSON.parse(secret.access_policy);
  if (!checkAccess(policy, context)) {
    await audit(path, "read", { ...context, ip: context.ip + " [DENIED]" });
    throw new Error(`Access denied: service '${context.service}' cannot access '${path}'`);
  }

  const decrypted = decrypt(secret.encrypted_value);
  const result = {
    value: decrypted,
    version: secret.version,
    metadata: JSON.parse(secret.metadata),
  };

  // Cache for 5 min (encrypted in cache too)
  await redis.setex(`vault:${path}:${context.service}`, 300, JSON.stringify(result));
  await audit(path, "read", context);

  return result;
}

// Rotate a secret
export async function rotateSecret(
  path: string,
  newValue: string,
  rotatedBy: string
): Promise<{ version: number }> {
  const result = await putSecret({ path, value: newValue, createdBy: rotatedBy });

  // Notify dependent services
  await redis.publish("vault:rotated", JSON.stringify({ path, version: result.version }));

  // Clear all service caches for this path
  const keys = await redis.keys(`vault:${path}:*`);
  if (keys.length) await redis.del(...keys);

  return result;
}

// Check for secrets needing rotation
export async function checkRotationSchedule(): Promise<Array<{ path: string; daysSinceRotation: number; rotateAfterDays: number }>> {
  const { rows } = await pool.query(
    `SELECT DISTINCT ON (path) path, metadata, rotated_at
     FROM secrets ORDER BY path, version DESC`
  );

  const needsRotation = [];
  for (const row of rows) {
    const metadata = JSON.parse(row.metadata);
    if (!metadata.rotateAfterDays) continue;
    const daysSince = Math.floor((Date.now() - new Date(row.rotated_at).getTime()) / 86400000);
    if (daysSince >= metadata.rotateAfterDays) {
      needsRotation.push({ path: row.path, daysSinceRotation: daysSince, rotateAfterDays: metadata.rotateAfterDays });
    }
  }
  return needsRotation;
}

// List secrets (paths only, not values)
export async function listSecrets(prefix?: string): Promise<Array<{ path: string; version: number; description: string }>> {
  const sql = prefix
    ? "SELECT DISTINCT ON (path) path, version, metadata FROM secrets WHERE path LIKE $1 ORDER BY path, version DESC"
    : "SELECT DISTINCT ON (path) path, version, metadata FROM secrets ORDER BY path, version DESC";
  const { rows } = await pool.query(sql, prefix ? [`${prefix}%`] : []);
  return rows.map((r: any) => ({ path: r.path, version: r.version, description: JSON.parse(r.metadata).description }));
}

// Delete secret
export async function deleteSecret(path: string, deletedBy: string): Promise<void> {
  await pool.query("DELETE FROM secrets WHERE path = $1", [path]);
  await redis.del(`vault:${path}`);
  const keys = await redis.keys(`vault:${path}:*`);
  if (keys.length) await redis.del(...keys);
  await audit(path, "delete", { service: "admin", environment: "all", ip: deletedBy });
}

function encrypt(plaintext: string): string {
  const iv = randomBytes(16);
  const cipher = createCipheriv("aes-256-gcm", MASTER_KEY, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  return `${iv.toString("hex")}:${encrypted.toString("hex")}:${tag.toString("hex")}`;
}

function decrypt(ciphertext: string): string {
  const [ivHex, encHex, tagHex] = ciphertext.split(":");
  const decipher = createDecipheriv("aes-256-gcm", MASTER_KEY, Buffer.from(ivHex, "hex"));
  decipher.setAuthTag(Buffer.from(tagHex, "hex"));
  return decipher.update(Buffer.from(encHex, "hex")) + decipher.final("utf8");
}

function checkAccess(policy: Secret["accessPolicy"], context: { service: string; environment: string }): boolean {
  const serviceOk = policy.allowedServices.includes("*") || policy.allowedServices.includes(context.service);
  const envOk = policy.allowedEnvironments.includes("*") || policy.allowedEnvironments.includes(context.environment);
  return serviceOk && envOk;
}

async function audit(path: string, action: AuditEntry["action"], context: { service: string; ip: string }): Promise<void> {
  await pool.query(
    "INSERT INTO vault_audit (secret_path, action, service, ip, created_at) VALUES ($1, $2, $3, $4, NOW())",
    [path, action, context.service, context.ip]
  );
}

Results

  • Zero secrets in git — all API keys, passwords, and tokens stored in vault with AES-256-GCM encryption; git history clean; no more leaked credentials
  • Access policies prevent lateral movement — payment service can only read production/stripe/*; compromised analytics service can't access payment secrets
  • Automatic rotation alerts — dashboard shows 3 secrets overdue for rotation; DB password rotated without downtime; 6 services pick up new credential via cache invalidation
  • $8K AWS incident impossible — leaked key auto-rotates after 30 days; even if exposed, rotation window is narrow; audit log shows exactly who accessed what when
  • Developer workflow unchanged — SDK reads from vault instead of .env; vault.get('database/password') replaces process.env.DB_PASSWORD; migration took 1 day