個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Mandates, not full powers: EUDI Wallet-anchored authorisation for AI agents
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Mandates, not full powers. Plenipo lets a person authorise an AI agent from their European Digital Identity Wallet, and lets any service check that authority before the agent acts.
A plenipotentiary is an envoy who acts for someone and presents credentials to prove it. Plenipo gives AI agents the same thing, with one difference: the powers are never full. Every mandate has a scope, limits and an expiry, every sub-delegation can only narrow it, and the person can revoke it at any time.
Status: 0.5, experimental. The cryptography is real; wallets, PID providers and banks in the examples and the sandbox are test doubles. The code went through security reviews by independent AI review agents (two rounds for 0.3, one for 0.5), and their findings are fixed (see CHANGELOG.md); it has not yet been audited by an external security firm. Do not use it to protect real money or real data yet.
The EU Digital Identity (EUDI) Wallet is the state-recognised identity that every EU Member State must offer its citizens under eIDAS 2. It lets a person prove who they are online with the strength of an ID card at a counter, disclose only the data a service needs, and approve a specific piece of content, such as a payment, with that identity.
The wallet assumes the person is present. AI agents act when the person is not, and today they do it with passwords, API keys or saved cards: full powers, with no limits, no expiry, and no way for a service to know who authorised them. Plenipo lets a person approve a mandate in their EUDI Wallet once (allowed actions and counterparties, spending limits, expiry), lets the agent attach proof of that approval to every request, and lets any service verify it before acting. Sub-delegation can only narrow a mandate, and the person can revoke it at any time.
Person's EUDI Wallet Agent Sub-agent Service (MCP tool)
──────────────────── ───── ───────── ──────────────────
approves the mandate ──root──▶ holds mandate ──delegates──▶ narrower mandate
(PID + key binding over signs invocation ──▶ verifies chain,
the mandate hash) limits, revocation
ALLOW / DENY
urn:eudi:sca:emandate:1 type that certified EUDI wallets render, and let a
representative give a mandate on behalf of a company within the powers of their role.The wire formats and verification rules are specified in docs/spec.md, with test vectors for other implementations. The reasons behind them are in docs/design.md.
npm install plenipo
Node 22 or later. ES modules only.
From a clone of this repository:
npm ci
npm test # forged delegations, x5c, CRL/OCSP, status lists, OpenID4VP, TS12, shared stores, test vectors
# (Postgres and Redis tests run when the servers are available, and skip otherwise)
npm run demo # OpenID4VP approval, agent, sub-agent, a rail operator's MCP server
npm run example:gateway # a bank's HTTP API behind the Plenipo gateway
cd sandbox && docker compose up --build # a bank, a wallet, an agent platform and a live dashboard
The sandbox also runs with Node, has 17 scripted scenarios and an MCP bridge that lets Claude Desktop or Claude Code act as the agent. More in examples/.
The demo prints:
Booking bot: Roma-Milano 18 Oct, 89.00 euro
rail.example: ALLOW, acting for Mario Rossi, chain depth 1
Booking bot: Milano-Roma 20 Oct, first class, 120.00 euro
rail.example: DENY LIMIT_PER_OPERATION, amount 12000 exceeds 10000 on mandate 1
...
The person revokes the mandate; the platform flips its entry in the status list.
rail.example: DENY REVOKED, mandate 0 is revoked in its status list
import { createMandate, delegateMandate, createInvocation, verifyAuthorization, generateAgentKey } from 'plenipo';
const agent = await generateAgentKey();
const mandate = createMandate({
agentJwk: agent.publicJwk,
actions: ['travel.*'],
counterparties: ['*.rail.example'],
limits: { currency: 'EUR', per_operation: 15000, total: 30000 },
ttlSeconds: 7 * 24 * 3600,
maxDepth: 1,
});
// The wallet approves `encodeMandateTransactionData(mandate)` and returns the root.
const chain = { root, delegations: [] };
const invocation = await createInvocation(agent.privateKey, {
chain, // the invocation signs the hash of the chain it is presented with
mandateId: mandate.id, action: 'travel.book', counterparty: 'www.rail.example',
amount: { value: 8900, currency: 'EUR' }, aud: 'mcp://rail.example',
});
const decision = await verifyAuthorization(chain, invocation, {
trustAnchors, // CA certificates from the PID provider trusted lists
mandateStatusTrust, // who signs the mandate status lists
audience: 'mcp://rail.example',
nonces, // single-use nonce store, mandatory
ledger, // atomic spending reservations, needed for total limits
expected: { action: 'travel.book', counterparty: 'www.rail.example', amount: { value: 8900, currency: 'EUR' } },
});
// On ALLOW, commit decision.reservation after acting, or release it if you do not act.
To get the root from a real wallet, createMandateRequest builds the OpenID4VP request
(DCQL for the PID, the mandate as transaction data, encrypted response) and
handleMandateResponse turns the wallet's answer into the root. See
docs/interop.md for what wallets need to support.
On an MCP server, wrap a tool with guardTool from plenipo/mcp. The client sends the chain
and the invocation in the request _meta under plenipo/authorization. The guard must say how
it reads the counterparty and the amount from the tool arguments, or pass null when the tool
has none; it never trusts what the agent declares on its own:
const bookTrain = guardTool<{ route: string; price_cents: number }>(handler, {
action: 'travel.book',
counterparty: () => 'www.rail.example',
amount: (a) => ({ value: a.price_cents, currency: 'EUR' }),
trustAnchors, audience: 'mcp://rail.example', nonces, ledger,
});
| Area | Module | Docs |
|---|---|---|
| Mandates, delegation, invocations, verification | plenipo | spec, design |
| Wallet leg: OpenID4VP | plenipo | interop |
| Experimental: Delegate SD-JWT links, TS12 e-mandate roots, legal persons and roles | plenipo/experimental | delegation, root |
| Trust: x5c chains, EU Lists of Trusted Entities (ETSI TS 119 602), CRL and OCSP | plenipo | trust |
Keys in a KMS or HSM (ExternalSigner) | plenipo | this README |
| Shared stores on Postgres and Redis: ledger, nonces, idempotency keys, sessions, revocation | plenipo | stores |
| Idempotent retries: one signed key per intention, at most one attempt runs | plenipo | spec, integration |
| Bank integration: HTTP gateway, problem+json errors, status list service, audit log, OPA and Cedar export | plenipo | integration |
| Project | What it does | Difference |
|---|---|---|
| IETF Delegate SD-JWT (Google draft) | delegation chains for SD-JWT | no attenuation, revocation or required expiry; Plenipo enforces all three |
| Mastercard Verifiable Intent | layered SD-JWT mandates for commerce | anchored to card issuers; Plenipo is anchored to the EUDI Wallet PID |
| KYA Brasil | agent mandates signed with ICP-Brasil certificates | same idea on Brazil's PKI; Plenipo targets eIDAS 2 |
| Wallet4Agent (Talao) | identity and credential wallet for agents | gives agents a wallet; Plenipo gives people a way to authorise agents |
Plenipo aims to stay compatible with these formats rather than add another one.
See ROADMAP.md. Changes by version are in CHANGELOG.md.
plenipo_mandate and a TS12 e-mandate bound to the PID, with a test PID from the sandbox, and the
sandbox bank enforced them (docs/interop.md). No phone wallet yet: the EUDI
reference wallet core declares accepted types since release 0.31.0; IT-Wallet does not accept
custom types.maxStatusAgeSeconds).Contributions are welcome: read CONTRIBUTING.md. Report vulnerabilities privately as described in SECURITY.md, not in public issues.
Apache 2.0, see LICENSE and NOTICE. Copyright 2026 Fabio Lalli and the Plenipo contributors.
| MCP guard | plenipo/mcp | design |
| Test PID provider, wallet and CA (development only) | plenipo/testing | examples |
| Sandbox and walt.id setup | sandbox/ | sandbox, sandbox/README |