Skip to content

Vault Audit Logging ​

Track who changed what in your vault, when, and from where. The audit log provides a tamper-evident trail for compliance and debugging.

Quick Start ​

ts
import { logAuditEntry, queryAuditLog, verifyAuditLog } from "nevr-env/vault";

// Entries are logged automatically when using vault operations
// But you can also log custom entries:
logAuditEntry("secret.rotate", {
  type: "secret",
  secretKeys: ["API_KEY"],
});

// Query the log
const entries = queryAuditLog({
  action: "secret.rotate",
  fromDate: new Date("2024-01-01"),
});

// Verify integrity
const result = verifyAuditLog();
console.log(result.valid); // true if no tampering detected

How It Works ​

The audit log uses a blockchain-like structure where each entry contains:

  • A SHA-256 hash of its contents
  • A reference to the previous entry's hash

This creates a tamper-evident chain - if anyone modifies an old entry, the chain breaks.

Audit Actions ​

ActionDescription
vault.pushLocal .env pushed to vault
vault.pullVault pulled to local .env
vault.syncBidirectional sync
vault.diffDiff viewed
secret.addNew secret added
secret.updateSecret value changed
secret.removeSecret removed
secret.rotateSecret rotated
key.generateNew encryption key generated
key.rotateEncryption key rotated
access.grantedVault access granted
access.deniedVault access denied
config.changeConfiguration changed

Querying the Log ​

By Action ​

ts
import { queryAuditLog } from "nevr-env/vault";

// Single action
const pushes = queryAuditLog({ action: "vault.push" });

// Multiple actions
const secretChanges = queryAuditLog({
  action: ["secret.add", "secret.update", "secret.remove"],
});

By Actor ​

ts
const byUser = queryAuditLog({ actor: "john.doe" });

By Secret Key ​

ts
const apiKeyHistory = queryAuditLog({ secretKey: "API_KEY" });

By Date Range ​

ts
const lastWeek = queryAuditLog({
  fromDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
  toDate: new Date(),
});

With Limit ​

ts
const recent = queryAuditLog({ limit: 10 });

Audit Summary ​

Get a high-level overview:

ts
import { getAuditSummary } from "nevr-env/vault";

const summary = getAuditSummary({
  from: new Date("2024-01-01"),
  to: new Date("2024-12-31"),
});

// {
//   totalEntries: 150,
//   byAction: {
//     "vault.push": 45,
//     "vault.pull": 80,
//     "secret.rotate": 25,
//   },
//   byActor: {
//     "john.doe": 100,
//     "ci-service": 50,
//   },
//   secretsModified: ["DATABASE_URL", "API_KEY", "STRIPE_KEY"],
//   firstEntry: "2024-01-15T10:30:00Z",
//   lastEntry: "2024-12-20T14:45:00Z",
// }

Verifying Integrity ​

ts
import { verifyAuditLog } from "nevr-env/vault";

const result = verifyAuditLog();

if (!result.valid) {
  console.error("Audit log has been tampered with!");
  for (const error of result.errors) {
    console.error(`Entry ${error.index}: ${error.error}`);
  }
}

Exporting the Log ​

ts
import { exportAuditLog } from "nevr-env/vault";

// JSON format
const json = exportAuditLog("json");

// CSV format (for spreadsheets)
const csv = exportAuditLog("csv");

// Plain text (human readable)
const text = exportAuditLog("plaintext");

Log Rotation ​

Prevent the log from growing too large:

ts
import { rotateAuditLog } from "nevr-env/vault";

const result = rotateAuditLog({
  keepEntries: 1000, // Keep last 1000 entries
  archivePath: ".nevr-env.audit.2024.archive",
});

console.log(`Archived ${result.archived} entries`);
console.log(`Remaining ${result.remaining} entries`);

Audit Entry Structure ​

ts
interface AuditEntry {
  id: string;           // Unique entry ID
  timestamp: string;    // ISO timestamp
  action: AuditAction;  // What happened
  actor: {
    name: string;       // Username or service
    email?: string;     // Email if available
    type: "user" | "service" | "ci" | "unknown";
    machine?: string;   // Hostname
  };
  target: {
    type: "vault" | "secret" | "key" | "config";
    vaultPath?: string;
    secretKeys?: string[];  // Keys only, never values!
    beforeHash?: string;
    afterHash?: string;
  };
  context: {
    environment?: string;  // NODE_ENV
    branch?: string;       // Git branch
    commit?: string;       // Git commit
    ciRunId?: string;      // CI run ID
  };
  previousHash?: string;   // Link to previous entry
  hash: string;            // This entry's hash
}

CI/CD Integration ​

The audit log automatically captures CI context:

yaml
# GitHub Actions
- name: Pull from vault
  run: npx nevr-env vault pull
  env:
    NEVR_ENV_KEY: ${{ secrets.NEVR_ENV_KEY }}
# Audit entry will include:
# - actor: github-actions (from GITHUB_ACTOR)
# - context.branch: main (from GITHUB_REF_NAME)
# - context.commit: abc123 (from GITHUB_SHA)
# - context.ciRunId: 12345 (from GITHUB_RUN_ID)

Best Practices ​

1. Regular Integrity Checks ​

ts
// Add to your CI pipeline
const result = verifyAuditLog();
if (!result.valid) {
  process.exit(1);
}

2. Archive Old Entries ​

ts
// Run monthly
rotateAuditLog({ keepEntries: 5000 });

3. Monitor for Anomalies ​

ts
const summary = getAuditSummary({ from: lastWeek });

// Alert if unusual activity
if (summary.byAction["access.denied"] > 10) {
  sendAlert("Multiple denied access attempts detected");
}

4. Commit the Audit Log ​

Unlike .env, the audit log is safe to commit:

gitignore
# .gitignore
.env
.nevr-env.vault  # Optional: commit if sharing secrets
.nevr-env.audit.log  # Safe to commit for audit trail

Released under the MIT License.