Skip to main content
A policy with approvalSignals accepts an action only with a signed approval from a trusted approver key, such as a manager approving a refund. This guide shows how to trust a new approver key, rotate one, and revoke one, through the API. Each change is proposed by one person and approved by a different person with a step up signature, and it takes effect on the next request. Nothing is deployed.
Approvers added this way are stored in the approval_issuers table. Approvers listed in the server code (createApprovalIssuerRegistry.ts) still work, and are shown by the same list call, but they change only with a pull request and a deploy.

How it works

Before you begin

You need three people, or at least three roles:
  • The new approver, who will sign approvals. They make their own key pair and never share the private key.
  • A proposer with an API key added as a human.
  • A checker, a different person, with an API key added as a human and a registered step up key. This is the same setup policy approvals use: see Policy lifecycle and approvals.
The deployment needs the migration 20260929120000_add_approval_issuers.sql. Self hosted deployments apply it on start. Otherwise run npm run db:migrate -- apply before deploying this version, as in Production deployment, step 2.

Step 1: The approver makes a key pair

On the approver’s own machine:
This writes manager-priya__manager-priya-key-1.private.pem, which stays on that machine, and manager-priya__manager-priya-key-1.public.pem, which they send to the proposer. Any Ed25519 key works; openssl genpkey -algorithm ed25519 makes one too. approverId and keyId are the names the approver will put in every approval they sign. Use letters, digits, ., _ and -, at most 128 characters. Put a number in the key id, so a later key can have the next one.

Step 2: Propose adding the key

The response is the change, waiting for a checker:
201 Created
The server stores the key in canonical PEM. A key that is not Ed25519 is refused with 400.

Step 3: The checker reviews it

Check the approver and key id, the reason, and that the public key is the one the approver sent you, compared over a channel other than the one the proposer used.

Step 4: The checker approves it

The checker signs a step up authorization on their own machine, naming this change and the action, and sends it with the approval. It is the same step up authorization policy changes use: the change id goes in pendingPolicyChangeId.
200 OK
From the next request on, approvals signed with manager-priya-key-1 verify.

Step 5: Confirm the key is trusted

200 OK
The approver can now sign approvals, with scripts/sign-approval.ts, signApproval() or parmana.crypto.sign_approval(). See Human approval.

Rotate a key

Key ids are used once, so a rotation is two changes:
  1. The approver makes a new key pair with the next key id, such as manager-priya-key-2.
  2. Propose and approve adding it (Steps 2 to 4).
  3. The approver signs new approvals with the new key.
  4. Propose and approve revoking the old key (below).
Approvals signed with the old key keep working until step 4. Approvals are valid for 15 minutes by default, so revoking a few minutes after the switch refuses none that are in use.

Revoke a key

Revoke when an approver leaves the role, or a private key may be exposed. Once the revocation is approved, every approval the key ever signed is refused, including ones not used yet.
A checker then approves it as in Step 4. Only keys added through approver changes can be revoked this way; a key in the server code is revoked by setting revoked: true there and deploying.
A revocation needs a second person, like every change. If a private key is exposed and no checker can be reached, an operator with database access can revoke it directly. This skips maker checker, so the change row must name who did it and why:

Reject a change

A rejection changes no key. The change keeps its rejectionReason.

Errors

Reference