AgentPAM.ai

Integration · primary path

Connect the IdP and gateway you already run

This is V1's primary integration path: the agent reaches a remote / internal MCP server through your existing gateway, and AgentPAM plugs in as the external Authorization Server + PDP + credential broker. (The local laptop / stdio scenario is out of current scope — V2+ roadmap.)

You already have an IdP (Okta / Entra) and an MCP gateway. AgentPAM sits between them — you change configuration, not code; the control plane runs on your own Linux, air-gap capable, with no outbound callback.

Deployment topology

The three things we do not touch

The IdP is yours, the MCP gateway is yours, the MCP servers / APIs are yours. We provide only the control-plane container group in the middle — running on your own Linux, air-gap capable, with no outbound callback.

topology.txtscrolls horizontally
+-- Your own Linux environment (air-gap capable, no outbound callback) --------+
|                                                                              |
|   +------------+     OIDC federation    +------------------------------+     |
|   | Your IdP    |<----------------------| AgentPAM control plane        |     |
|   | Okta/Entra |   (consumes assertions only)  | /authorize /token  AS+STS |     |
|   +-----+------+                        | PDP  AuthZEN / COAZ       |     |
|         | human SSO (Tu)                | Credential broker  RFC 8693 |     |
|   +-----v------+   ext_authz / AS       | Audit sink                   |     |
|   | Your MCP    |<--------------------->| JWKS (independent HA)        |     |
|   | gateway    |   inject cred (Tup)    +------------------------------+     |
|   | (RS)       |                                                             |
|   +-----+------+                                                             |
|         | inject short-lived credential                                       |
|   +-----v------+                                                             |
|   | Sensitive  |  <- remote / internal, NOT a laptop                          |
|   | API/server |                                                             |
|   +------------+                                                             |
+------------------------------------------------------------------------------+
        ^ agent holds Ts (session token, DPoP, 5-15 min)
   +----+-----+
   | AI Agent |  managed workstation / VDI  (agent is on the endpoint,
   | Claude   |  but the MCP server is not)
   +----------+
Yours

IdP

Okta / Entra. Human authentication always happens here; we only consume assertions.

Yours

MCP gateway

It is the Resource Server. We plug into it; we do not replace it.

Yours

MCP server / API

Remote or internal, not a laptop. Credentials are broker-injected; the agent never sees them.

Connect the IdP

Connect the IdP: a federation consumer, not a replacement

AgentPAM does not authenticate humans (constitution C-02). Human authentication happens at your IdP; we trust its assertions over OIDC federation. We do not store human passwords and we do not do MFA.

The four things you do on the IdP side

  1. Register AgentPAM as an OIDC Relying Party (or SAML SP) and give us a client_id / client_secret (or a federation trust).
  2. Configure AgentPAM's redirect / token endpoint — our internal /callback address.
  3. Expose the IdP's JWKS and discovery endpoints to us (internal reachability is enough).
  4. Map the claims we need (sub, email, groups / roles).
agentpam.yamlscrolls horizontally
identity_providers:
  - id: corp-okta
    issuer: https://corp.okta.internal          # your IdP, internal
    jwks_uri: https://corp.okta.internal/oauth2/v1/keys
    audience: agentpam                            # we are the audience
    claim_mapping:
      subject: sub
      email: email
      groups: groups

Supported IdPs and federation methods

IdPMethodNotes
OktaOIDC + ID-JAG / Cross-App AccessPreferred. EMA (Enterprise-Managed Auth) is stable — moves consent and policy to the IdP, no consent screen
Microsoft EntraOIDC + OBO (On-Behalf-Of)We consume Entra Agent ID facet claims directly (xms_act_fct / xms_sub_fct)
Google WorkspaceOIDCStandard OIDC federation
Keycloak / self-hostedOIDC + RFC 8693Common with air-gapped customers; Keycloak supports token exchange natively
SAML-only legacy IdPSAML assertion → we convert to OIDCFallback path
EMA / ID-JAG — hand “user-driven agent connections” to the IdPThis answers challenge #7 directly. With EMA enabled: the human SSOs to the IdP → the agent obtains an ID-JAG (Identity Assertion JWT Authorization Grant) from the IdP → the agent exchanges the ID-JAG (RFC 8693 token exchange) with us for a session token → the connection completes automatically under IdP policy. No OAuth consent screen, no shadow connections, one revocation point. We do not implement the IdP side of EMA; we implement its RS/AS side — receiving the ID-JAG and doing the token exchange. Okta ships it first, Entra follows.

Connect the gateway

Connect the gateway: two co-primary paths, chosen by type

