How the Integration Broker Handles Secret Management and BYOK Keys
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)
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
getSecretfunction - Header injection and request forwarding
// 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)
Integration metadata stores only secret references, never values:
// 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. The integration record retains only the secretRef handle.
2. Worker Capability Grant
The main process issues a capability token to each worker:
// 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:
// 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】.
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 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:
// 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 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 |
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 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.
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 and related components, enabling feature toggles and status indicators while maintaining the security boundary that keeps secret values in the main process.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →