MAQPNADocs

Identity and delegation#

An agent should never hold a long-lived credential, and every call it makes should say which agent, acting for which person, with which permissions. MAQPNA gives each session a session identity issued by the identity broker, and uses it on every governed call.

Term Meaning
identity broker maqpna-identity: issues session tokens, publishes its public keys (JWKS), exchanges and delegates tokens
session identity The signed identity of one session: agent, tier, namespace, the person it acts for, scopes
session token The bearer form of the session identity: a compact JWS signed with Ed25519 (alg: EdDSA)
access token An admin-API token for a person (OIDC) or the static break-glass token; never given to agents
identity provider Your OIDC single sign-on system (Keycloak, Entra ID, Okta)

What a session token says#

{
  "iss": "maqpna-identity",
  "sub": "spiffe://acme.eu/ns/team-a/agent/coder/session/fix-4821",
  "aud": "maqpna-gateway",
  "iat": 1791024000, "nbf": 1791024000, "exp": 1791024900,
  "jti": "5f0c2e9a7b1d4c3e8a6f2b0d9c1e7a54",
  "act": { "sub": "user:alice@acme.eu" },
  "scope": "tools:github models:team-a.coder",
  "maqpna_ns": "team-a", "maqpna_agent": "coder", "maqpna_session": "fix-4821", "maqpna_tier": "tier-1-microvm",
  "maqpna_groups": "eng oncall",
  "maqpna_principal_verified": "requester"
}
  • sub is a SPIFFE-style workload ID: trust domain, namespace, agent and session. The gateway checks that it agrees with the maqpna_* claims.
  • act.sub is the person the agent acts for (on-behalf-of, RFC 8693 style). maqpna_groups holds their verified groups (at most 32), and maqpna_principal_verified says how the person was bound: requester (the creator of the session, checked by an admission policy), trustedCreator (a trusted portal created it) or idToken (an OIDC ID token was verified). Absent means unverified.
  • scope is space-separated: tools:<server>, models:<route>, agents:<name>. A session's scopes must be a subset of its agent's.
  • Lifetime. 15 minutes by default, never more than the broker's maximum (1 hour) or the session's remaining TTL. The operator re-mints the token at 80% of its lifetime; for warm-pool and attested sessions the pod refreshes it itself.

Least privilege, by construction#

  1. No static secrets in sandboxes. The token is mounted read-only, expires in minutes and is useless outside its scopes. Upstream credentials (API keys, OAuth client secrets, mTLS keys) live only in the gateway; the agent never sees them.
  2. Derived tokens only narrow. Token exchange (POST /v1/exchange, RFC 8693) issues a shorter-lived token for an allow-listed audience, with scopes provably narrower than the original. An exchanged token cannot be exchanged again.
  3. No token passthrough. The gateway strips the agent's Authorization header before forwarding. The upstream receives X-Maqpna-Namespace, X-Maqpna-Agent, X-Maqpna-Session, X-Maqpna-User, X-Maqpna-Subject and X-Maqpna-Groups from the verified claims, or a short-lived transaction token (Txn-Token, 60 seconds by default), and its own credential.
  4. Per-user credentials stay in a vault. When an MCP server needs the user's own OAuth token (GitHub, Microsoft 365), the gateway injects it from its encrypted token vault after the user connected the account once; the agent never holds it.
  5. Optional workload identity (SPIFFE). With SPIRE, the sandbox also presents an X.509-SVID whose ID must equal the token subject (tls.svidMode: optional | required).

Delegation between agents#

When agent A calls agent B over agent-to-agent (A2A), B may need to act for the same person. B exchanges the call's transaction token (or A's token) together with its own session token at the broker and receives a delegation token whose act claim nests the chain, outermost first:

"act": { "sub": "spiffe://acme.eu/ns/team-a/agent/planner/session/p-17",
         "act": { "sub": "user:alice@acme.eu" } }
  • The chain is capped at 5 hops (-max-delegation-depth); loops back into an agent of the chain are refused (delegation_loop).
  • Scopes are narrowed to B's, expiry is at most A's and B's, and the original person's groups and verification carry through.
  • Policies keep matching the human: the gateway walks the chain to the user: entry. Revoking any agent of the chain, or the delegator's token ID, revokes the delegated token too.
sequenceDiagram
    participant A as Agent A (planner)
    participant GW as Gateway
    participant B as Agent B (coder)
    participant IB as Identity broker
    A->>GW: A2A SendMessage to team-a/coder<br/>(A's session token, scope agents:coder)
    GW->>GW: verify, revocation for A and its chain,<br/>allowedCallers, depth, policy, DLP
    GW->>B: forward + Txn-Token (tctx.target team-a/coder)
    B->>IB: POST /v1/exchange<br/>subject_token = Txn-Token, actor_token = B's token
    IB-->>B: delegation token: sub = B, act = {A, act: {user:alice}}
    B->>GW: tools/call with the delegation token
    GW->>GW: policy principals match user alice,<br/>ledger ext.actChain

What you see#

maqpna token inspect decodes a session token and shows its claims; maqpna token verify checks it against the broker's keys; maqpna whoami shows the identity and roles the gateway sees for your admin access token. See maqpna token inspect, maqpna token verify and maqpna whoami.