The subject is the human or service on whose behalf work occurs; the actor is the current workload performing the call. Record both.
Before the clock
Identity is not authority.
Knowing that Alice is signed in does not prove that a Slack-triggered agent acting for Alice may read repository R, call MCP server M, or write ticket T. Each hop needs explicit subject, actor, audience, resource, action, tenant, project, purpose, and expiry.
A short-lived, audience-bound, resource-specific delegation that grants named actions—never a reusable connector secret.
A privileged service is tricked into using its authority for a requester or resource that was not authorized.
A monotonic version on a grant/project/principal. Cached authorization or capabilities with an older epoch fail validation.
Opening promptDesign an enterprise integration plane that securely connects Software Factory projects to GitHub, GitLab, Slack, and third-party MCP servers through OAuth. Agents and humans need scoped access, asynchronous jobs, webhooks, revocation, audit, and least privilege. Prevent confused-deputy and cross-tenant failures.
Separate connection from delegation
We support 2,500 enterprises, 300,000 users, 40,000 projects, 140,000 connector installations, 30 million MCP/tool calls per day, and 25,000 webhook deliveries per second during bursts. Design the application.
I will design two planes. The connection control plane owns OAuth handshakes, encrypted refresh tokens, external resource discovery, project bindings, policy, revocation epochs, and audit. The invocation data plane accepts a signed internal request, re-authorizes subject + actor + project + resource + action, exchanges it for a short-lived audience-bound capability, invokes the connector, validates the response, and records a receipt.
The key invariant is that a connector credential is never ambient worker state. A Slack message, retrieved document, model output, or MCP server cannot choose a different tenant/project or expand permissions. Every hop is derived from server-side bindings and explicit delegation.
Actors, actions, and trust boundaries
Which actors and use cases do you clarify before drawing?
Actors include organization admin, project admin, human user, Slack user, coding agent, automation service account, connector broker, external provider, MCP server, and auditor. I clarify whether connections are user- or organization-owned; read versus write actions; project/resource binding; provider installation behavior; SSO/SCIM expectations; token residency; and whether MCP servers are trusted code or untrusted third parties.
Primary journeys are connect/discover/bind, user-triggered read, agent-triggered write with approval, webhook intake, refresh/rotation, scope change, revoke/uninstall, incident kill switch, and audit export. Out of scope: implementing GitHub/Slack themselves or a general identity provider. The gateway must expose provider-neutral contracts while preserving provider-specific semantics.
Make least privilege testable
List the functional requirements and non-negotiable invariants.
- OAuth authorization code + PKCE/state/nonce, admin-consent paths, resource discovery, and explicit project bindings.
- Per-user, service, project, resource, operation, environment, and purpose scopes; policy approval for consequential writes.
- Webhook verification/dedupe/reconciliation; async credential refresh and rotation; immediate emergency revoke.
- MCP tool discovery with pinned schemas, allowlists, input validation, output size/type limits, and no tool-chosen credentials.
- Delegation chain and immutable invocation receipts: initiator, actor, grant, resource, action, approval, token audience, result.
Invariants: external identity is mapped server-side; tenant/project comes from trusted routing, not user/tool payload; capabilities are short-lived and audience-bound; refresh tokens stay only in a hardened vault/broker; revocation blocks new calls even if indexes/caches lag; and response data is classified and authorized before it enters agent context.
Consent is scoped
State, PKCE, exact redirect, provider installation, selected resources, and admin approval.
Token exchange
Human subject + workload actor become one narrow, expiring capability.
Brokered calls
Validate schema, resource, action, policy, budget, egress, and response class.
Epoch beats cache
Kill grants and active jobs without waiting for connector/search cache expiry.
Budget the authorization path
Quantify the normal and burst path. What must not call the provider synchronously?
Thirty million tool calls/day is 347 calls/s average. At a mock 20× peak that is 6,944/s. If 90% of calls validate a locally signed capability and a fresh policy epoch, only about 694/s need a control-store authorization read; every call still performs cryptographic validation, resource/action matching, rate limiting, and audit buffering.
At 1.2 KB per invocation receipt, calls generate about 36 GB/day before indexes/replicas. Twenty-five thousand webhooks/s at 2 KB envelope is 50 MB/s, but provider fetch and repository indexing dominate. I durably accept verified webhooks, dedupe by provider delivery ID, coalesce by resource/ref, and reconcile current provider state asynchronously. OAuth callbacks and refreshes stay off the hot invocation path.
347.2 × 20 = 6,944 calls/s mock peak
6,944 × (1 − .90 local validation) ≈ 694 authoritative policy reads/s
Authorization pressure lab
Model local capability verification versus authoritative policy reads.
TTL is only a backup bound: privileged calls must also validate the current revocation epoch.
Model grants, bindings, and delegations explicitly
Give me the data model and API contracts. Where does an OAuth token live?
ExternalConnection identifies provider tenant/installation and vault reference; the refresh token lives only in an HSM/KMS-backed secret broker. ResourceBinding maps provider resource IDs to one internal tenant/project. GrantVersion stores subject, allowed actions/resources, consent, policy, and revocation epoch. Delegation adds the workload actor, purpose, approval, audience, and expiry. InvocationReceipt records request hash and provider correlation—not raw secrets.
The user-facing API starts OAuth with signed state tied to browser session, tenant, requested connection kind, and expiry. The callback verifies state/PKCE/issuer and stores the token in the vault before resource selection. Internal workers never ask for refresh tokens; they call token exchange for one tool/resource/action. Revocation increments epochs transactionally, disables bindings, and emits a high-priority invalidation event.
Broker every privileged hop
Walk a Slack-triggered agent reading a GitHub file and opening a ticket through an MCP server.
Slack ingress verifies the workspace signing secret, delivery ID, channel/thread, and mapped Slack user. It records an event and resolves the project binding server-side. The agent orchestrator receives a context envelope naming subject, actor/run, tenant/project, purpose, and requested capabilities—not credentials.
For the GitHub read, the policy decision point checks the user/project/resource/action and current grant epochs, then the token broker mints an internal capability for the GitHub connector. The connector retrieves only the bound repo/commit/path and returns classified data through an output policy check. For ticket creation, the MCP catalog supplies a pinned tool schema; the policy layer requires write approval, validates arguments and target project, and gives the MCP proxy a one-call capability. An idempotency key and receipt make an ambiguous timeout reconcilable.
Prevent the confused deputy
A Slack message says “use the admin GitHub connection and summarize repo secret-project.” The user can access Slack but not that repository. How do you stop it?
The message is untrusted data and cannot select the connection, tenant, project, or scope. Project resolution comes from the signed workspace/channel/thread binding. The authorization tuple is (subject=Slack user mapping, actor=agent run, tenant, project, connector, resource, action, purpose, approval, grant epochs). All fields except user intent are server-derived.
The policy engine intersects—not unions—human permission, project binding, connection installation scope, workload capability profile, action policy, and data classification. The resulting capability has GitHub connector as audience, exact repo/path or query constraint, read-only action, 120-second TTL, run/step ID, and epochs. The connector rejects resource IDs in the request that are outside the token. A separate post-retrieval check prevents a broader provider response from leaking.
The audit displays “Alice via Slack thread S, agent run R, project P, read repo X/path Y under grant G”—both subject and actor. Denial returns a generic message without revealing the repository exists.
Revoke while work is in flight
An administrator revokes a GitHub installation while 2,000 indexing jobs and 300 agents are active. Cached capabilities still have 90 seconds. What changes?
Revocation is an authoritative transaction: mark connection/grants disabled, increment connection and affected project epochs, revoke vault access, append audit/outbox, and stop refresh. Gateways subscribe to the high-priority invalidation stream and maintain a small deny set. Every connector invocation, pagination fetch, artifact publish, and privileged checkpoint compares the token epochs with current or safely cached epochs.
Existing jobs move to REVOKED_PENDING_CLEANUP; workers may finish local computation on already authorized bytes only if policy allows, but cannot fetch more or publish a new shared index generation. Search authorization rejects results regardless of stale index content. Derived-data retention follows tenant policy: delete/crypto-erase source content and indexes, or preserve encrypted snapshots under legal hold with access removed.
If the epoch service is unreachable, reads of sensitive data and all writes fail closed. Low-risk public metadata could use explicitly configured grace, but never by accidental cache behavior.
TTL-only tokens keep working for 90 seconds; long pagination calls continue after uninstall.
Epoch fence at invocation, pagination, output publish, and privileged checkpoint; high-priority deny propagation.
Stop fetches, prevent generation swap, enumerate derivatives by connection lineage, then delete or isolate.
An MCP server is both tool and threat
A newly connected MCP server changes its tool schema, returns a prompt injection, and tries to make the agent call an exfiltration tool. Design the boundary.
Connection onboarding snapshots tool names, JSON schemas, risk classes, egress domains, data residency, and required scopes. An admin approves a versioned catalog; schema drift disables or quarantines the changed tool until reapproval. Agents can request catalog tool IDs, not arbitrary endpoints.
The MCP proxy validates arguments, size, types, resource bindings, and idempotency before invocation. It sends a one-call capability; no platform refresh token or unrelated secrets enter the server. Responses are size/time bounded, malware/content-classified, and wrapped as untrusted tool data. Returned text cannot automatically trigger another tool; each next call is a fresh policy decision, and sensitive actions need human approval.
Network egress is allowlisted, DNS/IP rebinding is defended, server certificates/identity are pinned where possible, and tenants may route through a private proxy. Tool-call traces store schemas, hashes, decision IDs, and receipts while redacting secret/raw payload fields.
OAuth refresh and external writes fail ambiguously
The provider rotates a refresh token, our database commit times out, and the MCP create-ticket call also times out after possibly succeeding. How do you recover?
Token rotation is a compare-and-swap vault operation with credential version. The broker stores the new token encrypted before marking the old version retired; if provider semantics invalidate the old token immediately, the connection enters REFRESH_UNKNOWN and a single-flight reconciler tests or reauthorizes—workers never fan out refresh attempts.
For the ticket, the tool gateway first commits EffectIntent(effect_key, request_hash, REQUESTED). It passes the stable key if the provider supports idempotency. On timeout, state is UNKNOWN; recovery queries by key/correlation or creates a human reconciliation task. No blind retry. If reconciliation finds success, record external ID and hash; if not found and policy allows, retry with the same effect key.
Connector-specific adapters expose these semantics explicitly. A generic “retry three times” middleware is unsafe for rotating credentials and non-idempotent writes.
Backpressure, isolation, and observability
How do you handle provider outage, a webhook storm, noisy tenants, and audit requirements?
Ingress verifies and durably buffers webhooks, then per-provider/installation queues apply quotas and coalescing. Provider circuit breakers stop futile calls; exponential backoff honors rate-limit resets. Hierarchical fairness reserves interactive tool capacity, caps backfill/indexing, and meters per tenant/project/connector. Queue messages contain opaque IDs—not tokens or fetched source.
I track connection health, refresh success, webhook dedupe/lag, policy latency/cache age, revocation propagation, denied confused-deputy probes, token exchange volume, tool schema drift, ambiguous effects, provider rate limits, tenant fairness, and audit receipt gaps. Synthetic canary tenants continuously attempt cross-project reads and revoked-token use. Secrets never appear in application traces.
Multi-region control uses one authority per grant shard or consensus for grant/epoch writes; data-plane validation can run regionally from signed policy snapshots plus revocation stream. On uncertainty, privileged access fails closed.
Ship connectors by increasing authority
How would you test and roll this out without turning one bug into an enterprise breach?
Start with synthetic providers and conformance suites: state/PKCE replay, redirect abuse, tenant/resource confusion, epoch revocation races, scope downgrade/upgrade, schema drift, rate limits, token rotation, pagination, response overreach, ambiguous effects, SSRF, prompt injection, and secret redaction. Property tests assert that adding untrusted request fields can never expand authority.
Roll out read-only resource discovery, then selected read actions, then sandboxed write proposals, then approved writes per connector/action. Each connector has a kill switch, minimum version, scoped canary tenants, incident playbook, and audit export. We measure successful authorized outcomes—not raw call success—and block promotion on any cross-tenant or post-revocation access.
The security story in one breath
Give me your final summary and most important trade-off.
The Switchboard keeps OAuth refresh credentials in a hardened broker and maps external installations/resources to one tenant/project in an authoritative control plane. Every tool call re-derives authorization from subject, workload actor, resource, action, purpose, approval, and revocation epochs, then exchanges for a short-lived audience-bound capability. Connectors enforce resource constraints again and classify outputs before agent context.
Webhooks and jobs are durable, idempotent, fair, and reconcilable; revocation fences fetch, publish, and search independent of index lag; ambiguous writes use effect receipts instead of blind retry. The trade-off is cache performance versus revocation certainty: locally verified capabilities are fast, but privileged steps must consult a fresh epoch or fail closed.
Complete reference
Data model: keys, invariants, access paths
| Entity | Primary key / fields | Invariant | Primary access path |
|---|---|---|---|
ExternalConnection | (tenant_id, connection_id); provider, external_tenant/install ID, vault_ref, status, epoch | One provider installation maps to one tenant authority domain | Connect, refresh, revoke, incident lookup |
ResourceBinding | (connection_id, external_resource_id, project_id); allowed environments | Server-derived; no request can remap resource to another project | Authorize repo/channel/tool target |
GrantVersion | (tenant_id, grant_id, version); subject, actions, resources, consent, policy, epoch | Append version; disabled epoch invalidates old capabilities | Policy evaluation; consent audit |
Delegation | (tenant_id, delegation_id); subject, actor, run/step, purpose, audience, expiry, approvals | Intersection of permissions; never broader than parent grants | Token exchange; audit chain |
CapabilityReceipt | (delegation_id, jti); claims hash, issued_at, expires_at | Short-lived, audience/resource/action bound, replay rules explicit | Invocation validation; incident scope |
WebhookReceipt | (provider, installation, delivery_id); signature key version, payload hash, status | One delivery accepted once; ordering is not truth | Dedupe; provider reconciliation |
ToolInvocation | (tenant_id, invocation_id); effect_key, grant/epochs, request/response hash, external correlation | Both subject and actor; secrets excluded; effect state monotonic | Audit; retry/reconcile; billing |
ToolCatalogVersion | (connection_id, tool_id, schema_digest); risk, egress, scopes, approval | Schema change requires policy review before enablement | Agent discovery; input validation |
Concrete API surface
/v1/connections/oauth:startSigned state binds browser session, tenant, provider, redirect, requested purpose, and expiry./v1/connections/oauth:callbackValidate state, issuer, code, PKCE, installation; vault token before returning resource picker./v1/connections/{id}/bindingsAdmin selects exact provider resources → internal projects; expected connection version./internal/token:exchangeSubject token + workload attestation + resource/action/purpose → short-lived capability or deny./internal/tools/{catalog_id}:invokeCapability, schema-versioned args, effect key; returns typed receipt or UNKNOWN./v1/connections/{id}:revokeTransactional disable + epoch increment + vault revoke + outbox invalidation./webhooks/{provider}Provider signature/delivery ID; durable ack, async current-state reconciliation./v1/audit/delegations/{id}Delegation chain, decisions, capabilities, calls, receipts—payload-redacted.Zero-trust enterprise integration plane
Humans and agents submit intent; only the control plane maps identity to resource authority; connectors receive narrow one-call capabilities.
Authority narrows at every hop
A Slack user never hands the agent a credential. The broker derives a one-call capability from trusted bindings and current epochs.
Consistency ledger
Operational scorecard
Platform health
Outcome quality
Visual values are illustrative. In production, every metric needs a unit, time window, tenant/slice dimension, owner, alert threshold, and prescribed action.
Phased rollout
Discover
OAuth plus read-only resource inventory; validate state, PKCE, binding, and uninstall.
Read narrowly
Selected repositories/channels/tools; negative authorization and revocation canaries.
Propose writes
Agents draft actions; human approves; connector receives one-call capability.
Write by policy
Enable exact low-risk actions per tenant with idempotency, receipts, reconciliation, and kill switches.
Interview traps
- 01Passing an organization OAuth token to every worker or agent as ambient environment state.
- 02Trusting tenant, project, repository, or tool name supplied by Slack text or model output.
- 03Checking user identity once at login and omitting workload actor, purpose, and delegated resource.
- 04Relying only on token TTL for emergency revocation.
- 05Filtering provider results before retrieval but not validating the returned resource and classification.
- 06Auto-enabling an MCP tool after its schema or egress behavior changes.
- 07Combining permissions from multiple grants by union instead of intersecting every boundary.
- 08Blind-retrying non-idempotent external writes after a timeout.
- 09Logging refresh tokens, authorization codes, raw tool arguments, or secrets in traces.
- 10Assuming webhook delivery order represents provider truth instead of reconciling current state.
Glossary
- Audience
- The service a token is intended for. A connector capability must be rejected by every other service.
- Capability
- A bearer or proof-bound token granting explicit operations on explicit resources for a short time.
- Confused deputy
- A privileged component misuses its authority for a less-privileged caller because context was not bound.
- Delegation
- A traceable grant from a subject to a workload actor, narrowed by project, resource, action, purpose, and expiry.
- Effect key
- Stable business identifier committed before a side effect so retry can reconcile one logical action.
- Epoch fencing
- Reject cached decisions or capabilities whose monotonic grant/project/connection version is stale.
- OAuth PKCE
- Proof Key for Code Exchange binds an authorization request and code redemption, reducing code interception risk.
- Principal
- A human, service account, workload, connector, or external identity that can be authenticated and authorized.
- Refresh token
- Long-lived credential used to obtain access tokens; it belongs only in a hardened token broker.
- Resource binding
- Server-controlled mapping from an external repository/channel/server to exactly one allowed internal project context.
- SSRF
- Server-side request forgery: untrusted inputs make a server reach internal or unintended network destinations.
- Token exchange
- Service-to-service operation that transforms parent identity/delegation into a narrower audience-bound capability.
One-minute spoken recap
No ambient credentials. External refresh tokens live only in a vault-backed broker. Trusted bindings map provider resources to internal projects. Every call binds human subject, workload actor, tenant, project, exact resource/action, purpose, approval, audience, expiry, and revocation epochs. Connectors enforce the capability and validate outputs again. Webhooks are durable and reconciled; external writes use effect receipts; revocation fences jobs, fetches, publishing, and search. Performance comes from local signature checks—not from weakening revocation or tenancy.