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

> Secure your API keys and secrets locally with Apache Maka's credential vault. Discover how its atomic writes and type-safe validation prevent data leakage and corruption.

- Repository: [The Apache Software Foundation/maka](https://github.com/apache/maka)
- Tags: how-to-guide
- Published: 2026-09-02

---

**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`](https://github.com/apache/maka/blob/main/credential-vault.json) with a strictly defined schema. In [`packages/storage/src/runtime-policy/credential-vault-document.ts`](https://github.com/apache/maka/blob/main/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.

### Safe File Reading with Symlink Protection

Reading the vault follows a hardened path in [`packages/storage/src/runtime-policy/document-io.ts`](https://github.com/apache/maka/blob/main/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.

```typescript
// 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:

```typescript
// 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`](https://github.com/apache/maka/blob/main/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:

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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.