ADR-008: Anonymity Revoking Dashboard — Production
| Status | Rejected |
| Date | 2026-03-11 |
Context
ADR-007 describes the MVP anonymity revoking dashboard — a working system where authorized users can decrypt transaction data via a web UI, backed by a Lambda that reads the private key from Secrets Manager. The MVP prioritizes speed of delivery and simplicity.
This ADR describes the production version, which addresses two limitations of the MVP:
-
Key exposure in the Lambda runtime. In the MVP, the Lambda function loads the plaintext private key into its own memory to perform decryption. If the Lambda runtime is compromised (via a supply-chain attack, a vulnerability in a dependency, or a misconfigured IAM policy), the attacker obtains the full private key. This is the single most critical risk in the MVP design.
-
Limited dashboard functionality. The MVP provides only a "submit hashes → see results" flow. There is no way for an Anonymity Revoker to review their own past requests, and no way for an admin to audit revoker activity through the dashboard itself (the admin must query DynamoDB directly).
Changes from ADR-007
| Aspect | MVP (ADR-007) | Production (this ADR) |
|---|---|---|
| Key handling | Lambda loads plaintext key from Secrets Manager | Lambda delegates decryption to a Nitro Enclave — the plaintext key never exists outside the enclave |
| Revoker dashboard | Submit hashes, view results | + revoking history with search |
| Admin dashboard | None (AWS console only) | Dedicated admin view: per-revoker activity history and search |
| Backend for history | N/A | Lambda queries DynamoDB audit log |
Proposal
Nitro Enclave for key isolation
The central change is that the anonymity revoking private key never exists in plaintext outside a Nitro Enclave. The Lambda runtime orchestrates requests but cannot access the key material.
How it works
┌──────────────────────────────────────────────────────────┐
│ EC2 instance (parent) │
│ │
│ ┌────────────────┐ ┌────────────────────────────┐ │
│ │ Application │──vsock──▶ Nitro Enclave │ │
│ │ (host-side │ │ ┌──────────────────────┐ │ │
│ │ service) │ │ │ Decryption service │ │ │
│ └────────┬───────┘ │ │ (loads key at boot, │ │ │
│ │ │ │ performs ElGamal │ │ │
│ │ │ │ decryption) │ │ │
│ │ │ └──────────────────────┘ │ │
│ │ └────────────────────────────┘ │
└───────────┼──────────────────────────────────────────────┘
│
┌───────┴───────┐
│ API Gateway │
└───────────────┘
- An EC2 instance runs with Nitro Enclaves enabled. The enclave is launched at boot with a signed enclave image (EIF) that contains only the decryption service.
- At enclave startup, the enclave uses AWS Nitro Enclaves KMS integration to decrypt the anonymity revoking private key. The key is stored in Secrets Manager encrypted under a KMS key, and the KMS key policy allows decryption only when the request originates from an enclave with the expected PCR measurements (the hash of the enclave image). This means neither the EC2 host, nor any IAM principal, nor an AWS admin with console access can decrypt the key — only the attested enclave code can.
- The host-side application receives decryption requests (from API Gateway) and forwards the encrypted payload to the enclave over a vsock channel.
- The enclave decrypts the payload and returns the result over the vsock channel.
- The host-side application writes the audit log to DynamoDB and returns the result to the caller.
Key property: The plaintext private key exists only in the enclave's isolated memory. The host-side application, the EC2 instance's operating system, and any AWS IAM principal cannot read it. Even if the EC2 host is fully compromised, the attacker can only ask the running enclave to decrypt specific payloads (which are logged) — they cannot extract the key itself.
Enclave image and attestation
The enclave image (EIF) is built from a minimal, reproducible Docker image containing only the decryption service binary. The image's PCR values (Platform Configuration Registers — hashes of the enclave code, kernel, and configuration) are recorded at build time and configured in the KMS key policy. If the enclave code is modified, the PCR values change and KMS refuses to release the key.
This provides attestation: the system can cryptographically prove that only a specific, reviewed version of the decryption code has access to the key.
Architecture (updated)
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Frontend │────▶│ API Gateway │────▶│ EC2 + Enclave │────▶│ Secrets Manager │
│ (Vercel) │ │ (REST API) │ │ (host + vsock) │ │ (encrypted key) │
└──────────────┘ └──────┬───────┘ └───────┬──────────┘ └────────┬────────┘
│ │ │ │
│ ┌─────┴──────┐ ┌─────┴──────┐ ┌──────┴──────┐
└─────────────▶│ Cognito │ │ DynamoDB │ │ KMS │
│ (authn/z) │ │ (audit log)│ │ (key policy)│
└────────────┘ └────────────┘ └─────────────┘
Services (changes from ADR-007)
| Service | Role |
|---|---|
| EC2 with Nitro Enclaves | Replaces Lambda. The host-side application handles API requests, forwards encrypted payloads to the enclave for decryption, writes audit logs, and serves history/search queries. |
| Nitro Enclave | Isolated runtime that holds the plaintext private key and performs ElGamal decryption. Communicates with the host only via vsock. Has no network access, no disk, and no external API access. |
| KMS | Wraps the private key. The key policy restricts decryption to requests from enclaves with matching PCR values. This is the enforcement mechanism that prevents key extraction outside the enclave. |
| Secrets Manager | Stores the KMS-encrypted private key blob. The host-side application can read the encrypted blob, but cannot decrypt it — only the attested enclave can. |
| DynamoDB | Unchanged from ADR-007 — stores the audit log. Now also serves as the data source for history and search queries. |
| Vercel, Cognito, API Gateway | Unchanged from ADR-007. |
Dashboard features
Revoker dashboard (all authenticated users)
In addition to the existing "submit hashes → decrypt" flow from ADR-007, each Anonymity Revoker can now view their own revoking history:
- History view: A chronological list of the user's past decryption requests, showing timestamp, transaction hashes, and decrypted results.
- Search: Filter history by transaction hash, date range, or keyword.
- Implementation: The frontend calls a history endpoint on API Gateway. The host-side application queries DynamoDB using the authenticated user's email as the partition key, with optional filters. The query is scoped — a revoker can only see their own history.
Admin dashboard
The admin has a separate view that provides visibility into all revoker activity:
- Per-revoker activity: Select an Anonymity Revoker by email and view their full decryption history.
- Global search: Search across all revokers by transaction hash, date range, or revoker email.
- Implementation: The admin role is distinguished via a Cognito group (e.g.,
admin). The API Gateway / host-side application checks group membership and allows unrestricted DynamoDB queries only for admin-group users. Non-admin users are restricted to their own records.
DynamoDB access patterns
The audit table from ADR-007 supports these queries with the following key structure:
| Access pattern | Key design |
|---|---|
| Revoker views own history (by date) | Partition key: userEmail, sort key: timestamp |
| Admin views revoker's history | Same — query by userEmail partition |
| Search by transaction hash | GSI with partition key: txHash |
| Admin global search by date range | GSI with partition key: a fixed value (e.g., ALL), sort key: timestamp |
Roles (updated)
| Role | Description |
|---|---|
| Admin | Same as ADR-007, plus: manages the KMS key policy and enclave image deployments. Has access to the admin dashboard view. |
| Anonymity Revoker (user) | Same as ADR-007, plus: can view their own revoking history and search past requests. |
User flow (changes from ADR-007)
The decryption request flow is the same from the user's perspective. Under the hood, the Lambda is replaced by the EC2 host + enclave pair — this is transparent to the frontend.
New flows:
- Revoker views history: User navigates to the history tab, sees a paginated list of their past requests with search and date filters.
- Admin reviews activity: Admin navigates to the admin dashboard, selects a revoker (or searches globally), and reviews decryption activity across all users.
AWS security configuration
The production version has a stricter security posture than the MVP due to the enclave-based design.
IAM roles and least-privilege access:
| Principal | Permissions | Notes |
|---|---|---|
| EC2 instance role (host) | secretsmanager:GetSecretValue (scoped to the encrypted key blob ARN), dynamodb:PutItem + dynamodb:Query (scoped to the audit table), invoke API endpoints for chain data | The host can read the encrypted blob but cannot call kms:Decrypt — only the enclave can. |
| Nitro Enclave | kms:Decrypt (scoped to the wrapping KMS key, conditioned on PCR attestation) | The enclave has no IAM role of its own — it uses the instance role's credentials, but the KMS key policy restricts decryption to requests with valid attestation documents. |
| API Gateway | Invoke the host-side application | Cognito authorizer attached, same as ADR-007. |
| Admin (IAM user or role) | Full access to Secrets Manager, KMS key management, Cognito User Pool, DynamoDB audit table, EC2/enclave management | MFA-protected sessions required. Can update KMS key policy and deploy new enclave images. |
| Anonymity Revokers | None — no AWS access | Interact through the frontend. DynamoDB queries for their own history are performed by the host-side application, scoped by their authenticated email. |
Service-level hardening:
- KMS key policy: The critical control. The
kms:Decryptaction is allowed only when the request includes a valid Nitro Enclave attestation document with PCR values matching the approved enclave image. All other principals (including the EC2 instance role, the admin, and root) are deniedkms:Decrypt. The admin retainskms:CreateGrantand key management permissions but cannot decrypt. - Enclave image signing: The EIF should be signed and its PCR values recorded in a version-controlled manifest. Deploying a new enclave image requires updating the KMS key policy with the new PCR values — this is an auditable change.
- Secrets Manager: Stores the KMS-encrypted key blob. Resource policy denies all access except the EC2 instance role (read-only). The blob is useless without KMS decryption, which only the enclave can perform.
- DynamoDB audit table: Same as ADR-007 — point-in-time recovery, deletion protection, append-only IAM policy. Additionally, the admin dashboard queries use read-only access; the host-side application's
PutItempermission is restricted to new records. - Cognito: Same as ADR-007 — no self-signup, short-lived tokens. Admin-group membership is managed exclusively by the admin via the AWS console.
- EC2 instance: Run in a dedicated VPC with no inbound access except from API Gateway. Outbound restricted to AWS service endpoints (Secrets Manager, KMS, DynamoDB) via VPC endpoints (no internet egress). The instance should use an IMDSv2-only configuration (no IMDSv1) to prevent SSRF-based credential theft.
- CloudTrail: Enabled on all KMS, Secrets Manager, and DynamoDB API calls. KMS
Decryptcalls from the enclave will appear in CloudTrail with the attestation context, providing a second audit trail independent of the application-level DynamoDB log. - Network isolation: The Nitro Enclave has no network interface — it communicates only via vsock with the host. This is enforced by the Nitro Enclaves architecture and cannot be overridden by software on the host.
Consequences
What becomes easier:
- The private key cannot be extracted even if the EC2 host is fully compromised — the Nitro Enclave provides hardware-level isolation. This eliminates the single largest risk from the MVP.
- KMS attestation-based key policy means the key can only be used by a specific, reviewed version of the decryption code — provides cryptographic proof of what software has access.
- Anonymity Revokers can review their own past activity, supporting accountability and self-audit.
- Admins can audit all revoker activity through the dashboard without needing to query DynamoDB directly.
What becomes harder:
- Operational complexity increases: EC2 instances with Nitro Enclaves require more management than Lambda (patching, scaling, availability).
- Deploying a new version of the decryption service requires building a new enclave image, recording its PCR values, updating the KMS key policy, and restarting the enclave — a more involved release process than updating a Lambda function.
- The admin must manage KMS key policies and understand enclave attestation — a higher knowledge bar than the MVP's Secrets Manager setup.
What stays the same:
- On-chain contracts and circuits are unchanged.
- The cryptographic scheme (ElGamal on Grumpkin) is unchanged.
- The authentication and authorization model (Cognito + Google + email whitelist) is unchanged.
- The audit log schema and DynamoDB table from ADR-007 are reused — the production version adds GSIs and query endpoints but does not change the data model.
- End-user privacy properties are unaffected.