After the DR-001 correction: there is no “universal” path (we already retracted that claim). Pick one by your gateway type. The third is a fallback.

GatewayPathWhat you change
agentgateway / ContextForge / Kong EE / AWS AgentCorePath B: we are the external ASOne config block pointing at us
Envoy/Istio / Kong OSS / Pomerium / Obot / anything with a PDP hookPath A: we are the external PDPext_authz / OPA pointing at us
Docker MCP Gateway / no hooksPath C: as an upstream MCP proxy (fallback)The gateway treats us as an upstream server
Enforcement granularity: the three paths are not equivalent (stated honestly)

The three paths enforce at different moments, which decides the granularity they can reach. Parameter-level policy (e.g. refund.amount ≤ 500) — our headline differentiator — is enforced by nobody on pure Path B: a generic gateway only validates the token's sig + audience + scope, does not interpret our custom RAR predicate, and does not pass tool arguments to us.

CapabilityPath A (per-call PDP)Path B (token-carried)Path C (proxy)
Enforcement momentevery tools/callat Ts-mint (once)every tools/call
Who is the PEPus (sees tool+args)the gateway (token only)us
Server-level
Tool-level✅ (within token scope)
Parameter-level amount ≤ 500❌ nobody enforces
Per-call revocation❌ (until Ts expires)

Rule (no exceptions): parameter-level ⇒ requires a per-call hook (Path A / ext_authz, or Path B + a per-call callback). Pure Path B is server / tool coarse-grained only. We do not claim "config-only gets you parameter-level" — Path B is "config-only integration, parameter-level needs a per-call hook."

Path B — we are the gateway's Authorization Server

gateway-config.yaml · agentgatewayscrolls horizontally
# 1) the gateway publishes protected resource metadata (RFC 9728)
#    GET /.well-known/oauth-protected-resource ->
mcpAuthentication:
  issuer: https://agentpam.customer.internal       # points at us
  audiences: [ "https://mcp.customer.internal" ]   # * MANDATORY in agentgateway
  jwksUri: https://agentpam.customer.internal/.well-known/jwks.json

# 2) upstream credential injection: the gateway exchanges for Tup, the agent never sees it
backendAuth:
  oauthTokenExchange:
    path: /token                                   # * local YAML key is 'path', NOT tokenEndpointPath
    grantType: urn:ietf:params:oauth:grant-type:token-exchange

Keys corrected against the G-04 source review (resource.proto): the local YAML key is path, not tokenEndpointPath (the latter is the xDS/proto field name and will fail to load in a config file). audiences is structurally mandatory in agentgateway — omit it and the config fails to load, which is the safe default. ⚠️ Note this backendAuth exchange / injection path is config-level understanding, not runtime-verified against a live backend (G-22).

Path A — we are the gateway's external PDP (broader coverage)

envoy-extauthz.yaml + POST /pdp/evaluatescrolls horizontally
# Envoy ext_authz -> our PDP
http_filters:
- name: envoy.filters.http.ext_authz
  typed_config:
    grpc_service:
      envoy_grpc: { cluster_name: agentpam_pdp }   # points at our PDP
    with_request_body:                             # * pass body so we see MCP method/tool
      max_request_bytes: 8192
      allow_partial_message: true

--- our PDP interface (AuthZEN / COAZ-MCP compatible) ---
POST /pdp/evaluate
{
  "subject":  { "id": "u_dana", "type": "human" },        # the human
  "context":  { "agent": "agent_support_7f3a",            # agent, 2nd dimension (COAZ)
                "task": "task_88213" },
  "action":   { "name": "tools/call", "tool": "payments.refund" },
  "resource": { "server": "payments", "args": { "amount": 120 } }
}
-> 200 { "decision": true,
        "obligations": ["audit:high"],
        "headers": { "Authorization": "Bearer <Tup short-lived>" } }  # injected on allow

The key is letting the PDP mutate headers on allow (the credential-injection primitive), and passing the request body so we can see the MCP method / tool. Our PDP interface is AuthZEN / COAZ-MCP compatible: subject is the human, context.agent is the agent's second dimension.

Path C — fallback: as an upstream MCP proxy

When the gateway has no hooks at all (e.g. Docker MCP Gateway): the gateway configures us as an upstream MCP server, and we act as the policy-enforcement proxy in front of the real server. Cost: an extra hop, and we only see traffic routed to us. Benefit: zero requirements on your gateway. For Docker MCP Gateway this is the only viable path — its before:http: hook cannot mutate headers, so Path A does not work either.

