# How the Integration Broker Handles Secret Management and BYOK Keys

> Discover how the Integration Broker securely manages secret management and BYOK keys, injecting credentials at request time without exposing them to workers or logs.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: how-to-guide
- Published: 2026-08-20

---

**The Integration Broker is a loopback-only HTTP proxy that materializes and injects credentials at request time, ensuring BYOK keys and other secrets are never exposed to workers or logged.**

The **Integration Broker** in `chaitanyagiri/munder-difflin` provides a secure, zero-knowledge pathway for workers to authenticate with external services. This architecture treats **BYOK (Bring-Your-Own-Key)** API keys identically to other integration secrets—encrypted at rest, decrypted only by the main process, and injected into outbound requests without ever crossing process boundaries.

## Core Architecture Components

### Integration Broker ([`src/main/integrationBroker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/integrationBroker.ts))

The broker is the sole component with access to decrypted secrets. It operates as a **loopback-only HTTP server** bound to `127.0.0.1`, preventing any external network exposure.

Key responsibilities:

- **Capability token issuance** — Workers receive random handles via `grant()`, not secrets
- **Constant-time token validation** using `timingSafeEqual`
- **Lazy secret decryption** via injected `getSecret` function
- **Header injection and request forwarding**

```typescript
// Broker initialization with dependency injection
const broker = new IntegrationBroker({
  getRecord: getIntegrationRecord,  // fetches IntegrationRecord
  getSecret: decryptSecret          // decrypts from integration-secrets.json
});

await broker.start();  // binds to OS-assigned port on 127.0.0.1

```

The `start()` method binds to a random port on the loopback interface【src/main/integrationBroker.ts#L87-L99】. Tokens are minted with `grant()` and tracked in-memory via `byToken` and `byWorker` maps【src/main/integrationBroker.ts#L26-L35】. Token lookup employs `timingSafeEqual` to prevent timing attacks【src/main/integrationBroker.ts#L44-L51】.

### Integration Record Schema ([`src/shared/integrations.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/integrations.ts))

Integration metadata stores only **secret references**, never values:

```typescript
// Secret reference creation
secretRefFor(id: string): string {
  return `int:${id}`;  // e.g., "int:openai"
}

```

The `authTypeNeedsSecret()` function determines whether decryption is required【src/shared/integrations.ts#L95-L98】—returning `true` for all auth types except `'none'`.

## Secret Flow: From Registration to Injection

### 1. Registration and Encrypted Storage

When a user registers a BYOK integration (e.g., OpenAI API key), the value is encrypted and stored in [`integration-secrets.json`](https://github.com/chaitanyagiri/munder-difflin/blob/main/integration-secrets.json). The integration record retains only the `secretRef` handle.

### 2. Worker Capability Grant

The main process issues a **capability token** to each worker:

```typescript
// Main process grants scoped access
const token = await broker.grant(workerId, ['openai', 'custom-api']);

```

This token is a random string—never a secret—stored only in the broker's memory.

### 3. Broker Request Handling

Workers address requests to the loopback endpoint:

```

http://127.0.0.1:<port>/i/<integrationId>/<path>
Authorization: Bearer <capability-token>

```

The broker validates the token, checks authorization scope, and proceeds to secret resolution.

### 4. Lazy Decryption and Header Injection

Secrets are decrypted **only when forwarding**:

```typescript
// From integrationBroker.ts - secret retrieval
if (authTypeNeedsSecret(rec.authType)) {
  secret = await this.deps.getSecret(rec.secretRef);  // L102-L107
}

```

The `buildAuthHeaders()` function constructs appropriate authentication headers, with the decrypted secret merged immediately before the outbound request【src/main/integrationBroker.ts#L102-L107】.

```typescript
private async forward(
  req: IncomingMessage,
  res: ServerResponse,
  rec: IntegrationRecord,
  upstream: URL,
  secret: string | undefined
) {
  const injected = buildAuthHeaders(rec.authType, rec.authHeader, secret);
  Object.assign(outHeaders, injected);  // secret injected here only
  // ... forward and stream response
}

```

## BYOK-Specific Security Guarantees

### Main Process Exclusive Decryption

As noted in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) near line 4036, **BYOK keys are owned by the main process** and never transmitted across IPC boundaries. Workers cannot access these values through any API.

### Boolean Presence Flag Pattern

The renderer and worker processes detect BYOK availability through **presence-only IPC calls**:

```typescript
// Renderer store pattern (src/renderer/src/store/store.ts)
const hasOpenAiKey: boolean = await ipcRenderer.invoke('realtimeHasOpenAiKey');
// Returns true/false only — never the key value

```

This enables UI components like [`AiEnginesSettings.tsx`](https://github.com/chaitanyagiri/munder-difflin/blob/main/AiEnginesSettings.tsx) to conditionally enable features without secret exposure.

### authType Mapping for BYOK Services

BYOK integrations typically use:

| Service | `authType` | Header Construction |
|---------|-----------|---------------------|
| OpenAI | `'bearer'` | `Authorization: Bearer <api-key>` |
| Custom REST | `'header'` | Custom header name with secret value |

The broker's `buildAuthHeaders()` handles both patterns, ensuring the decrypted key reaches the upstream service correctly formatted.

## Security Properties Summary

| Threat | Mitigation |
|--------|-----------|
| Secret logging by workers | Workers never receive secrets; only capability tokens |
| Network sniffing | Loopback-only binding (`127.0.0.1`) |
| Timing attacks on tokens | `timingSafeEqual` comparison |
| Secret exposure in crashes | In-memory tokens; no secret in worker heap |
| Unauthorized integration access | Per-worker allowlists in `grant()` |
| Storage compromise | AES-encrypted [`integration-secrets.json`](https://github.com/chaitanyagiri/munder-difflin/blob/main/integration-secrets.json) |

## Summary

- **Zero-knowledge workers**: Workers operate with capability tokens, never secret values
- **Loopback-only broker**: Network isolation prevents external secret access
- **Lazy decryption**: Secrets materialize only at request forwarding time
- **BYOK equivalence**: User-provided API keys receive identical protection to system-managed secrets
- **Presence-only visibility**: UI and workers detect key availability through boolean flags, never values

## Frequently Asked Questions

### How does the broker prevent workers from reading BYOK keys?

Workers receive **capability tokens**—random strings that authorize broker access for specific integrations. The actual secret decryption occurs inside the broker's `forward()` method via the injected `getSecret` dependency. Workers cannot invoke this function directly, and no IPC endpoint exposes secret values. As the source notes, "BYOK keys are owned by the main process and only injected by the broker."

### What encryption protects secrets at rest?

Secrets are stored in [`integration-secrets.json`](https://github.com/chaitanyagiri/munder-difflin/blob/main/integration-secrets.json) using the system's encrypted keychain or a passphrase-derived key. The broker receives a `decryptSecret` function at initialization that handles keychain access. The encryption algorithm and key management are abstracted behind this dependency interface in [`src/main/integrationBroker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/integrationBroker.ts).

### Can workers request arbitrary integrations through the broker?

No. The `grant(workerId, allowedIds)` method in `src/main/integrationBroker.ts#L26-L35` binds each capability token to an explicit allowlist of integration IDs. The broker validates every request against this scope before secret retrieval. A worker with `['openai']` scope cannot access `custom-api` integrations.

### How does the UI know whether a BYOK key is configured without accessing it?

The renderer process uses IPC methods like `realtimeHasOpenAiKey` that return **boolean values only**. This pattern appears in [`src/renderer/src/store/store.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/store.ts) and related components, enabling feature toggles and status indicators while maintaining the security boundary that keeps secret values in the main process.