# How Apache Maka Manages API Keys and Secrets Locally: A Deep Dive into credential-store.ts and activation-secret-injector.ts

> Learn how Apache Maka securely manages API keys and secrets locally using credential-store.ts and activation-secret-injector.ts for safe injection and cleanup.

- Repository: [The Apache Software Foundation/maka](https://github.com/apache/maka)
- Tags: deep-dive
- Published: 2026-09-01

---

**Apache Maka stores API keys and secrets in a permission-hardened JSON file managed by `CredentialStore` in [`credential-store.ts`](https://github.com/apache/maka/blob/main/credential-store.ts), while `ActivationSecretInjector` from [`activation-secret-injector.ts`](https://github.com/apache/maka/blob/main/activation-secret-injector.ts) resolves and injects these secrets into sandboxed environments at runtime with automatic cleanup and redaction.**

Apache Maka handles sensitive credentials through a two-layer architecture that separates persistent storage from runtime injection. The [[`credential-store.ts`](https://github.com/apache/maka/blob/main/credential-store.ts)](https://github.com/apache/maka/blob/main/packages/storage/src/credential-store.ts) module provides durable, file-based storage with atomic writes and cross-process locking, while [[`activation-secret-injector.ts`](https://github.com/apache/maka/blob/main/activation-secret-injector.ts)](https://github.com/apache/maka/blob/main/packages/storage/src/activation-secret-injector.ts) manages the secure resolution and injection of these secrets into isolated execution contexts. Together, these components ensure that API keys, OAuth tokens, and bot secrets remain protected at rest and are only exposed to specific processes that require them.

## File-Based Secret Persistence in credential-store.ts

Maka persists secrets to a JSON file called [`credentials.json`](https://github.com/apache/maka/blob/main/credentials.json) located at the workspace root. The `createFileCredentialStore()` factory function returns a `FileCredentialStore` instance that encapsulates all read and write operations, ensuring that concurrent processes cannot corrupt the underlying file through a combination of atomic writes and filesystem-level locking.

### Atomic Writes and Cross-Process Locking

The store uses `withCredentialFileLock` (lines 23-42) to orchestrate exclusive access to the credentials file. This utility creates a temporary lock directory to guarantee that only one process can write at a time, preventing race conditions during parallel operations. Writes are performed atomically using `writeSecretFileAtomic`, which ensures that the existing file is never in a partially-written state.

### Schema Validation and Filesystem Security

To prevent data corruption from version mismatches, the store enforces a strict `CREDENTIAL_SCHEMA_VERSION` (currently `1`) that is validated on every read (lines 68-74). Unknown schema versions trigger a hard error, refusing to load potentially incompatible data. Filesystem permissions are hardened through `ensureSecretDir` and `writeSecretFileAtomic`: the JSON file receives **0600** permissions (owner read/write only), while the parent directory is set to **0700** (lines 44-57, 62-73).

### Typed Storage and CRUD Operations

Public APIs use the `CredentialKind` enum (e.g., `'api_key'`, `'bot_token'`, `'app_secret'`) to categorize secrets. Internally, the `toStoredKind()` function maps these to concise storage keys like `'apiKey'` and `'botAppSecret'` (lines 57-66, 57-77). The `getSecret`, `setSecret`, and `deleteSecret` methods perform read-modify-write cycles under the file lock. When deleting, specifying a kind removes only the matching `<slug>:<kind>` entry, while omitting the kind wipes all secrets for that slug (lines 94-100, 38-48).

### Compare-and-Set for Safe Concurrent Updates

The `compareAndSetSecret` method implements atomic compare-and-set (CAS) semantics (lines 69-86). It reads the current value, compares it to an expected baseline, and only commits the new value if they match. The operation returns whether the write succeeded and, if not, what the current value is. This mechanism enables safe token refreshes where multiple processes might attempt to update the same credential simultaneously.

## Runtime Secret Injection via activation-secret-injector.ts

When functions or sandboxes require secret access, Maka uses the `ActivationSecretInjector` class to resolve references and inject values into isolated environments. Unlike direct file access, this approach validates integrity before injection and guarantees cleanup after execution.

### Bulk Resolution of Secret References

The injector accepts an array of `ActivationSecretEnvironmentBinding` objects that reference stored secrets. It calls `store.resolveForActivation(...)` on the underlying `ManagedSecretStore` to fetch all required material in a single round-trip (lines 25-34). This batch resolution minimizes I/O overhead when activating contexts that require multiple API keys or tokens.

### Pre-Injection Validation

Before injection, the resolved material undergoes strict validation through `snapshotMaterial` and `sameReference` checks (lines 35-51). The system verifies string lengths, reference integrity, and type correctness. Any mismatch throws a `ManagedSecretError` immediately, preventing partially-resolved injections that could leave an environment with incomplete credentials.

### Sandboxed Environment Isolation

Secrets are injected via the `ActivationSecretSink` interface. The default implementation, `ActivationEnvironmentSecretSink`, writes values into a **fresh `process.env`-like object** rather than the host’s global `process.env` (lines 66-84, 88-95). This isolation ensures that secrets are only available to the specific sandboxed process and are not accidentally inherited by subsequent operations.

### Leasing, Cleanup, and Redaction

The `prepare()` method returns an `ActivationSecretInjectionLease` that must be released after use. Calling `release()` triggers the sink to restore the original environment state and clears the injector’s internal `#values` cache (lines 35-45, 90-96). Additionally, `PreparedInjectionHandle.redact()` scrubs secret values from log messages using `redactLiteralSecrets`, replacing sensitive strings with `[redacted]` (lines 31-34, 12-20).

## Implementation Examples

The following TypeScript examples demonstrate how to use the credential store and activation injector in practice.

### Storing and Retrieving Secrets

Create a store instance rooted at your workspace directory to persist API keys:

```typescript
const store = createFileCredentialStore('/path/to/workspace');

// Persist an API key for a service identified by its slug
await store.setSecret('my-service', 'api_key', 'sk-abcdef123456');

// Retrieve the stored key later
const apiKey = await store.getSecret('my-service', 'api_key');
console.log(apiKey); // 'sk-abcdef123456'

```

### Safe Token Refresh with Compare-and-Set

Use `compareAndSetSecret` to handle concurrent updates safely during token refresh:

```typescript
const result = await store.compareAndSetSecret(
  'my-service',
  'api_key',
  'sk-abcdef123456',      // expected current value
  'sk-newtoken987654',    // new value to write
);

if (!result.committed) {
  // Another process updated the key; current value is in result.currentValue
  console.log('Concurrent update detected, new value:', result.currentValue);
} else {
  console.log('Token refreshed successfully');
}

```

### Injecting Secrets into Sandboxed Environments

Resolve and inject secrets for a specific activation context:

```typescript
const injector = new ActivationSecretInjector(managedSecretStore);
const sandboxEnv = {}; // Fresh environment object

const prepared = await injector.prepare({
  context: activationContext,
  bindings: [
    {
      reference: { schemaVersion: 1, secretId: 'my-service:apiKey' },
      target: { kind: 'environment', name: 'MY_SERVICE_API_KEY' },
    },
  ],
  sink: new ActivationEnvironmentSecretSink(sandboxEnv),
});

// Secret is now available in the isolated environment
console.log(sandboxEnv.MY_SERVICE_API_KEY); // 'sk-abcdef123456'

// Cleanup when activation ends
await prepared.release();
// sandboxEnv.MY_SERVICE_API_KEY is now undefined

```

## Summary

- **CredentialStore** persists secrets to a JSON file at the workspace root with atomic writes, cross-process file locking, and strict schema versioning to prevent corruption.
- **Filesystem permissions** are hardened to 0600 for the credentials file and 0700 for its parent directory, ensuring only the owner can access secret material at rest.
- **Compare-and-set operations** enable safe concurrent updates, such as OAuth token refreshes, by only writing when the expected current value matches the stored value.
- **ActivationSecretInjector** resolves secret references in bulk, validates integrity before injection, and isolates secrets in sandboxed environment objects rather than the global process environment.
- **Automatic cleanup** via lease release restores the original environment state and clears internal caches, while redaction utilities prevent secret exposure in logs.

## Frequently Asked Questions

### How does Maka prevent concurrent processes from corrupting the credentials file?

Maka uses `withCredentialFileLock` (lines 23-42 in [`credential-store.ts`](https://github.com/apache/maka/blob/main/credential-store.ts)) to implement cross-process mutual exclusion. This utility creates a temporary lock directory before any write operation, ensuring that only one process can modify the [`credentials.json`](https://github.com/apache/maka/blob/main/credentials.json) file at a time. Combined with atomic write operations via `writeSecretFileAtomic`, this prevents partial writes and race conditions during concurrent access.

### What security measures protect secrets at rest?

The credential store enforces **0600** file permissions (owner read/write only) on the [`credentials.json`](https://github.com/apache/maka/blob/main/credentials.json) file and **0700** permissions on its parent directory (lines 44-57 and 62-73). Additionally, the store validates a `CREDENTIAL_SCHEMA_VERSION` on every read (lines 68-74), refusing to load files with unknown versions to prevent use of corrupted or outdated data.

### How does the activation injector prevent secret leakage between sandboxed executions?

The `ActivationSecretInjector` uses the `ActivationEnvironmentSecretSink` to write secrets into a **fresh `process.env`-like object** rather than the host's global environment (lines 66-84 in [`activation-secret-injector.ts`](https://github.com/apache/maka/blob/main/activation-secret-injector.ts)). When the lease is released, the sink restores the original environment state and the injector clears its internal `#values` cache (lines 90-96), ensuring secrets are not retained in memory or accessible to subsequent executions.

### Can Maka handle concurrent token refreshes without race conditions?

Yes. The `compareAndSetSecret` method in [`credential-store.ts`](https://github.com/apache/maka/blob/main/credential-store.ts) (lines 69-86) implements compare-and-set (CAS) semantics. It only writes the new value if the current stored value matches the expected baseline provided by the caller. If another process has updated the secret in the meantime, the operation returns the current value without overwriting, allowing the caller to retry with the updated baseline.