Back to skill

Security audit

Poseidon OTC

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it can move real Solana funds with a hot wallet and has under-scoped authorization and secret-handling risks users should review carefully.

Install only if you are comfortable using a dedicated low-balance burner wallet on Solana mainnet. Treat all autonomous actions as capable of moving funds, avoid storing valuable private keys in environment files or logs, and require your own approval/spending controls around offer updates, confirmations, claims, cancellations, and execution. Review the Poseidon API trust boundary before using this for valuable trades.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
src/index.ts:241
Finding
Reusable Identity Secrets Are Disclosed to the Remote API and Public Blockchain## Vulnerability Details **File Location**: `src/index.ts:241-246`, `src/index.ts:425-433`, `src/index.ts:576-592`, `src/index.ts:770-781`, `src/index.ts:905-913`, `src/index.ts:1148-1185`, and `src/index.ts:1234-1288` **Vulnerability Type**: Sensitive authentication material disclosure **Risk Level**: High ### Vulnerable Code Room creation sends Party A's identity secret to the configured API: ```typescript const body = { roomId, numericRoomId: numericId, partyAIdentitySecret: identitySecret, partyAIdentityHash: identityHash, expiresIn: options.expiresIn || 3600, }; ``` Joining a room sends Party B's identity secret to the API: ```typescript const body = { wallet: this.wallet.publicKey.toBase58(), partyB: this.wallet.publicKey.toBase58(), partyBIdentitySecret: identitySecret, txSignature, signature: sig, ts, }; ``` Cancellation sends both the participant identity secret and Party A's identity secret: ```typescript const res = await fetch( `${this.config.apiUrl}/api/trade-rooms/cancel-onchain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomId, numericRoomId: room.numericRoomId, partyAIdentityHash: room.partyAIdentityHash, partyAIdentitySecret: room.partyAIdentitySecret, isPartyA, identitySecret, partyASlots, partyBSlots, }), } ); ``` Claiming locked tokens also sends the identity secret to the service: ```typescript const res = await fetch( `${this.config.apiUrl}/api/trade-rooms/claim-locked`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomId, numericRoomId: room.numericRoomId, partyAIdentityHash: room.partyAIdentityHash, isPartyA, identitySecret, tokenSlots, }), } ); ``` The same secre ...[truncated 3489 chars]
Remediation
## Remediation Suggestions 1. Stop including `partyAIdentitySecret` and `partyBIdentitySecret` in API request bodies and API responses. 2. Never place reusable confidential values in Solana transaction instruction data. 3. Replace preimage-based authorization with wallet signatures over canonical, operation-specific payloads. 4. Bind each signature to: - The protocol and network. - The exact action. - The room ID. - The participant role. - All token, recipient, lockup, and transaction parameters. - A server-issued nonce. - A short expiration time. 5. Enforce one-time nonce consumption and participant-role validation on the server. 6. If identity commitments are a protocol requirement, use a construction that proves knowledge without revealing a reusable preimage. 7. Store any unavoidable client-side secret in protected local storage and erase it after the room lifecycle ends. 8. Remove identity-secret fields from `TradeRoom` responses and redact them from all application and infrastructure logs. 9. Rotate or invalidate existing room secrets because previously submitted transaction data may remain publicly accessible. 10. Document the trust boundary introduced by the API and relayer instead of describing the complete workflow as requiring no trust.

T09 · Insecure Skill Coding Practices