Honest labelling of how far each was tested (G-22)Only agentgateway's AS discovery handshake was runtime-tested (darwin-arm64, v1.4.0-alpha.2: one mcpAuthentication block publishes us as the AS and issues an RFC 9728 challenge). The backendAuth.oauthTokenExchange path that actually mints and injects Tup is not yet runtime-verified against a live backend (G-22) — it is config-level. The other three Path B gateways and every Path A snippet are likewise documentation-grade, not runtime-verified. We will not write these up as “verified”; the full matrix is on the architecture page.

End to end

End-to-end sequence (Path B)

sequence-path-b.txtscrolls horizontally
[1] Human SSO -> your IdP -> Tu (OIDC, ~1h)
[2] agent -> AgentPAM /token  (RFC 8693)
        subject_token=Tu, actor_token=agent instance assertion
        authorization_details=[{ type: agent_task, task_id, purpose, sensitivity }]
    AgentPAM verifies Tu (against your IdP JWKS) -> issues Ts
        Ts: sub=human, act={sub: agent_instance}, cnf.jkt (DPoP), 5-15 min
[3] agent calls your MCP gateway with Ts
        gateway verifies Ts (against OUR JWKS) + audience
[4] pure path B: gateway enforces token (server / tool scope, COARSE)
        parameter-level requires a per-call hook (path A / ext_authz) -> per tools/call PDP decision with args
[5] allow -> gateway exchanges at AgentPAM /token for Tup (upstream cred, 60-300s)
        ** the agent never touches Tup **
[6] gateway injects Tup and calls the upstream API / MCP server
[7] AgentPAM writes the audit event (human/agent/task/tool/resource/decision/traceparent)
[8] task ends or CAEP revocation -> Ts invalidated immediately (no wait for TTL)

The whole chain is delegation, never impersonation. sub is always the human; act carries the agent instance. After step 5, the agent never touches Tup (the upstream credential).

One-day POC

A landing checklist you can finish in a day

#ActionWhoVerify
1docker load the AgentPAM container group image (offline media)Your opsdocker images
2Fill agentpam.yaml: IdP issuer / jwks, audienceYour opsStartup logs clean
3Register AgentPAM as an RP in the IdPYour IdP adminDiscovery reachable
4Point the gateway at us (Path B metadata + token endpoint, or Path A ext_authz)Your platform teamcurl .well-known/oauth-protected-resource returns us
5Register the first AgentBlueprint + AgentIdentity (with a human sponsor)Your platform teamShows in the Portal
6Define policy (server / tool / parameter, three levels)Your security teamPolicy in force
7Run one agent callYour developerSee acceptance below

POC acceptance (verifiable on the spot)

#CheckExpected
1Agent calls a permitted toolSucceeds; the upstream log shows a short-lived credential
2Agent calls a tool the policy deniesThe request never reaches upstream; the denial is in the audit log
3Parameter over the limit (refund $2500 > $500)Parameter-level denial
4Dump the agent session and search for the upstream credentialTup not found (only Ts)
5Audit eventCarries human + agent + task, correlatable via traceparent
6Mark the task complete, then call againImmediately denied (even though Ts has not expired)
7Disconnect AgentPAM, then restart the gateway (HA test)⚠️ See “Two honest disclosures” below — this is a coupling we must tell you about

Bridge, not bucket

A bridge, not a bucket — zero stored credentials

The previous section admitted we are a high-value target. This section is its architectural answer: a broker that stores every customer's credentials has merely concentrated the risk (Salesloft Drift fell exactly this way). We do not build that bucket.

Invariant BNB-0 (verifiable, not a slogan)

No long-lived credential that can directly access a customer's upstream API exists in cleartext anywhere in AgentPAM's persistent storage.

Four kinds of key material, handled differently — honestly, only the first two can be a true “bridge”

Key materialWhat it isCan it be a “bridge”?How it is handled
① Upstream API credentialsThe customer's GitHub / DB / cloud access credential (the source of Tup)✅ YesNot stored. Fetched from the customer vault / cloud IAM per request, injected, then discarded
② Customer IdP token (Tu)The human SSO token✅ YesNot stored. Validated in one token exchange, never persisted
③ Our signing keyThe key that signs Ts / JWKS❌ Must exist somewhereIn an HSM / KMS — generated in hardware, never exportable, sign-only
④ Secret zeroThe bootstrap credential we use to reach the customer vault⚠️ The hardestPrefer no long-lived secret: projected SA token / workload identity; bare-metal falls back to rotatable mTLS

Three bridge modes — we store only a “where to fetch” reference, never the credential

