KEY_PROVIDER=aws-kms, Parmana signs inside AWS KMS. The private key is created in KMS and never leaves it. The server sends KMS the bytes to sign and receives a signature back.
This page takes you from no key to a verified deployment. Do the steps in order. Every step ends with a Check.
What KMS signs, and what stays a file
So a KMS deployment still needs the gateway key file. Only the
default key moves to KMS. See the Environment variable reference.
Only KEY_PROVIDER values local and aws-kms work. azure-key-vault, gcp-kms and hsm pass validation but have no implementation, and the server refuses to start with them.
Before you start
Step 1: Create the key
The key must be an asymmetric Ed25519 signing key. The server accepts no other key spec and stops withKmsSigner only supports ECC_NIST_EDWARDS25519 keys if it finds one.
KeyId in the output. Then give the key the alias the server looks for. The server turns the key id default into alias/default:
AWS::KMS::Key takes the same KeySpec and KeyUsage values, and AWS::KMS::Alias creates the alias.
Check:
ECC_NIST_EDWARDS25519 SIGN_VERIFY Enabled.
The default region for this project is ap-south-1. Use one region everywhere, because AWS_REGION in step 3 must match the key.
Step 2: Create the role that Vercel assumes
The server never holds an AWS access key. On Vercel it exchanges the project’s OIDC token for short lived credentials by assuming an IAM role. Create a role with a trust policy and a permission policy. Trust policy. It lets your Vercel project, and only it, assume the role. This is the format from the Vercel OIDC documentation for AWS. Replace the four values in square brackets:oidc.vercel.com/[TEAM SLUG] must exist in your AWS account first. Create it as the Vercel documentation describes. Give the role the production environment only. Do not add preview, because a preview deployment should not sign with the production key.
Permission policy. Three actions on one key, and nothing else:
Note the role ARN, which looks like
arn:aws:iam::[AWS ACCOUNT ID]:role/[ROLE NAME]. You need it in step 3.
Check: in the IAM console, open the role and confirm the trust policy names your team and project, and the permission policy names your key.
Step 3: Set the environment on Vercel
Set these for the Production environment. The full meaning of each is in the Environment variable reference.
Keep the gateway key set as before, either files in
PARMANA_KEY_DIR or PARMANA_KEY_MATERIAL_JSON. Do not set an AWS access key. The server never reads one.
Turn on OIDC federation for the Vercel project, as the Vercel documentation describes, so the function receives an OIDC token. Then deploy again, because variables are read once at startup.
Check: vercel env ls production lists all three variables.
Step 4: Deploy and check the key is reachable
URL:
/ready should answer {"status":"READY","authDisabled":false}.
On Vercel the app is built on the first real request, not when the module loads, because the OIDC token only exists while a request is being handled. The first request after a cold start therefore does the setup work, including the call to KMS.
Check: /keys/default returns "algorithm": "ed25519". A 500 here means the server could not reach the key. See Troubleshooting.
Step 5: Verify signing
The repository has a script that signs test data through the sameKmsSigner the server uses, on both sides of the 4096 byte limit. It changes nothing in AWS. It makes about seven kms:Sign calls, one kms:GetPublicKey and one kms:DescribeKey per run.
Sign in with a limited user first (see Sign in with a limited user), then run from the repository root:
0 when every check passes. If AWS_ROLE_ARN is set in your shell, the script ignores it and says so, because that variable selects the Vercel OIDC token, which only exists on Vercel.
Check: the last line reads All 7 checks passed.
Step 6: Verify a real record offline
Signing that verifies in a script is not the same as a record that a third party can check. Run one real request as in Integrate Parmana: specification for AI agents. Save the Execution Trust Record it returns, and get the key from the server:pem value into default.public.pem, then verify with only that public key:
"valid": true and "errors": []. Verifying the same record against a different public key must fail. A full record is usually larger than 4096 bytes, so this also proves the commitment path end to end. The rule a verifier you write yourself must follow is in Verify independently.
How messages over 4096 bytes are signed
KMS refuses a raw Ed25519 message longer than 4096 bytes. A full Execution Trust Record is larger than that. Parmana signs a message over the limit as a fixed 97 byte commitment instead: the bytesPARMANA-ED25519-LARGE-MESSAGE-V1, one NUL byte, then the SHA-512 digest of the message. The result is still an ordinary Ed25519 signature. Messages of 4096 bytes or fewer are signed unchanged. No marker is stored on the record, because the choice depends only on the message length. The decision is recorded in ADR-0010 in docs/adr.
Sign in with a limited user
Do not run checks as the AWS account root user. Use an IAM user that can do only what this page needs. A user with the three actions from step 2, on the one key, is enough to runnpm run verify:kms. It does not need iam:*, and it should have no access keys.
Sign in with the console based login, which stores short lived credentials on your machine and needs no access key:
SignInLocalDevelopmentAccess to the user, because aws login needs it.
Check:
Arn should end in user/<your user name> and must not end in :root. Then confirm the user is limited: aws iam list-users --profile <your profile> should fail with AccessDenied.
Run the whole server against KMS locally
To reproduce a full request with KMS signing on your machine, start the server with these settings, on top of the minimum from the production runbook:
The gateway key is still read from
PARMANA_KEY_DIR. A connector that verifies the gateway’s authorization, such as the refund service, must use the same key provider and the same KMS access, or it will report an invalid signature.
Troubleshooting
What has been verified
Verified on 2026-09-20, against a real KMS key (alias/default, ap-south-1, ECC_NIST_EDWARDS25519, enabled), using a limited IAM user with only the three actions above:
Not covered by those runs:
- The Vercel OIDC role. The runs used the local AWS credential chain. ADR-0010 records the live production verification.
- The real
parmana-paytm-agent. The full run used the repository’s mock refund service. - Key rotation and other regions. Neither was tested.
Next
Production deployment runbook
The full procedure to deploy and check a server.
Verify independently
Check a signed record without trusting the server, including the large
message rule.