Error
Location
src/index.ts:1720
Finding
State-Changing API Calls Lack Cryptographic Caller Authentication## Vulnerability Details **File Location**: `src/index.ts:1720-1732`, `src/index.ts:1746-1810`, and `src/index.ts:1825-1831` **Vulnerability Type**: Missing authorization proof on privileged API operations **Risk Level**: High ### Vulnerable Code `addWitness()` sends a forgeable wallet address without a signature: ```typescript const res = await fetch( `${this.config.apiUrl}/api/trade-rooms/${roomId}/add-witness`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ requesterWallet: this.wallet.publicKey.toBase58(), witnessWallet, }), } ); ``` `executeSwap()` can call the relayer-backed execution endpoint without requiring a configured wallet or participant signature: ```typescript const res = await fetch( `${this.config.apiUrl}/api/trade-rooms/execute-swap`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomId, numericRoomId: room.numericRoomId, partyAIdentityHash: room.partyAIdentityHash, partyASlots, partyBSlots, }), } ); ``` `markExecuted()` accepts only an attacker-supplied transaction signature: ```typescript const res = await fetch( `${this.config.apiUrl}/api/trade-rooms/${roomId}/mark-executed`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ txSignature }), } ); ``` ### Technical Analysis These endpoints mutate room state or request privileged relayer activity, but the client requests contain no cryptographic proof of the caller's identity or authorization. A wallet address is public information and is not an authentication credential. Therefore, `requesterWallet` in `addWitness()` can be copied or replaced by any direct API caller. The method is documented as “Party A only,” yet the request does not prove possession of Part ...[truncated 2976 chars]
Remediation
## Remediation Suggestions 1. Require authenticated participant authorization for every state-changing endpoint. 2. Reject bare wallet-address claims; require a valid signature from the claimed wallet. 3. Sign a canonical request containing the action, room ID, complete body, nonce, and expiration. 4. For `addWitness()`, verify that the signer is Party A before modifying the room. 5. For `executeSwap()`: - Require the caller to be Party A or Party B. - Verify both required confirmations from authoritative on-chain state. - Reconstruct token slots and recipient accounts server-side. - Do not trust caller-supplied amounts, token standards, or recipient accounts. - Confirm that the room has not been cancelled, completed, or expired. 6. For `markExecuted()`: - Restrict access to the trusted relayer or remove the public endpoint. - Fetch and validate the transaction from Solana. - Verify successful finalization, expected program ID, room PDA, instruction type, token movements, and recipient accounts. - Make the update idempotent. 7. Add server-side authorization tests covering forged wallet fields, nonparticipants, replayed requests, altered bodies, stale timestamps, and unrelated transaction signatures. 8. Apply rate limiting and security logging to relayer-backed operations.

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:158
Finding
Authentication Signatures Are Not Bound to the Requested Action or Parameters## Vulnerability Details **File Location**: `src/index.ts:158-174` and `src/index.ts:216-226` **Vulnerability Type**: Replayable and context-insufficient authentication signature **Risk Level**: Medium ### Vulnerable Code The default authentication message identifies only the room, timestamp, and generic action: ```typescript function buildAuthMessage( roomId: string, timestamp: number, action: string = 'Authenticate' ): string { return `Poseidon OTC Trade Room Room: ${roomId.slice(0, 8)}...${roomId.slice(-4)} Action: ${action} By signing, you confirm wallet ownership. This signature is valid for this session. [${roomId}:${timestamp}]`; } ``` Most requests use the default generic action and return the signature as reusable authentication metadata: ```typescript private async authHeaders(roomId: string) { if (!this.wallet) { return { 'Content-Type': 'application/json' }; } const ts = Date.now(); const msg = buildAuthMessage(roomId, ts); const sig = this.sign(msg); return { 'Content-Type': 'application/json', 'X-Wallet': this.wallet.publicKey.toBase58(), 'X-Signature': sig, 'X-Timestamp': ts.toString(), }; } ``` The same generic message pattern is used before multiple HTTP and WebSocket actions: ```typescript const ts = Date.now(); const msg = buildAuthMessage(roomId, ts); const sig = this.sign(msg); ``` ### Technical Analysis A secure request signature must commit to the complete security-relevant context of the operation. The current signature generally commits only to: - A room ID. - A client-generated timestamp. - The generic word `Authenticate`. It does not commit to: - The HTTP method or endpoint. - The requested action. - The request body. - Token mint addresses or amounts. - The confirmation stage. - The recipient wallet. - Lockup duration. - WebSocket message type. - A server-issued, s ...[truncated 2279 chars]
Remediation
## Remediation Suggestions 1. Replace generic room authentication signatures with action-specific request signatures. 2. Define a deterministic canonical payload containing: - Protocol name and version. - Solana network or chain identifier. - HTTP method or WebSocket message type. - Endpoint or action name. - Full room ID without display truncation. - Hash of the complete normalized request body. - Wallet address and participant role. - Server-issued nonce. - Issued-at time and short expiration. 3. Verify the canonical payload server-side before processing the request. 4. Issue unpredictable nonces from the server and atomically mark each nonce as consumed. 5. Use a short validity period measured in minutes rather than a 24-hour reusable session signature. 6. Reject signatures used with a different endpoint, body, room, wallet, or action. 7. Ensure WebSocket messages use unique signed payloads for `subscribe`, `update-offer`, `confirm`, `propose-lockup`, `accept-lockup`, and `execute`. 8. Avoid placing signatures in query strings because URLs are commonly recorded in access logs, browser history, monitoring tools, and intermediary systems. Use authorization headers or request bodies protected by TLS instead. 9. Add negative tests demonstrating rejection of replayed signatures, altered parameters, cross-endpoint reuse, expired timestamps, and duplicate nonces.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (22)

