Skip to main content

Security boundaries

PAP handles credentials and actions on a user's behalf. Treat these as release requirements:

  • Never put tokens, credentials, authorization codes, private keys, assertions, DPoP proofs, or cookies in model context, logs, URLs, analytics, or errors.
  • Validate the exact discovery domain and OAuth issuer relationship.
  • Defend metadata requests against SSRF and DNS rebinding; bound redirects, response bytes, duration, and caching.
  • Generate pairwise user IDs randomly and persist the mapping. Do not derive them from personal data, even with a keyed hash.
  • Keep account tokens encrypted at rest and session tokens in memory where practical.
  • Create a fresh DPoP proof for every request and every retry.
  • Use resource-bound Bearer tokens only for MCP; require DPoP elsewhere.
  • Require exact current-revision approval before confirming an operation.

Open security points lists what is open now: residual risks, what a deployment must provide, and the draft's open questions; the safety demonstrations show the release gate's properties end to end.

Trust boundaries​

BoundaryMinimum control
Remote metadataHTTPS, bounded redirects/bytes/time, strict JSON, SSRF and DNS-rebinding defenses.
Agent identityFetch verified client metadata and JWKS; allow only asymmetric approved algorithms.
Session requestVerify issuer, subject, audience, time window, signature, and single-use jti.
Authorized requestVerify token, DPoP proof, method, URL, nonce, replay ID, resource, and scopes.
Model/tool boundaryPass opaque references, never credentials or private signing material.
Consequential actionApprove and confirm the exact current operation revision.

Secret inventory​

Treat session and account tokens, private keys, client and session assertions, DPoP proofs, authorization codes, device codes, one-time codes, mediated credentials, and cookies as secrets. Redact them before requests reach logging, tracing, error reporting, analytics, or model instrumentation.

Metadata fetching​

Metadata URLs are controlled by the other party. Resolve and inspect every address, reject loopback/private/link-local targets unless explicitly allowed, validate the address actually connected to, and repeat the check for every redirect. Never forward credentials, cookies, or authorization headers.

AgentIdentityClient applies this boundary to personal-agent identity. It fetches a client_id without redirects, requires the returned identifier to equal the fetched URL, and validates same-domain redirect and JWKS URLs. JWKS retrieval permits only bounded same-host redirects and accepts complete public P-256/ES256 or RSA/RS256 signing keys. Symmetric keys and every standard private JWK member are rejected before a key can reach assertion verification.

For Node backends, @datalayer/personal-agent-protocol/server provides createResolvingFetch: it resolves every A and AAAA address of the host, refuses the URL if any is not global unicast (isGlobalAddress: loopback, private, link-local and cloud metadata, CGNAT, ULA, and an IPv4 address carried in IPv6, NAT64 or 6to4 judged as that IPv4 address), and dials only the vetted address over node:https, with the URL's own name for SNI, certificate checks, and Host. The Node company (buildCompany) uses it by default; a host passes it as fetch. NodeUrlSafetyPolicy is the same check without the connection, for assertSafeUrl. Both are deliberately absent from the portable bundle, whose textual check refuses local names (a trailing dot included) and every non-global IP literal. In Python, the policy returns the approved addresses (ApprovedUrl) and the default transport dials only those (PinnedNetworkBackend), with SNI and certificate checks still on the host name; metadata fetches also stop at a byte limit and a total time limit, however slowly the server sends, and carry no cookie or credential.

The same server entry owns TypeScript company-side assertion signing, DPoP verification, and replay-cache contracts. Package tests inspect the emitted portable entry to ensure those symbols and Node DNS APIs are absent. This prevents accidental imports into browser and Canvas bundles; production key custody must still use a KMS/HSM-backed Reactor signer or an equivalent remote signing service.

Credentials stay out of logs, errors, URLs, traces, analytics, and model context​

A canary suite proves it, in both SDKs (test_canaries.py, canaries.test.mjs). The demo worlds run end to end — Manzanita (Device, Direct, and Mediated Sign-In, Account Tokens, conversations, operations, MCP with a Bearer token, the website), Northwind with Ledgerly, every conformance case, and the Pydantic AI examples' tools — with a pap.randomSource that writes CANARY and a counter into every value it mints, and canary passwords. Every credential exchanged is harvested from the wire by kind (Session and Bearer tokens, Account Tokens, DPoP proofs and private keys, client, Session and browser assertions, authorization and device codes, PKCE verifiers, mediated passwords and one-time codes, website cookies), and the suite fails if any of them appears in:

  • a log record of any logger at DEBUG (Python), or a console line (TypeScript);
  • an exception the SDK raises: its message, str/repr or util.inspect, and its attributes (Python 3.12+ records every raise in SDK code through sys.monitoring; older versions record every PersonalAgentProtocolError);
  • a URL any party requests;
  • a trace or an analytics event: every Reactor of the run has a pap.telemetry sink, and each telemetry event is searched as the JSON an analytics pipeline gets and as the span the OpenTelemetry adapter starts (its name, attributes, and status);
  • what a model reads: every response body except the token and device endpoints', each message's text, every PapSignIn.model_view(), and the examples' tool results.

Two values travel in URLs because the protocol puts them there, and neither is requested by an agent: the authorization code, in the redirect to the agent's redirect_uri (OAuth 2.0), and the device user_code, in verification_uri_complete (RFC 8628), which the user's browser opens. The user code may also travel in a message's data for the person's page, never in its text. The one conformance probe that puts a Session Token in a URL on purpose, to see the company refuse it (security.token-in-url-refused), is left out of the run.

The TypeScript suite also reads the npm package as built (lib/, with its source maps): it carries no PEM or JWK private key material, compact JWT, literal Bearer or DPoP token, or canary, and the browser entry (lib/index.js and every module it imports) reaches neither lib/demo nor the demo's fictional credentials.

Current limitations​

The SDKs' defaults are correct in one process. A deployment provides a durable, shared company store (which also holds the replay cache), key custody, and the agent's token, pairwise ID, and authorization-state stores and browser handoff; Open security points lists them with the residual risks.

See the official implementation guides for the complete upstream guidance.

Demonstrations​

Replay, issuer mix-up, cross-tenant access, stale approval, retries, restart, rollback, revocation, and rotation each have one end-to-end test, in both SDKs, against the reference company: see Safety demonstrations.