ModeMechanismWhat we store
A. Vault passthroughCustomer runs HashiCorp Vault / Infisical. We call its dynamic secret engine per request for a short-lived credential, inject, discardNothing — even the reference is a path into the customer's vault
B. Cloud STS exchangeCustomer runs cloud IAM. We use workload identity to exchange for a short-lived STS credential (e.g. AWS AssumeRole 15min), inject, discardOnly a role ARN reference
C. Upstream OAuth clientUpstream is SaaS (GitHub / Slack). The refresh token lives in the customer's vault, not ours; we fetch it per request to mint an access token, inject, discardOnly a vault reference

The damage ceiling if we are fully RCE'd

Bucket (Salesloft model)Bridge (AgentPAM)
Directly obtainedEvery customer's upstream credentials, in cleartextA pile of vault path / ARN references + a non-exportable HSM handle
Redeeming still requiresNothing — use them directlyPassing through secret zero → the customer vault's own policy and audit
Each redemptionLeaves no traceLogged, rate-limited, revocable by the customer's vault
Blast radiusEvery customer (multi-tenant SaaS)One customer (private, C-13)
What the customer can doDepend on the SaaS vendorRotate vault credentials, revoke secret zero, stop the container — the customer holds the controls

Don't trust us — verify it yourself

A customer runs these six checks. Checks 1 and 6 are the core — they empirically prove BNB-0: there are no eggs in our storage, and you hold the power over the credentials.

#CheckExpected
1Dump our persistent storage (DB / disk) and grep for upstream credential signatures (ghp_ / sk- / AKIA / DB passwords)Zero hits — only path / ARN references
2Inspect the HSM / KMS config; confirm the signing key is non-exportableCKA_EXTRACTABLE=false
3Cut our connection to your vault, then watch an agent callFails — proving credentials are fetched live, not stored by us
4Audit our outbound network (should be zero when air-gapped)No “phone home” connections (C-15)
5Verify the image signature + SBOMSignature valid, dependencies auditable
6Revoke one credential in your vault, then immediately retry an agent callFails immediately — proving we neither cache nor retain

Three places we cannot be a “perfect bridge” (the honest list)

The signing key must exist

Ts / JWKS need to be signed with our private key, and that key must exist somewhere — we use an HSM to make it “stored but unextractable”, not “unstored.” Residual risk: a signing oracle — after RCE an attacker cannot take the key, but might induce the HSM to legitimately sign a malicious token. The HSM cannot stop that; policy-side defenses do (issued-content templates + rate limiting + anomaly detection).

Secret zero can't be fully eliminated on bare-metal air-gap

K8s (projected SA token) and cloud (workload identity federation) achieve no long-lived secret; but bare-metal air-gap's best case is a rotatable mTLS client certificate, not zero secret. Even if it leaks, the attacker gains “access to your vault,” and must still pass through your vault's own policy, audit, and revocation.

A sub-second in-memory window

Even on the bridge, a credential appears in memory for ≤ a second between fetch and inject. Mitigations: zeroize immediately after injection, keep it out of logs and long-lived objects, and do not cache across requests by default (trading a little latency for minimal exposure). We do not pretend this window is zero.

Bridge, not bucketWriting these three down honestly is itself part of the credibility. A broker that claims “absolutely safe, zero risk” is precisely the one you should not trust.

Two honest disclosures

Two things we must be honest with you about

G-20 · startup dependency

If we are down, a Path B gateway may fail to start

agentgateway fetches JWKS eagerly at startup and fails closed — if AgentPAM is unreachable at that moment, the gateway may not come up. Mitigation: deploy the JWKS endpoint in independent HA, isolated from the policy engine's failure domain, and support customer-side static JWKS hosting. This must be in the SLA and deployment docs; we do not hide it.

G-21 · Envoy audience silent-fail

Omitting audiences silently disables the check

agentgateway structurally requires audiences (omit it and the config fails to load — safe). But with Envoy, omitting audiences silently disables audience validation — tokens can be replayed across audiences. A deployment check must actively probe this rather than assume the default is safe.

G-09 · we are a high-value target

The control plane holds the keys to every sensitive API

Mitigation: see “A bridge, not a bucket” above — our persistent storage holds no cleartext credential that can directly reach an upstream API (invariant BNB-0). Plus root keys in your KMS / HSM, single-tenant isolation by construction, and hash-chained tamper-evident audit. Bridge + private = double containment: the blast radius is bounded by your own failure domain, and a breach yields no offline-usable credential store — only a logged, revocable, single-tenant live oracle (honest limits in the “three places we cannot be a perfect bridge” below).