How to Integrate Munder-Difflin with External Services: A Complete Developer Guide
Munder-Difflin integrates with external services through a phase-2 integrations layer that uses an encrypted secret store, integration registry, and loopback broker to securely route HTTP requests from workers to any REST API.
The munder-difflin framework provides a secure, three-component architecture for connecting AI agents to third-party APIs. According to the chaitanyagiri/munder-difflin source code, agents call external services by routing requests through a local broker that handles authentication transparently—workers never handle raw credentials directly.
How the Munder-Difflin Integration Architecture Works
The integration system consists of three tightly-coupled components designed to separate configuration, secrets, and request execution.
Component Overview
| Component | Responsibility | Key File |
|---|---|---|
| Integration Registry | Stores metadata (ID, label, kind, base URL, auth type) without secrets | src/shared/integrations.ts |
| Encrypted Secret Store | Persists credentials encrypted with Electron's safeStorage |
src/main/integrations.ts |
| Loopback Integration Broker | Resolves URLs, injects auth headers, and forwards requests | src/main/integrationBroker.ts |
This separation ensures that secrets are never exposed to the renderer process or logged, while still allowing workers to make authenticated API calls seamlessly.
Registering a New Integration
You can register integrations either through the Settings UI or programmatically via the preload bridge. The UI presents built-in templates defined in src/shared/integrations.ts including GitHub, Linear, Stripe, and custom REST endpoints.
Programmatic Registration Example
// List built-in templates
const templates = await window.cth.integrationsTemplates();
// Create a custom REST integration for a fictional "Acme API"
const newRecord = {
id: 'acme',
label: 'Acme API',
kind: 'custom-rest',
baseUrl: 'https://api.acme.com',
authType: 'header',
authHeader: 'X-Api-Key',
enabled: true
};
const up = await window.cth.integrationsUpsert(newRecord);
if (!up.ok) throw new Error(up.error);
// Store the secret (never exposed to the renderer)
await window.cth.integrationsSetSecret({ id: 'acme', secret: 'my-super-secret-key' });
The integrationsUpsert call validates against the IntegrationRecord schema defined in src/shared/integrations.ts. The authType property determines which authentication mechanism the broker will use when building request headers.
Securing Credentials with the Encrypted Store
Secrets are handled exclusively in the main process through four operations implemented in src/main/integrations.ts:
setSecret— encrypts and stores credentials usingsafeStorage.encryptStringgetSecret— decrypts credentials only when needed for request forwardinghasSecret— checks for credential existence without decryptiondeleteSecret— removes credentials permanently
Critical security property: Secrets are decrypted only in src/main/integrations.ts and never transmitted to the renderer or exposed in logs. The authHeader field in the registry record specifies which header receives the decrypted secret value.
Making Authenticated Requests from Workers
Once an integration is enabled, workers invoke it through a local loopback URL pattern. The broker in src/main/integrationBroker.ts handles all authentication transparently.
Worker-Side Request Pattern
// In a worker script (e.g., a Claude Code skill)
async function fetchAcmeData() {
// The harness resolves /i/acme/... and injects X-Api-Key automatically
const resp = await fetch('http://localhost:3000/i/acme/v1/orders?status=open');
const data = await resp.json();
return data;
}
When the broker receives this request, it executes the following sequence from src/main/integrationBroker.ts and src/shared/integrations.ts:
- Lookup — calls
getRecord('acme')to obtainbaseUrl,authType, andauthHeader - URL resolution —
resolveUpstreamUrlsafely combines base URL with the supplied path, preventing path traversal and host switching attacks - Secret retrieval —
getSecretdecrypts the credential in the main process only - Header construction —
buildAuthHeadersgenerates the appropriate authentication headers - Request forwarding — performs the external HTTP call and returns the response to the worker
Managing and Removing Integrations
Disable integrations by setting enabled: false in the record, or remove them entirely to delete associated secrets.
// Disable without deleting secrets
await window.cth.integrationsUpsert({ ...existingRecord, enabled: false });
// Complete removal (also deletes stored secret)
await window.cth.integrationsRemove({ id: 'acme' });
The integrationsRemove call triggers cascading cleanup: the registry record is deleted from the config, and deleteSecret purges the encrypted credential from the secret store.
Key Implementation Files for Integration Development
| File | Purpose |
|---|---|
src/shared/integrations.ts |
IntegrationRecord schema, validation, resolveUpstreamUrl, buildAuthHeaders, INTEGRATION_TEMPLATES |
src/main/integrations.ts |
Registry CRUD, encrypted setSecret/getSecret/hasSecret/deleteSecret |
src/preload/index.ts |
IPC bridge exposing integrationsList, integrationsUpsert, integrationsSetSecret to renderer |
src/main/integrationBroker.ts |
Capability injection, HTTP request forwarding, main process broker |
Summary
- Three-layer architecture separates registry metadata, encrypted secrets, and request execution
- Programmatic control via
window.cth.integrations*methods in the preload bridge - Transparent authentication — workers use simple
/i/<id>/<path>URLs without handling credentials - Security guarantees implemented in
src/main/integrations.ts: encryption at rest, decryption only in main process, no renderer exposure - Built-in templates for common services (GitHub, Linear, Stripe) plus extensible custom REST support
Frequently Asked Questions
How does munder-difflin keep API secrets secure?
Secrets are encrypted using Electron's safeStorage module in src/main/integrations.ts. They are decrypted only when the broker needs to forward a request, and never exposed to renderer processes, worker threads, or log files. The setSecret and getSecret functions handle all cryptographic operations in the main process.
Can workers specify arbitrary URLs when calling integrations?
No. The resolveUpstreamUrl function in src/shared/integrations.ts constrains request URLs to the baseUrl registered in the integration record. This prevents path traversal attacks and ensures workers cannot switch to unauthorized hosts by manipulating the path parameter.
What authentication methods are supported?
The authType field in IntegrationRecord supports multiple schemes. The buildAuthHeaders helper generates appropriate headers based on authType and authHeader values. Common patterns include Bearer tokens, API keys in custom headers, and Basic authentication—all implemented without exposing raw credentials to workers.
How do I add support for a new service like Jira or Asana?
Define a new template in INTEGRATION_TEMPLATES within src/shared/integrations.ts, then register an integration with kind matching your template. The existing broker architecture handles arbitrary REST services without modification, provided you specify the correct baseUrl, authType, and authHeader fields.
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 →