Cloud integrations
The connectors PermDock Cloud offers on its Integrations page, what each one speaks and what it never does; every connector is a standard wire format or an existing PermDock interface, none is an npm entry, and none sits on the decision path.
Status: planned
PermDock Cloud has an Integrations page where an environment is connected to the platforms around it: identity providers, Vercel, observability backends, SIEMs, webhooks, compliance platforms, your own database, chat surfaces and an MCP host. This page is the contract for that UI. It fixes what each connector speaks; the PermDock-Cloud repository implements it. Two rules from the rest of the docs apply to every row (adapters, PermDock Cloud, invariant 15):
- A connector is a standard wire format (AuthZEN, OCSF, CloudEvents, OTLP, SCIM, CAEP, JOSE, OIDC) or an existing PermDock interface (
ApprovalStore,DecisionSink,SnapshotSource,DirectoryStore). There is nopermdock/auth0,permdock/datadogorpermdock/vanta, and connecting a platform never adds a dependency to your application. - No connector sits on the decision path. Inbound connectors change who the Cloud authenticates, what your
DirectoryStorecontains, or (in Cloud-native directory mode) which claims the Cloud mints into the tokens your app verifies; outbound connectors copy evidence and notifications out. Nothing any connector does is consulted bycan,decide,filter,whereorsimulate.
Each table below has the same columns. Connector is what the Integrations page shows. Standard or interface is the only thing the connector speaks. What it carries is the data that crosses. Never is the thing the connector is not allowed to do, and each of those is a row in the threat model.
Inbound: identity providers
One identity provider can be connected in up to four roles. They are independent: a tenant may use Okta as a SCIM source while its end users authenticate with Clerk.
The environment's directory mode decides which rows apply. In Cloud-native mode the Cloud directory is the source of the claims it mints, and upstream providers connect only as federated login. In bring-your-own mode your provider or your tables stay authoritative: end users either log in through the Cloud issuer, which federates to the provider, or bypass the Cloud entirely and your app uses the provider's subjectFrom*; the SCIM relay feeds your DirectoryStore.
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| Trusted issuer (Auth0, Okta, Entra ID, Clerk, Supabase Auth, WorkOS AuthKit, Better Auth, Google Identity, any JWKS-publishing issuer) | OIDC Discovery or RFC 8414 metadata, JWKS, RFC 9068 access tokens; verified by permdock/jwt with the same discovery, accept, algorithms and profile rules the application uses | Who the hosted ADS and the snapshot edge accept as an end user: iss, sub, aud, roles, groups, org_id, act, cnf, mapped to a Subject exactly as subjectFromJwt does in your app | Widen what the embedded engine decides; accept a token whose tenant claim has no onboarded connection (no tenant, never a default); read memberships from the provider's management API |
| CAEP transmitter (Okta, Entra ID, Google Workspace, Auth0, any SSF transmitter) | Shared Signals Framework: RFC 8935 push or RFC 8936 poll into the Cloud's SSF receiver; OIDC Back-Channel Logout as the alternative | session-revoked, credential-change, assurance-level-change events keyed on the subject; logout_token for Back-Channel Logout | Change a decision; the event invalidates signed snapshots served by the Cloud edge and nothing else (Shared Signals, SSF adapter) |
| SCIM source (Okta, Entra ID, Google Workspace, WorkOS Directory Sync, any SCIM 2.0 client) | SCIM 2.0 (RFC 7643, RFC 7644, RFC 9865) into the hosted relay; RFC 7523 JWT bearer from the relay to your scimHandler | User and Group resources, the tenant's group-to-role mapping as the urn:permdock:scim:schemas:extension:roles:1.0 attribute, one sync-log entry per operation | Hold an authoritative copy of what it relays (your DirectoryStore stays the record in bring-your-own mode); implement MembershipSource or RoleSource; grant a role the policy did not declare assignable (SCIM adapter) |
| Federated upstream (Clerk, Auth0, WorkOS, Google, Enterprise SSO, any OIDC Discovery issuer) | OIDC authorization code plus PKCE from the Cloud issuer to the upstream | The upstream's authentication of the user; the Cloud then issues the access token your app verifies with subjectFromJwt({ discovery: PERMDOCK_CLOUD_URL }) | Become the application's session layer; copy upstream roles into claims without the directory mode's mapping; accept an upstream token without its iss and aud checks |
| Cloud-native directory (the Cloud's own users, groups and assignment UI) | PermDock JWT claims (JWT authorization claims) on Cloud-issued tokens; dev.permdock.membership events for every change | roles, groups, entitlements and the tenant claim for each principal, taken from declared assignable roles and plans in the uploaded catalog | Be read at request time; implement MembershipSource or RoleSource; mint a role or plan the catalog does not declare; hand out a role the acting admin does not hold in that tenant |
"Connect Supabase" on the identity side means Supabase Auth as a trusted issuer (its project JWKS, role and app_metadata handled as on the Supabase adapter page); the Cloud never reads your Supabase tables or RLS policies. "Connect Clerk", "Connect Auth0" and "Connect WorkOS" mean the same three roles with the provider's discovery URL, SSF transmitter and SCIM client.
Inbound: Vercel
The Vercel connector is already specified on the Cloud adapter page ("Vercel Marketplace" and "Hosted Authorization Decision Service"); the Integrations page links those sections rather than duplicating them.
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| Vercel Marketplace | Marketplace provisioning API (install, resource, SSO, billing) | One PermDock environment per Vercel project and environment; PERMDOCK_CLOUD_URL and PERMDOCK_CLOUD_KEY written to the project | Write a NEXT_PUBLIC_* variable; share one environment between preview and production |
| Vercel OIDC | Vercel's OIDC federation token verified against Vercel's JWKS | ADS caller identity: sub, project_id, environment mapped to a workload principal | Accept a static shared secret in place of the token; accept a token for another environment |
| Vercel Chat SDK | Chat SDK requestApproval (below, delivery) | Approval cards on Slack and Teams with signature-verified responders | Resolve an approval from a message body |
Outbound: observability (OTLP)
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| OTLP collector endpoint (Datadog, Grafana Cloud, Honeycomb, Axiom, New Relic, any OpenTelemetry Collector) | OTLP/HTTP log records and spans with the permdock.* attribute set from the OpenTelemetry adapter (permdock.outcome, permdock.permission, permdock.subject.id, permdock.actor.id, permdock.actor.kind, permdock.matched.role, permdock.denials.count) | A copy of every event in the evidence log as an OTLP log record, and the hosted ADS's own permdock.decide spans | Be the evidence. Collectors sample and drop, retention is short, a span is not signed; the sink's log and its signed export are the evidence (audit and observability "OpenTelemetry is not the evidence path") |
Because the attribute names are the same set permdock/otel emits from inside your application, a backend cannot tell an app-side span from a Cloud-side log record, and one dashboard serves both.
Outbound: SIEM (OCSF)
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| SIEM or security lake (Splunk HTTP Event Collector, Microsoft Sentinel data collector, AWS Security Lake, Google SecOps, Elastic ingest, Datadog Cloud SIEM) | The OCSF Authorize Session projection toOcsf(event) from wire formats, posted over the destination's ingest HTTP API | Every decision and approval event, pinned to one OCSF version | Diverge from the projection a self-hosted DecisionSink recipe emits; the Cloud runs the documented projection, it does not own a private one |
Outbound: webhooks
Webhooks are the generic escape hatch: anything not listed on this page receives CloudEvents over HTTP.
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| HTTP webhook | CloudEvents 1.0 events in a batch signed as a compact JWS with typ: permdock-decisions+jwt under the events claim, posted as application/jwt (wire formats "Signed outputs"); there is no unsigned mode | Events of type dev.permdock.decision, dev.permdock.approval, dev.permdock.directory (a SCIM operation replayed), dev.permdock.membership (a role change) and dev.permdock.catalog (a policy publish or a drift finding); source is the emitting service, subject the permission key, resource id or principal id | Deliver an approval resolution: a webhook tells a receiver that an approval is pending or resolved, and the receiver cannot answer it |
A receiver verifies the JWS against the environment's JWK Set, <PERMDOCK_CLOUD_URL>/v1/environments/<env>/.well-known/jwks.json (cloudEndpoints().jwks from permdock/cloud), with aud equal to itself and exp; the batch jti and each event id make replay detectable. Retries use exponential backoff with the same jti and id, so a receiver deduplicates on them. A TypeScript receiver uses verifyWebhook from permdock/cloud; any other language uses its JOSE library the same way.
import { cloudEndpoints, verifyWebhook } from "permdock/cloud";
import { memoryReplayStore } from "permdock/ssf";
const replay = memoryReplayStore();
export async function POST(request: Request) {
const delivery = await verifyWebhook(request, {
jwks: cloudEndpoints().jwks,
audience: "https://app.example.com/api/permdock-webhook",
replay,
});
if (!delivery.ok) return new Response(null, { status: 401 });
for (const event of delivery.events) {
if (event.type === "dev.permdock.catalog" && event.data.kind === "publish")
await permdockCloud.policies.refresh();
}
return new Response(null, { status: 204 });
}verifyWebhook never throws. It answers { ok: false, reason } with unsigned (the body is not a compact JWS), invalid-token (with the verifier's cause), invalid-events (an event outside the closed type list, a data shape that does not match its type, or a __proto__, constructor or prototype key) or replayed (a jti the ReplayStore already holds). parseCloudEvent is the same per-event check for events read from a queue.
Signed only, because a webhook receiver is usually a public URL. An unsigned mode with a shared secret or an optional HTTP signature would be the mode everyone ships first, and a forged catalog or membership event then triggers a refresh or an alert on attacker input. The batch reuses the decision-export envelope so a receiver has one typ, one key set and one verifier for everything the Cloud sends.
Outbound: compliance evidence
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| Compliance platform (Vanta, Drata, Secureframe) | The Cloud export endpoint: a signed batch (permdock-decisions+jwt) or CSV pulled on the review window with a scoped API key | Access-review evidence with the columns from the Evidence and governance contract: subject.principal, actor, permission, outcome, tenant, matched.role, via | Return sampled data; the evidence log writes every event and the export is complete for its window |
Outbound: data destinations
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| Your database (Supabase Postgres, Neon, any Postgres) | Either a scheduled pull of signed batches by a job you run, or a CloudEvents webhook into a function you own (a Supabase Edge Function, a route handler) that inserts rows | The same events, into a table with your schema; the sink recipes show the columns | Read from your database. The Cloud has no credential to your Postgres, never reads a table or an RLS policy, and never uses the service_role key |
This is how "connect Supabase" reads on the data side: Supabase is a destination that receives evidence, not a source the Cloud queries.
Outbound: approval delivery
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| Slack, Microsoft Teams, Discord | Vercel Chat SDK requestApproval, the recipe from the approvals adapter "Delivery" section run by the Cloud | An approval card per ApprovalRequest; the platform signature identifies the responder, the Cloud maps the responder to an approver and applies the actor and distinct-approver rules | Take an approver identity from the message body; let the request's actor approve its own call |
| A link to the authenticated inbox | Notification that an approval is pending | Resolve from the link alone; the approver signs in to the inbox (the signed-link question stays open on approvals) | |
| Paging (PagerDuty, Opsgenie, any incident tool with an events API) | A CloudEvents webhook for dev.permdock.approval with phase: requested | The page; the on-call person resolves in the inbox or a chat card | Resolve an approval |
Outbound: MCP
| Connector | Standard or interface | What it carries | Never |
|---|---|---|---|
| PermDock Cloud MCP server (connected from Claude, Cursor, ChatGPT or any MCP host at mcp.permdock.com) | MCP over streamable HTTP with OAuth 2.1 per MCP authorization; the Cloud is the resource server and its own identity is the authorization server | Read-only tools: query evidence (the review questions above), list pending approvals, read the published catalog and drift findings, all scoped to the token's subject and tenants | Resolve an approval, publish a policy or change a connector. Approver identity must come from the Cloud's own authentication, and an agent approving an agent's action defeats the human-in-the-loop guarantee (approvals) |
What the page does not offer
- A connector that resolves memberships or roles from a provider API. That is a
MembershipSourceorRoleSourcein your application, or SCIM into aDirectoryStoreyou own (tenancy, invariant 15). - A connector that pushes a policy into the application. Policies are code in your bundle;
permdock cloud pushpublishes a copy to the hosted ADS and the catalog, in one direction. - Per-vendor npm entries or per-vendor OpenAPI extensions.
- An authentication product or a gateway. Trusted issuers are how the Cloud consumes authentication, not how it provides it.
- An MCP
explaintool that evaluates a permission for a subject. Explaining a past decision needs no evaluation: the stored event already carriesmatched,viaanddenials, and the evidence query tool returns it. Evaluating a hypothetical one would make the Cloud's MCP server take a subject as an argument, which invariant 13 forbids, and would answer from the pushed copy of the policy rather than the code the application runs. What-if questions belong tosimulatein the application or adescribePolicytest.
Example app
No example app of its own: apps/examples/eve-agent (the Marketplace template) exercises the Vercel, Slack and evidence-export connectors against a Cloud environment; apps/examples/scim exercises the SCIM relay.
Related standards
- OpenID Connect, JOSE and JWT authorization claims: trusted issuers.
- Shared Signals and CAEP: CAEP transmitters.
- SCIM 2.0: SCIM sources and the relay.
- MCP authorization: the Cloud's MCP server.
- Wire formats: CloudEvents types, the OCSF projection and
permdock-decisions+jwt. - adapters, PermDock Cloud.
Last updated on
Cloud
permdock/cloud is the thin, optional client for PermDock Cloud; it implements ApprovalStore, DecisionSink and SnapshotSource over a documented HTTP API, never sits in the decision path, and is provisioned from the Vercel Marketplace.
Shared Signals (SSF / CAEP)
permdock/ssf receives Shared Signals Framework security event tokens (push and poll) and maps CAEP events to snapshot and cache invalidation so permissions go stale when the IdP says so, not on a timer.