Summary

Timeline: From 2026-02-04 → To 2026-02-10
Languages: Rust, TypeScript

Findings
Total issues: 4 (4 resolved)
Critical: 0 (0 resolved) · High: 1 (1 resolved) · Medium: 1 (1 resolved) · Low: 2 (2 resolved)

Notes & Additional Information
0 notes raised (0 resolved)

Scope

OpenZeppelin conducted a gray-box penetration test of the PSM (private state manager) developed by OpenZeppelin for use with Miden. The audit team was provided full source-code access to the application, enabling both static and dynamic analysis. The scope of this engagement focused on the primary API endpoints and their underlying infrastructure.

The penetration testing was performed on the upgrade-to-v0.13 branch of the OpenZeppelin/private-state-manager repository, at commit 7aa4461.

In scope were the following files:

 crates
├── server/*

Update: All resolutions and the final state of the audited codebase mentioned in this report are contained at commit eb89f8d.

System Overview

The Private State Manager (PSM) is a synchronization and backup layer designed for Miden private accounts. Its primary purpose is to enable a device, or a group of devices, to coordinate state updates securely without requiring trust in other participants or the server operator.

Miden Architecture

Miden is a privacy-focused, zero-knowledge (ZK) rollup designed for high-throughput applications. Unlike traditional blockchains where accounts simply hold balances, Miden uses an actor model where each account is a smart contract that can execute custom logic, store data, and manage assets autonomously.

The system relies on two key concepts:

  • Accounts: Programmable entities that hold assets and execute code. Miden offers strong privacy guarantees by ensuring that the network operator only tracks a cryptographic commitment (hash) to the account data in the public database, rather than the raw state itself.
  • Notes: UTXO-like messages used to exchange data and assets between accounts. Like accounts, notes are programmable and their details are kept private, with only commitments stored on-chain until they are consumed.

Transactions in Miden execute locally on the client device, generating a ZK proof of validity. The network operator verifies this proof and updates the global state database. While this architecture provides superior privacy and scalability, it shifts the burden of state management to the user. Since the canonical state is not public, users must securely manage their own account data and synchronize it across devices, a challenge that the PSM addresses.

Private State Manager (PSM)

The PSM is a specialized service designed to facilitate the secure management and synchronization of off-chain state for Miden accounts. It serves as a privacy-preserving coordination layer that allows users—whether operating from a single device or across multiple endpoints—to maintain a canonical record of their account state and transaction history without exposing sensitive data to the public network or relying on centralized trust for validity.

At its core, the PSM operates on two fundamental data structures:

  • State: Represents the snapshot of an account (including assets, storage, and nonce) at a specific point in time.
  • Delta: Captures the immutable, append-only changes applied to that state.

The system ensures that every state transition is cryptographically linked to the previous commitment, preventing history rewriting or forking.

Account Lifecycle

Before interacting with the PSM, an account must be configured via the /configure endpoint by submitting its initial state and authentication policy. The PSM validates these against the Miden network and stores them locally. To ensure proper synchronization, Miden accounts managed by the system should be created using the PSM client, which incorporates the PSM's key into the account's authentication component.

This design requires valid transactions to carry the PSM's signature, enforcing a flow where users must submit transactions to the PSM, allowing it to update the state and sign before broadcasting to the network. This mechanism is intended to prevent direct interaction with the Miden network that would bypass the PSM, thereby avoiding state desynchronization.

Delta Submission and Canonicalization

Deltas can be submitted directly via /delta or coordinated through the proposal workflow for multi-party accounts. The system supports two canonicalization modes:

  • Candidate Mode: Submitted deltas are stored with a candidate status. A background worker periodically verifies candidates against on-chain state and promotes them to canonical or marks them as discarded.
  • Optimistic Mode: Deltas are marked canonical immediately upon submission, and the state is updated atomically.

The canonicalization process ensures that the PSM's internal state remains consistent with the Miden network's view of the account.

Multi-Party Coordination

For accounts controlled by multiple signers, the PSM provides a proposal workflow:

  1. A proposer submits a delta proposal containing a transaction summary.
  2. Cosigners retrieve pending proposals and append their signatures.
  3. Once the required threshold is reached, any authorized party can promote the proposal to a canonical delta via /delta.
  4. Upon canonicalization, the corresponding proposal is deleted.

This mechanism enables private multisig coordination without exposing internal state to the network.

Architecture

The server provides a comprehensive API that allows clients to register accounts, fetch the latest state, and submit new deltas. For multi-party accounts (such as multisigs), the PSM includes a Delta Proposal workflow. This feature enables coordination between different signers, allowing them to propose a state change, collect the necessary signatures from other participants, and only finalize the delta once the threshold is met.

Interaction with the PSM is secured via a custom authentication scheme using Falcon signatures. Requests must include x-pubkey, x-signature, and x-timestamp headers, ensuring that only authorized parties can query or mutate the state of a specific account.

Below is a summary of the primary API endpoints within the scope of this assessment:

Method Endpoint Description
POST /configure Configures a new account on the PSM with its initial state definition.
POST /delta Submits a new delta (state transition) for an existing account.
GET /delta?account_id=<id>&nonce=<n> Retrieves a specific confirmed delta by its account ID and nonce.
GET /head?account_id=<id> Fetches the latest delta (highest nonce) for an account.
GET /state?account_id=<id> Retrieves the current canonical state of an account.
GET /delta/since?account_id=<id>&nonce=<n> Retrieves all deltas applied since a given nonce (useful for syncing).
PUT /delta/proposal Creates a new delta proposal for multi-party coordination.
POST /delta/proposal/sign Adds a co-signer’s signature to an existing pending proposal.
GET /delta/proposals?account_id=<id> Lists all pending delta proposals for a specific account.

The Storage component is responsible for persisting account snapshots, canonical deltas, and pending proposals. Backends are pluggable, supporting filesystem or database implementations (PostgreSQL).

Security Model and Trust Assumptions

There are few trust assumptions within the PSM, as it is designed to operate without a complex governance or privilege model. However, the security of the execution environment is paramount.

The PSM's Candidate mode relies on a background worker to verify deltas against on-chain state before promoting them to canonical. However, in Optimistic mode, this verification is bypassed: deltas are immediately accepted without confirming the current on-chain commitment. If the blockchain state has advanced since the delta's prev_commitment was captured, the PSM's internal state can silently diverge from the chain. As such, it is assumed that this mode is intended for testing purposes only and will not be enabled in production builds.

It is also assumed that the server infrastructure hosting the PSM is secure and adheres to industry best practices to prevent unauthorized data access or compromise.

The PSM supports two types of storage, using either the filesystem or Postgres. As discussed with the development team, the filesystem should only be used in test environments. Thus, it is assumed that for production environments, the default storage used will be Postgres.

Additionally, it is advisable that the PSM deployment be protected by a Web Application Firewall (WAF). This provides a necessary layer of defense against common web threats, such as injection attacks and Denial of Service (DoS) attempts, ensuring high availability and resilience.

Executed Test Cases

The testing methodology for this assessment was based on established industry standards, including the OWASP Web Security Testing Guide (WSTG) and the OWASP Application Security Verification Standard (ASVS). Custom test cases tailored to the specific business logic and architecture of the APIs were also employed.

Below is a list of additional tests (beyond those outlined in the aforementioned methodologies) that were conducted to assess the security of the PSM:

Result Description
⚠️ Exploitable Attempt to bypass rate limiting by spoofing X-Forwarded-For and X-Real-IP headers
⚠️ Exploitable Attempt to create an account on the PSM without using the PSM key
⚠️ Exploitable Path Traversal testing on filesystem mode
Safe Attempt to configure an account without ownership or authorization
Safe Attempt to configure an account using only its public commitment
Safe Verification of ability to interact with Miden network directly, bypassing the PSM
Safe Verification that all sensitive endpoints strictly enforce authentication
Safe Assessment of header authentication mechanisms for cross-account access
Safe Verification of x-pubkey and x-signature header enforcement
Safe IDOR testing on account information endpoints (/state, /delta, etc.)
Safe Use of malformed payloads in sending proposals, signing, and queries to crash the PSM
Safe Use different co-signers to manipulate the account state
Safe Vulnerability analysis for injection, mainly SQL Injection when using Postgres

High Severity

Arbitrary Proposal Sign via Path Traversal

The sign_delta_proposal function accepts a commitment parameter from the user and uses it directly in file path construction without sufficient validation. Specifically, while the account_id parameter is strictly validated as a hex-encoded field element, the commitment parameter is only checked for the presence of a "0x" prefix, which is stripped if found.

This allows an authenticated attacker to supply a commitment value containing directory traversal sequences (e.g., ../). When this value is used by the FilesystemService to construct the file path for the proposal, it can resolve to a file outside the intended directory for the attacker's account. The vulnerability exists in the get_delta_proposal_path method, which concatenates the user-provided commitment with the base storage path.

An attacker can read and modify delta proposals belonging to other accounts. This violates the intended isolation between tenants in the system. By modifying a proposal file of another account, an attacker can append their own signature to it. While this might not directly compromise the private key of the victim, it corrupts the state of the victim's data and can potentially lead to denial of service or confusion in the proposal process.

The following scenario demonstrates how an attacker (Account A) can modify a proposal belonging to a victim (Account B).

  1. The attacker authenticates as Account A.
  2. The victim (Account B) has a pending proposal with commitment hash TARGET_HASH. The file is stored at .../storage/ACCOUNT_B/proposals/TARGET_HASH.json.
  3. The attacker calls the PUT /delta/proposal endpoint with the following parameters:
    • account_id: ACCOUNT_A
    • commitment: ../../ACCOUNT_B/proposals/TARGET_HASH
    • credentials: Valid signature for Account A.

Note that the attacker must omit the 0x prefix from the target hash in the payload. The server's logic only strips 0x if the entire string starts with it. Since the payload starts with ../, prefix stripping is bypassed, and .json is appended directly.

  1. The server constructs the path: .../storage/ACCOUNT_A/proposals/../../ACCOUNT_B/proposals/TARGET_HASH.json
  2. The file system resolves this path to the victim's proposal file. The server reads the file, appends the attacker's signature, and writes it back, successfully modifying the victim's data.

Consider validating the commitment parameter to ensure that it is a valid hex string representing a hash before using it in file path operations. Alternatively, consider using a safe path-joining mechanism that prevents traversal outside the intended directory.

Update: Resolved in pull request #133.

Medium Severity

Missing PSM Commitment Validation on Account Registration

The PSM fails to maximize security by not validating that the account being registered actually lists the PSM as its authorized co-signer. When processing a configure request, the server correctly verifies the user's signature against the account's public key (in Slot 0 or 1) using the validate_credential method.

However, it does not inspect the contents of Slot 5 (where the psm_commitment is stored). This allows an attacker to create a valid Miden account where Slot 5 contains a public key controlled by the attacker themselves, rather than the PSM's key, and still successfully register this account with the PSM service.

An attacker can utilize the PSM as a state-tracking and data-availability service without being bound by its security policies. Since the attacker holds the private keys for both the user slot and the "fake" PSM slot (Slot 5), they can construct and sign transactions completely offline, bypassing any co-signing logic intended by the PSM operator. The PSM becomes an unwitting observer of an account it cannot control.

Consider adding a validation step in configure_account that explicitly reads the commitment from Slot 5 of the provided initial_state. The server should compare this commitment against the commitment of its own public key and reject the configuration request if they do not match.

Update: Resolved in pull request #134.

Low Severity

Rate Limit Bypass via Proxy Header Spoofing

The rate-limiting middleware in extract_client_ip trusts both X-Forwarded-For and X-Real-IP headers without validating that the request came from a trusted proxy.

When the server is exposed directly to the internet (bypassing any reverse proxy), an attacker can spoof these headers with different fake IP addresses on each request, effectively bypassing per-IP rate limiting entirely. With a default burst limit of 10 requests per second per IP, an attacker could achieve significantly higher throughput by varying the spoofed header.

The severity depends on deployment configuration. If the server is always deployed behind a reverse proxy that strips or overwrites client-supplied proxy headers, this vulnerability is not exploitable.

Consider implementing a trusted proxy configuration that only trusts X-Forwarded-For and X-Real-IP headers when the direct connection IP matches a configured list of trusted proxy addresses. Alternatively, consider documenting that deployment behind a header-sanitizing reverse proxy or WAF (Web Application Firewall) is a security requirement.

Update: Resolved in pull request #136. The team stated:

Our plan for the production deployment is to actually setup a proxy in the server which will be whitelisted using PSM_TRUSTED_PROXY_IPS env var, this way X-Forwarded-For and X-Real-IP can be trusted_

Memory Exhaustion via Delta Proposals

The push_delta_proposal function allows users to submit delta proposals without restricting the number of pending proposals. Each proposal is stored as a separate file on the server. The get_delta_proposals function subsequently reads all proposals for an account into memory simultaneously. An attacker can exploit this by submitting a large number of proposals, leading to excessive memory consumption which can crash the server process.

The push_delta_proposal function only checks for pending candidate deltas but does not limit the number of pending proposals in the proposals endpoint. This allows an attacker to continuously submit new proposals as long as they provide unique transaction summaries.

This vulnerability can be leveraged to perform a DoS attack against the Private State Manager. By exhausting server memory, an attacker can crash the server or prevent legitimate users from interacting with the service. The cost of the attack is relatively low for an authenticated user.

Consider implementing a limit on the number of pending proposals allowed per account. When this limit is reached, the server should reject new proposal submissions until existing ones are processed or discarded. Additionally, consider implementing pagination in the GET /delta/proposals request.

Update: Resolved in pull request #135.

Conclusion

The assessment identified a total of 4 vulnerabilities, comprising 1 high-severity, 1 medium-severity, and 2 low-severity issues. The PSM server presents a concise and well-structured API that correctly implements authentication mechanisms effectively, mitigating major risks associated with multi-account compromise.

The recommended fixes are sufficient to bring the PSM to a production-ready state. Given the nature of the findings and the clarity of the remediation path, a re-audit following the fix review is not deemed necessary.

The OpenZeppelin development team is commended for their exceptional cooperation throughout the engagement. They were highly responsive and engaged in extensive discussions regarding the PSM architecture, potential risks, and its integration with the Miden network, which significantly facilitated the assessment process.

Appendix

Issue Classification

OpenZeppelin classifies smart contract vulnerabilities on a 5-level scale:

  • Critical
  • High
  • Medium
  • Low
  • Note/Information

Critical Severity

This classification is applied when the issue’s impact is catastrophic, threatening extensive damage to the client's reputation and/or causing severe financial loss to the client or users. The likelihood of exploitation can be high, warranting a swift response. Critical issues typically involve significant risks such as the permanent loss or locking of a large volume of users' sensitive assets or the failure of core system functionalities without viable mitigations. These issues demand immediate attention due to their potential to compromise system integrity or user trust significantly.

High Severity

These issues are characterized by the potential to substantially impact the client’s reputation and/or result in considerable financial losses. The likelihood of exploitation is significant, warranting a swift response. Such issues might include temporary loss or locking of a significant number of users' sensitive assets or disruptions to critical system functionalities, albeit with potential, yet limited, mitigations available. The emphasis is on the significant but not always catastrophic effects on system operation or asset security, necessitating prompt and effective remediation.

Medium Severity

Issues classified as being of medium severity can lead to a noticeable negative impact on the client's reputation and/or moderate financial losses. Such issues, if left unattended, have a moderate likelihood of being exploited or may cause unwanted side effects in the system. These issues are typically confined to a smaller subset of users' sensitive assets or might involve deviations from the specified system design that, while not directly financial in nature, compromise system integrity or user experience. The focus here is on issues that pose a real but contained risk, warranting timely attention to prevent escalation.

Low Severity

Low-severity issues are those that have a low impact on the client's operations and/or reputation. These issues may represent minor risks or inefficiencies to the client's specific business model. They are identified as areas for improvement that, while not urgent, could enhance the security and quality of the codebase if addressed.

Notes & Additional Information Severity

This category is reserved for issues that, despite having a minimal impact, are still important to resolve. Addressing these issues contributes to the overall security posture and code quality improvement but does not require immediate action. It reflects a commitment to maintaining high standards and continuous improvement, even in areas that do not pose immediate risks.