Missing User Warnings

Critical
Confidence
96% confidence
Finding
executeSwap is the highest-risk action: it finalizes the asset exchange by sending room metadata and derived recipient token accounts to an API that performs the swap, yet there is no explicit final consent step in this code. In a wallet-enabled agent, a malicious prompt or compromised integration could cause the agent to execute the final exchange of escrowed assets irreversibly, making this especially dangerous in the context of a token-swapping skill.

Missing User Warnings

High
Confidence
92% confidence
Finding
updateOffer triggers depositOnchain, which can move user assets on-chain immediately, including wrapping SOL and transferring funds into escrow, without any built-in second-step confirmation, preview, or warning at the skill layer. In an autonomous-agent context, a prompt or upstream caller could invoke this action with attacker-chosen token mints and amounts, causing unintended irreversible blockchain transfers.

Missing User Warnings

High
Confidence
90% confidence
Finding
cancelRoom can initiate refund/cancel flows for deposited assets via a backend-assisted on-chain cancellation endpoint without an explicit safety interlock at the skill boundary. Because it sends identity secrets and slot/account metadata to the API to drive asset-moving logic, misuse or prompt injection against an autonomous agent could disrupt trades or trigger unexpected fund movements.

Missing User Warnings

High
Confidence
88% confidence
Finding
claimLockedTokens invokes an API endpoint that can release/transfer received assets after lockup, but the skill does not include an explicit confirmation or warning that this is an asset-moving operation. In an autonomous setting, this can be abused to trigger irreversible claims to configured receive wallets without the user realizing the transfer is being executed now.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README documents an autonomous mode that accepts a burner private key and then shows trade-advancing methods such as createRoom, joinRoom, updateOffer, and confirmTrade without prominently warning that this mode can cause real on-chain actions and movement of funds without manual wallet review. In a trading skill for Solana, understated documentation around autonomous execution materially increases the risk of accidental fund loss, unsafe agent automation, or users enabling hot-wallet trading without understanding the consequences.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The environment variable example instructs users to place a raw base58 private key in POSEIDON_BURNER_KEY, but does not sufficiently warn about the risks of storing long-lived secrets in environment variables, shell history, logs, deployment configs, or shared CI/CD systems. Because this skill can execute token trades on Solana, compromise of that key could directly lead to unauthorized transactions and theft of wallet funds.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill requires environment access for a private key and network access to external HTTP/WebSocket endpoints, but it does not declare any explicit tool scope or permission boundaries. In an agent ecosystem, this omission weakens operator visibility and policy enforcement, making it easier for the skill to access secrets and move funds without clear upfront authorization controls.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill advertises agent-to-agent commerce and autonomous trading near the top-level usage guidance without a prominent warning that the agent may sign transactions, lock tokens in escrow, and complete irreversible on-chain swaps. This can mislead deployers into enabling the skill without appreciating that it can directly commit funds from the configured wallet.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The quick-start walks directly from room creation to confirmations and swap execution, while the overall flow also references escrow deposits, but it does not place an immediate warning before these steps that tokens may be moved irreversibly on-chain. Because the skill uses a private key from the environment, a user or agent following the example can authorize real fund movement with insufficient friction or risk disclosure.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
withdrawFromOffer performs on-chain withdrawal transactions directly based on room state and user-supplied token parameters, again without an explicit warning or confirmation step. While this is usually less dangerous than arbitrary deposits because it tends to return escrowed assets, it still causes blockchain state changes and can fail or behave unexpectedly if parameters are stale or manipulated.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
confirmTrade sends an on-chain confirmation transaction with no explicit user-facing warning that it advances trade state and may contribute to making the swap executable. In a trading skill, state-transition transactions are security-sensitive because an agent can be socially engineered into finalizing a trade the user did not intend to approve.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The manifest describes the skill as executing trustless P2P token swaps with room creation, negotiation, escrow, atomic swaps, and WebSocket updates. The code additionally supports adding a third-party witness to observe a trade, which introduces participant/observation workflow beyond the described two-party swap functionality.

Context-Inappropriate Capability

Low
Confidence
80% confidence
Finding
The documentation promotes agent-to-agent room sharing, autonomous negotiation, confirmation, and execution using a hot wallet, which expands the blast radius from simple assisted swaps to delegated fund control. Even if aligned with the product goal, unrestricted autonomous behavior can cause unintended trades, manipulation by counterparties, or loss from flawed agent logic when no human review gate is required.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"url": "https://github.com/poseidon-cash/poseidon-otc-skill/issues"
  },
  "dependencies": {
    "@solana/web3.js": "^1.87.6",
    "@solana/spl-token": "^0.3.9",
    "bs58": "^5.0.0",
    "tweetnacl": "^1.0.3",
Confidence
92% confidence
Finding
The dependency is specified with a caret range, which allows newer semver-compatible releases to be installed without explicit review. In a wallet/trading skill that handles Solana transactions, this raises supply-chain risk because a compromised or vulnerable upstream release could be pulled into builds and affect transaction integrity, availability, or key-handling code paths.

Unverifiable Dependency: @solana/web3.js has 3 known advisory(ies) (CVE-2024-30253 (Handling untrusted input can result in a crash, leading to loss of availability ); CVE-2024-54134 (Modified package published to npm, containing malware that exfiltrates private k); MAL-2024-11183 (Malicious code in @solana/web3.js (npm))), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding
The manifest references @solana/web3.js with a non-exact version while known advisories exist for some releases, including malware and denial-of-service concerns. Because this skill performs Solana OTC swaps and may process keys, addresses, and transactions, inability to verify the exact installed version materially increases the risk of wallet compromise, malicious exfiltration, or service disruption.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "@solana/web3.js": "^1.87.6",
    "@solana/spl-token": "^0.3.9",
    "bs58": "^5.0.0",
    "tweetnacl": "^1.0.3",
    "js-sha3": "^0.9.3"
Confidence
90% confidence
Finding
Using a version range for @solana/spl-token permits unreviewed updates during installation. Because this skill is for token swaps and escrow on Solana, dependency drift in token-handling libraries can directly affect asset transfer logic and increase the blast radius of a malicious or flawed upstream release.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "@solana/web3.js": "^1.87.6",
    "@solana/spl-token": "^0.3.9",
    "bs58": "^5.0.0",
    "tweetnacl": "^1.0.3",
    "js-sha3": "^0.9.3"
  },
Confidence
86% confidence
Finding
The bs58 package is not pinned to an exact version, so future installs may resolve to different releases than were originally tested. While lower risk than blockchain transaction libraries, it still participates in key/address encoding workflows and could contribute to malformed transactions or supply-chain compromise.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@solana/web3.js": "^1.87.6",
    "@solana/spl-token": "^0.3.9",
    "bs58": "^5.0.0",
    "tweetnacl": "^1.0.3",
    "js-sha3": "^0.9.3"
  },
  "devDependencies": {
Confidence
91% confidence
Finding
tweetnacl is a cryptographic dependency, and allowing semver drift increases the risk of pulling in an unexpected release that changes security-sensitive behavior or is maliciously published. In a P2P trading skill, cryptographic correctness is central to signing, verification, and trust boundaries, so supply-chain issues here are particularly concerning.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@solana/spl-token": "^0.3.9",
    "bs58": "^5.0.0",
    "tweetnacl": "^1.0.3",
    "js-sha3": "^0.9.3"
  },
  "devDependencies": {
    "typescript": "^5.3.3",
Confidence
84% confidence
Finding
js-sha3 is an unpinned cryptographic/hash-related dependency, which creates nondeterministic builds and avoidable supply-chain exposure. Even if used only for hashing, a compromised or incompatible update could affect protocol message integrity or transaction-related derivations.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"js-sha3": "^0.9.3"
  },
  "devDependencies": {
    "typescript": "^5.3.3",
    "@types/node": "^20.10.0"
  },
  "engines": {
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "typescript": "^5.3.3",
    "@types/node": "^20.10.0"
  },
  "engines": {
    "node": ">=18.0.0"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Description-Behavior Mismatch

Low
Confidence
80% confidence
Finding
The manifest description focuses on creating rooms, negotiating offers, locking tokens, and executing atomic swaps. This code adds a separate room termination operation via a dedicated API endpoint, which is a broader room-management capability not clearly captured by the stated description.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/index.ts:29