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.