How Apache Maka's Credential Vault Protects API Keys and Secrets Locally

Apache Maka stores API keys and secrets in a secured, bounded JSON credential vault that enforces strict size limits, atomic disk writes, and type-safe validation to prevent data leakage or corruption.

The credential vault is the central mechanism in Apache Maka for protecting sensitive credentials locally. Unlike simple configuration files, it implements defense-in-depth through schema validation, filesystem hardening, and careful API design that prevents accidental exposure. This article examines the complete technical implementation as found in the Apache Maka source code.

How the Credential Vault Works

Vault Document Structure and Limits

The vault persists to a single file named credential-vault.json with a strictly defined schema. In packages/storage/src/runtime-policy/credential-vault-document.ts, the CredentialVaultDocument interface enforces:

  • Schema version locked to SCHEMA_VERSION = 1
  • Maximum entries capped at MAX_VAULT_ENTRIES = 2048
  • Document size limit of VAULT_DOCUMENT_MAX_BYTES = 2 MiB
  • Per-secret limit of MAX_SECRET_LENGTH = 64 KiB

Each entry contains a locator (hierarchical key), a generated credentialId, a revision number for optimistic concurrency, the encrypted secret, and an updatedAt timestamp. These limits prevent denial-of-service through excessive storage and ensure predictable memory usage.

Reading the vault follows a hardened path in packages/storage/src/runtime-policy/document-io.ts. The readBoundedJsonDocument function:

  1. Opens with O_NOFOLLOW — refuses to follow symbolic links
  2. Sets O_NONBLOCK — prevents blocking on special files
  3. Verifies the target is a regular file via fstat
  4. Enforces the 2 MiB size limit before parsing
  5. Validates UTF-8 encoding and JSON structure

Any violation throws RuntimePolicyStoreError, preventing malformed or malicious files from reaching the parser.

// From document-io.ts: readBoundedJsonDocument enforces these checks
// before JSON parsing begins. Errors are categorized and never leak
// secret material in exception messages.

Atomic Writes to Prevent Partial Secrets

The vault guarantees crash safety through atomic writes. When CredentialVaultDocumentOwner.set() commits changes:

// Simplified sequence from document-io.ts write path
const tmpPath = `${vaultPath}.${randomUUID()}.tmp`;
await fs.writeFile(tmpPath, serialized, { mode: 0o600 });
await fs.fsync(fd);           // Ensure data reaches physical storage
await fs.rename(tmpPath, vaultPath);  // Atomic visibility switch

This pattern ensures that:

  • Partial writes never appear — readers see only the complete old or new version
  • Temporary files use restrictive permissions (0o600: owner read/write only)
  • fsync flushes buffers — secrets survive system crashes

Stale temporary files are cleaned at startup via cleanupRuntimePolicyDocumentTemps to prevent secret leakage in crash artifacts.

Validation Pipeline Before Persistence

Every write passes through assertDocumentSize and assertCredentialInputSecretLimit in credential-vault-document.ts. The document is serialized to check its byte length against the 2 MiB limit. Individual secrets are validated against the 64 KiB limit. This double-checking catches oversized data before it reaches the atomic write stage.

Immutable Snapshots and Controlled Secret Access

The CredentialVaultDocumentOwner class exposes a snapshot API that returns deeply frozen objects:

// From credential-vault-document.ts - snapshot creation
const vaultSnapshot = {
  revision: document.revision,
  entries: document.entries.map(entry => ({
    credentialId: entry.credentialId,
    locator: entry.locator,
    revision: entry.revision,
    updatedAt: entry.updatedAt,
    // secret is EXCLUDED from snapshots
  })),
};

The raw secret field never appears in snapshots. Code that needs the actual credential must explicitly call credentialMaterial(), making secret access auditable and intentional.

Working with the Credential Vault API

Setting and Updating Secrets

import { CredentialVaultDocumentOwner } from '@maka/storage/runtime-policy';

const vault = new CredentialVaultDocumentOwner();

// Store a new API key
await vault.set('/path/to/workspace', {
  locator: { 
    scope: 'web_search', 
    provider: 'tavily', 
    kind: 'api_key' 
  },
  secret: process.env.MAKA_TAVILY_API_KEY ?? '',
  expected: null,  // null = create new, number = optimistic concurrency check
});

The locator uses a namespaced format: connection:<id>:<kind>, web_search:<provider>:api_key, or network_proxy:password. The vault enforces uniqueness on both locators and generated credentialId values.

Reading Without Exposing Secrets

const snapshot = await vault.read('/path/to/workspace');
console.log('Available credentials:', snapshot.entries.map(e => ({
  id: e.credentialId,
  type: e.locator.scope,
  lastModified: e.updatedAt,
})));
// Note: snapshot.entries[].secret does not exist

Deleting with Optimistic Concurrency

await vault.delete('/path/to/workspace', {
  expected: {
    locator: { scope: 'web_search', provider: 'tavily', kind: 'api_key' },
    credentialId: 'c0d3cafe-1234-5678-9abc-def012345678',
    revision: 3,  // Must match current revision or CredentialStaleResult
  },
});

The expected.revision requirement prevents lost updates in concurrent scenarios.

Key Implementation Files

File Responsibility
packages/storage/src/runtime-policy/credential-vault-document.ts CredentialVaultDocumentOwner class, schema definitions, validation logic, and snapshot creation
packages/storage/src/runtime-policy/document-io.ts Bounded JSON I/O, atomic writes with temp files, symlink protection, and startup cleanup
packages/storage/src/__tests__/runtime-policy-stores.test.ts Test coverage for size limits, atomicity guarantees, and secret lifecycle
packages/core/src/runtime-policy/credential-vault-codec.ts Serialization codec for runtime policy integration
apps/desktop/src/main/.../credential-store Desktop application integration point per workspace

Summary

  • Bounded storage: Hard limits on document size (2 MiB) and individual secrets (64 KiB) prevent abuse
  • Filesystem hardening: O_NOFOLLOW, regular file verification, and 0o600 permissions block symlink attacks and unauthorized access
  • Atomic durability: Temp-file-plus-rename pattern with fsync ensures crash-safe writes without partial secrets
  • Immutable snapshots: Deeply frozen read-only views exclude secret material by default
  • Optimistic concurrency: Revision numbers prevent race conditions during concurrent updates
  • Automatic cleanup: Startup removal of temp files eliminates stale secret leakage

Frequently Asked Questions

What happens if the credential vault file is corrupted or tampered with?

Any corruption — invalid JSON, UTF-8 encoding errors, schema violations, or size limit breaches — causes readBoundedJsonDocument to throw RuntimePolicyStoreError. The error messages intentionally exclude secret content. Administrators must restore from backup or reinitialize the vault.

Can multiple processes safely modify the credential vault concurrently?

While the atomic rename guarantees readers see complete writes, Apache Maka uses optimistic concurrency control rather than file locking. Each set or delete operation includes an expected revision; mismatches return CredentialStaleResult, allowing callers to retry with fresh state.

Why are secrets limited to 64 KiB?

The MAX_SECRET_LENGTH constant prevents accidental or malicious storage of large binary blobs that could exhaust disk space or memory. This accommodates all standard API keys, PEM certificates, and connection strings while rejecting inappropriate vault use.

Where does the credential vault store its files?

The vault resides at {workspaceRoot}/credential-vault.json. The CredentialVaultDocumentOwner methods accept the workspace path as their first argument, keeping vault location explicit and configurable per workspace rather than using hidden global directories.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →