Technical notes

Architecture

A connector for Claude that a bank issues to one customer. Its identity is three hardware keys; the customer binds it with a passkey; the bank attests it, gives it a mandate, and makes it a member of the bank's OpenID Federation - so a partner of the bank can trust it without ever having met it.

The pieces

This Mac                                               The bank (local)
┌───────────────────────────────────────┐             ┌────────────────────────────────────────────────┐
│ Claude Desktop / Claude Code          │             │ PingFederate 13.1.3 + pf-agentic-identity :9131 │
│   └─ connector (MCP, stdio)           │── PAR ─────▶│   authorization server (PAR, PKCE, DPoP)        │
│        ├─ SecureEnclaveSigner.app ──┐ │── token ───▶│   attestation filter: attest_jwt_client_auth    │
│        └─ yk_signer.py (YubiKey) ───┤ │             │   federation intermediate + hosted agents       │
│             keys: fed, inst, dpop   │ │── enrol ───▶│ device-enrolment (Java)  the Client Attester :7200│
│ browser: passkey, consent ◀─────────┘ │── sign in ─▶│ identity provider (passkeys)               :7500 │
│                                       │── publish ─▶│ Trust Mark Issuer                          :7400 │
└───────────────────────────────────────┘── call ────▶│ the bank's API                             :7100 │
                                     │                 └────────────────────────────────────────────────┘
                                     │                 ┌──────────────────────────┐  ┌──────────────────┐
                                     └── call ────────▶│ a partner (partner-rs)   │  │ Trust Anchor     │
                                                       │ :7300, trusts only the   │─▶│ go-oidfed        │
                                                       │ anchor's pinned keys     │  │ lighthouse :8180 │
                                                       └──────────────────────────┘  └──────────────────┘
Component Where What it is
packages/connector this Mac, spawned by Claude the MCP server: the agent's state machine, OAuth client, federation publisher. stdout is the MCP channel; everything else goes to stderr
helpers/se-signer this Mac SecureEnclaveSigner.app, a signed bundle: CryptoKit enclave keys, and App Attest on macOS 27. make bundle seals the connector and Node.js's own build inside it as ClaudeBankConnector.app, and the helper then serves only that connector
helpers/yk-signer this Mac a Python helper over yubikey-manager: keys in PIV slots 82, 83, 84
PingFederate Docker, https://localhost:9131 the bank's authorization server, federation intermediate and host of agent entities. Its RAR processor (au.idp.rar.FedRar) asks the PDP about every authorization_details entry of a mandate. Built from a worktree of pf-agentic-identity (exp/mac-connector)
device-enrolment the Mac, :7200 the bank's Client Attester: verifies the passkey sign-in and the device evidence, mints attestations, registers the agent at PingFederate
bank-idp :7500 the bank's identity provider for enrolment: passkeys (WebAuthn, rpId localhost), OIDC with PKCE. Stands in for PingOne
trust-mark-issuer :7400 issues the bank's customer-agent Trust Mark, and answers its status
bank-api :7100 the bank's own API: DPoP-bound tokens from its AS
partner-rs :7300 a different member of the federation, and a federation entity in its own right. Its enforcement point configures itself by resolving its own entity - the anchor sets its PDPs, the federation's layer, the Trust Marks it requires, the purposes it accepts. Its only trust input is the anchor's public keys
pdp :7600, :7650 a federation-aware AuthZEN PDP, and the federation's own policy layer. It resolves the agent and the resource itself and compares mission, purpose and grant (MISSION.md)
lighthouse Docker, :8180 the Trust Anchor. PingFederate and the Trust Mark Issuer are its subordinates; it lists the issuer in trust_mark_issuers

Three identities, never conflated

Identity Who On the wire
Principal the customer the access token's sub
Agent instance this connector, on this Mac agent_id in the attestation and the token; act.sub = its federation entity, https://localhost:9131/federation/agents/<agent_id>
Agent type the connector software the bank registered OAuth client_id = claude-bank-connector; the agent's software_id in federation metadata

One registered client serves every instance: the attestation says which instance is calling. Delegation, never impersonation - the token always names the customer as sub and the agent as the actor.

Three keys, never cross-used

Key Signs Published as Secure Enclave YubiKey
fed the agent's own Entity Configuration jwks of that Entity Configuration, and of the bank's Subordinate Statement about it no gate, usable after first unlock slot 82, PIN once
inst every attestation PoP: client authentication cnf.jwk of the bank's attestation Touch ID, only while unlocked slot 83, PIN once, touch every time
dpop DPoP proofs nowhere; tokens carry its thumbprint in cnf.jkt no gate, usable after first unlock slot 84, PIN once

A software backend exists for automated tests only (CONNECTOR_ALLOW_SOFTWARE_KEYS=1) and is never selected by default.

Who trusts what

Revocation

The bank does Partners The bank's AS The bank's API
withdraws the Trust Mark refuse on the next call: the federation's layer checks status every time still accept still accept
revokes the hosted entity refuse on the next call: the layer finds no chain refuse the next PAR, token or refresh accept already-issued tokens until they expire (ten minutes)

Choices worth knowing