Back to skill

Security audit

solana-payments-wallet-dev

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent Solana payments skill, but it needs Review because its examples affect real funds and signing credentials while several safety boundaries are under-specified.

Review before installing or using in production. Pin the skill source and npm dependencies, treat all transfer/wrap/unwrap snippets as real-fund operations, validate recipient, mint, amount, decimals, cluster, and serialized transaction contents before signing, and keep Privy app secrets and treasury authorization keys in server-side secret storage only. For nullifiers, use a deterministic or persisted operation identifier for retries rather than generating a fresh random ID each time.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
references/nullifiers.md:39
Finding
Random nullifier identifiers fail to enforce idempotency across repeated business operations<![CDATA[ ## Vulnerability Details **File Location**: `references/nullifiers.md:39-41` and `references/nullifiers.md:143-148` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: Medium ### Vulnerable Code At `references/nullifiers.md:39-41`: ```typescript // Create a unique 32-byte ID (e.g., hash of payment inputs) const id = new Uint8Array(32); crypto.getRandomValues(id); ``` At `references/nullifiers.md:143-148`: ```typescript async function main() { // Generate random 32-byte identifier const id = new Uint8Array(crypto.randomBytes(32)); // Build nullifier instruction const ix = await createNullifierIx(rpc, payer.publicKey, id); ``` ### Technical Analysis A nullifier prevents reuse only when every execution of the same logical operation derives or retrieves the same identifier. The examples generate a new random identifier for each invocation. Consequently, retrying an identical payment after a timeout, process restart, concurrent request, or user resubmission creates a different PDA and does not conflict with the original nullifier. The documented duplicate rejection works only if the same in-memory `id` value is deliberately reused. Randomness ensures uniqueness between invocations, but it does not bind the nullifier to the payment or business operation whose duplicate execution must be prevented. A secure design should derive the nullifier from an immutable, canonical operation identifier, or persist a randomly generated operation ID before the transaction is built and reuse it for every retry. ### Attack Path 1. A payment endpoint or worker creates a random nullifier ID. 2. It submits a transaction containing the nullifier and payment instructions. 3. The payment executes, but the caller receives a timeout or otherwise requests a retry. 4. The application invokes the example flow again and generates a new random ID. 5. The new ID derives a different nullifier PDA, so the on-chain duplicate check succeeds. 6. T ...[truncated 817 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a canonical operation ID before constructing the transaction, such as a database payment ID, invoice ID, or client idempotency key. 2. Derive the nullifier deterministically with explicit domain separation: ```typescript const canonicalInput = [ "my-application", "production", "payment-v1", paymentId, ].join(":"); const id = new Uint8Array( crypto.createHash("sha256").update(canonicalInput).digest() ); ``` 3. If a random operation ID is required, generate it once, persist it atomically with the pending payment record, and reuse it for all retries. 4. Do not derive the ID from ambiguous string concatenation. Use canonical serialization with fixed field ordering and explicit type or length boundaries. 5. Include application, environment, network, and operation-type domains so unrelated actions cannot collide. 6. Enforce a unique constraint on the operation or idempotency key in application storage before transaction submission. 7. Add tests covering concurrent duplicate requests, retries after timeouts, service restarts, and retries after successful on-chain execution. 8. Update the examples to distinguish a unique random nullifier from a deterministic idempotency nullifier and warn that generating a fresh ID on every retry does not prevent duplicate business actions. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:72
Finding
Security-sensitive dependencies are installed through mutable tags and unpinned versions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:72`; `references/payments.md:26`; `references/wallets.md:24`; `references/sign-with-adapter.md:16,57`; `references/sign-with-privy.md:15,70`; `references/nullifiers.md:28` **Vulnerability Type**: `T08: Insecure Dependencies` **Risk Level**: Low ### Vulnerable Code At `SKILL.md:72`, `references/payments.md:26`, and `references/wallets.md:24`: ```bash npm install @lightprotocol/compressed-token@beta @lightprotocol/stateless.js@beta @solana/web3.js @solana/spl-token ``` At `references/sign-with-privy.md:15`: ```bash npm install @privy-io/node @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js ``` At `references/sign-with-privy.md:70`: ```bash npm install @privy-io/react-auth @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js ``` At `references/sign-with-adapter.md:16`: ```bash npm install @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js ``` At `references/sign-with-adapter.md:57`: ```bash npm install @solana-mobile/mobile-wallet-adapter-protocol-kit @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js ``` At `references/nullifiers.md:28`: ```bash npm install @lightprotocol/nullifier-program @lightprotocol/stateless.js@beta ``` ### Technical Analysis The installation commands use mutable `beta` distribution tags or omit versions entirely. The package versions installed by these commands can therefore change after the Skill has been reviewed. A future release under the same command may contain incompatible behavior, a compromised transitive dependency, or malicious installation and runtime logic. This risk is especially relevant because the listed packages construct financial transactions, interact with wallets, transmit transactions to RPC services, and—in the Privy server flow—operate in a process that has access to signing cred ...[truncated 1805 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace mutable tags and unversioned package names with exact, reviewed versions: ```bash npm install --save-exact \ @lightprotocol/compressed-token=<reviewed-version> \ @lightprotocol/stateless.js=<reviewed-version> \ @solana/web3.js=<reviewed-version> \ @solana/spl-token=<reviewed-version> ``` 2. Commit `package-lock.json` or another supported lockfile and use `npm ci` in CI and production builds. 3. Review and approve dependency updates through a controlled pull-request process rather than resolving new releases during deployment. 4. Verify registry provenance, package publisher identity, integrity hashes, and release signatures where available. 5. Audit direct and transitive dependencies with suitable software-composition-analysis tooling. 6. Consider disabling lifecycle scripts during installation where compatible: ```bash npm ci --ignore-scripts ``` 7. Run installation and build jobs in isolated, least-privileged environments without production signing credentials. 8. Keep Privy secrets and treasury authorization material out of development and dependency-installation environments. 9. Validate transaction program IDs, account addresses, mint, recipient, amount, and serialized message content immediately before signing. 10. Prefer stable, audited releases over beta tags for production payment and wallet systems. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (4)

Rp1

Medium
Category
MCP Rug Pull
Confidence
89% confidence
Finding
The skill instructs users to install from an unpinned remote source using `npx skills add Lightprotocol/skills`, which can fetch whatever content is current at execution time. If the upstream repository, package resolution, or dependency chain is compromised, users may ingest altered skill content or tooling without version integrity guarantees.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This documentation provides ready-to-use examples for transfer, wrap, and unwrap flows that directly move tokenized assets, but it does not include explicit safety guidance about validating recipient addresses, token mint, amount, network, or the irreversibility of submitted transactions. In a payments-and-wallets skill, that omission increases the chance that integrators ship unsafe UX or automation that can cause permanent user fund loss through operator error or mis-signing rather than a protocol exploit.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The Node.js example sends a wallet authorization private key to Privy as part of the signing request, which is highly sensitive signing material. Even if this is required by the provider's API, documenting and encouraging this pattern without explicit warnings, scope restrictions, or secret-handling guidance can lead developers to expose treasury signing authority in backend logs, misconfigured environments, or third-party services.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This markdown file includes examples for sending tokens, wrapping from SPL, and unwrapping to SPL, which are user-asset affecting blockchain operations. The document explains how to perform them but does not include any warning about spending funds, network fees, or the irreversibility of submitted transactions.

Static analysis

No suspicious patterns detected.