Back to skill

Security audit

Solana Connect

Security checks for vulnerabilities and agentic risk

Overview

This Solana skill can move real funds, but its private-key and approval safeguards are weaker than its documentation claims.

Review this carefully before installing. Do not provide a funded wallet private key to this skill unless the runtime is isolated and trusted, keep dryRun enabled by default, avoid skipConfirmation for live transfers, use only trusted RPC endpoints, and prefer a hardware wallet, custody service, or external signer rather than passing private keys through an agent-facing API.

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
scripts/solana.js:149
Finding
Human-confirmation and mainnet safeguards can be bypassed through an agent-controlled option<![CDATA[ ## Vulnerability Details **File Location**: `scripts/solana.js:149-175`, `scripts/solana.js:233` **Vulnerability Type**: Security control bypass **Risk Level**: High ### Vulnerable Code ```javascript async function sendSol(privateKey, toAddress, amount, options = {}) { const { dryRun = true, skipConfirmation = false } = options; // SECURITY: Validate inputs if (!privateKey) { throw new Error('Private key is required'); } if (!toAddress || toAddress.length < 32) { throw new Error('Invalid recipient address'); } if (amount <= 0) { throw new Error('Amount must be positive'); } // SECURITY: Check max limits if (amount > MAX_SOL_PER_TX) { throw new Error(`Amount ${amount} SOL exceeds max limit of ${MAX_SOL_PER_TX} SOL`); } // SECURITY: Check testnet (warn if mainnet) const connection = getConnection(DEFAULT_RPC); if (!isTestNet(DEFAULT_RPC) && dryRun === false && !skipConfirmation) { console.warn('⚠️ WARNING: Running on MAINNET with real transactions!'); } // SECURITY: Require human confirmation for large amounts if (amount >= REQUIRE_HUMAN_CONFIRMATION && !skipConfirmation && dryRun === false) { throw new Error(`Amount ${amount} SOL requires human confirmation (threshold: ${REQUIRE_HUMAN_CONFIRMATION} SOL)`); } ``` The transaction is subsequently broadcast without any independent authorization check: ```javascript // Send real transaction const txSignature = await connection.sendRawTransaction(transaction.serialize()); ``` The bypass is also explicitly documented in `SKILL.md:91-94`: ```javascript // Skip human confirmation (for automated agents) await sendSol(key, to, amount, { dryRun: false, skipConfirmation: true }); ``` ### Technical Analysis The purported human-confirmation control is only a Boolean condition controlled by the same caller requesting the transaction. Setting `skipConfirmation` to `true` disables the large-transfer rejection. It also suppresses the mainnet ...[truncated 1864 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `skipConfirmation` from all agent-controlled or generally accessible API parameters. 2. Implement an out-of-band approval service operated outside the agent’s trust boundary. 3. Require a short-lived, cryptographically signed approval token for transactions at or above the threshold. 4. Bind each approval to the network, sender, recipient, exact lamport amount, recent blockhash, expiration time, and a unique nonce. 5. Reject token reuse and fail closed if approval verification is unavailable. 6. Enforce a separate mainnet policy. Do not treat a warning as an authorization control. 7. Add cumulative limits, rate limits, recipient allowlists, and configurable daily spending limits. 8. Separate simulation and broadcast into distinct APIs, with the broadcast API requiring stronger authorization. 9. Add tests proving that caller-controlled options cannot bypass confirmation or mainnet restrictions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/solana.js:140
Finding
Raw private keys cross the agent-facing API boundary despite key-isolation claims<![CDATA[ ## Vulnerability Details **File Location**: `scripts/solana.js:58-78`, `scripts/solana.js:140-181` **Vulnerability Type**: Insecure sensitive-key handling **Risk Level**: Medium ### Vulnerable Code ```javascript function connectWallet(privateKeyBase58) { if (!privateKeyBase58) { throw new Error('Private key is required'); } try { // Decode private key const privateKeyBytes = bs58.decode(privateKeyBase58); const keyPair = nacl.sign.keyPair.fromSeed(privateKeyBytes.slice(0, 32)); const publicKey = bs58.encode(keyPair.publicKey); // SECURITY: Only return address, never the private key return { address: publicKey }; } catch (e) { throw new Error(`Invalid private key: ${e.message}`); } } ``` The transaction API similarly requires the caller to provide the raw key: ```javascript async function sendSol(privateKey, toAddress, amount, options = {}) { const { dryRun = true, skipConfirmation = false } = options; // SECURITY: Validate inputs if (!privateKey) { throw new Error('Private key is required'); } if (!toAddress || toAddress.length < 32) { throw new Error('Invalid recipient address'); } if (amount <= 0) { throw new Error('Amount must be positive'); } // SECURITY: Check max limits if (amount > MAX_SOL_PER_TX) { throw new Error(`Amount ${amount} SOL exceeds max limit of ${MAX_SOL_PER_TX} SOL`); } // SECURITY: Check testnet (warn if mainnet) const connection = getConnection(DEFAULT_RPC); if (!isTestNet(DEFAULT_RPC) && dryRun === false && !skipConfirmation) { console.warn('⚠️ WARNING: Running on MAINNET with real transactions!'); } // SECURITY: Require human confirmation for large amounts if (amount >= REQUIRE_HUMAN_CONFIRMATION && !skipConfirmation && dryRun === false) { throw new Error(`Amount ${amount} SOL requires human confirmation (threshold: ${REQUIRE_HUMAN_CONFIRMATION} SOL)`); } try { // Create keypair from ...[truncated 2541 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace raw private-key parameters with opaque signer or key identifiers. 2. Perform signing in a separate trusted process, hardware wallet, HSM, operating-system keystore, or dedicated custody service. 3. Expose only a narrowly scoped signing interface that receives a fully described transaction and independently enforces policy. 4. Prevent keys from entering prompts, tool arguments, logs, traces, error messages, or persistent agent memory. 5. Zero temporary key buffers where practical and minimize their lifetime if local key handling is unavoidable. 6. Make wallet generation an explicit secure provisioning workflow. Store the secret in a protected keystore or provide a one-time, authenticated backup channel outside the agent interface. 7. Update documentation to accurately describe the trust boundary and key-handling model. 8. Add tests and logging filters that detect accidental secret propagation without recording the secret itself. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/solana.js:36
Finding
Unrestricted RPC endpoint selection enables SSRF-style internal network requests<![CDATA[ ## Vulnerability Details **File Location**: `scripts/solana.js:36-38`, `scripts/solana.js:81-103`, `scripts/solana.js:120-127`, `scripts/solana.js:284-293` **Vulnerability Type**: Server-side request forgery and untrusted RPC endpoint usage **Risk Level**: Medium ### Vulnerable Code ```javascript /** * Get Solana RPC connection */ function getConnection(rpcUrl = DEFAULT_RPC) { return new Connection(rpcUrl, 'confirmed'); } ``` Public query functions accept the endpoint directly without validation: ```javascript async function getBalance(address, rpcUrl = DEFAULT_RPC) { const connection = getConnection(rpcUrl); try { const balanceLamports = await connection.getBalance(new PublicKey(address)); const balanceSol = balanceLamports / LAMPORTS_PER_SOL; return { sol: balanceSol, lamports: balanceLamports }; } catch (e) { throw new Error(`Failed to get balance: ${e.message}`); } } /** * Get token accounts for an address */ async function getTokenAccounts(address, rpcUrl = DEFAULT_RPC) { const connection = getConnection(rpcUrl); ``` Transaction-history queries have the same behavior: ```javascript async function getTransactions(address, limit = 10, rpcUrl = DEFAULT_RPC) { const connection = getConnection(rpcUrl); try { const signatures = await connection.getSignaturesForAddress( new PublicKey(address), { limit } ); ``` The unrestricted connection constructor is also exported: ```javascript module.exports = { generateWallet, connectWallet, getBalance, getTransactions, getTokenAccounts, sendSol, getConnection, isTestNet, getConfig }; ``` ### Technical Analysis No allowlist, URL-scheme restriction, hostname verification, DNS-resolution check, or private-address rejection is applied before constructing the RPC connection. A caller able to control `rpcUrl` can therefore direct the runtime to issue Solana JSON-RPC traffic to arbitrary addresses reachable fr ...[truncated 2249 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not accept arbitrary RPC URLs from agent-controlled function parameters. 2. Use an administrator-configured allowlist of trusted Solana RPC origins. 3. Require HTTPS for non-loopback development configurations and reject URLs containing embedded credentials. 4. Resolve hostnames before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved address ranges. 5. Revalidate the destination after redirects and protect against DNS rebinding by validating resolved addresses at connection time. 6. Remove `getConnection` from the public agent-facing export unless unrestricted connection construction is explicitly required and separately authorized. 7. Separate production and test RPC configuration and authenticate changes to the production endpoint. 8. Treat RPC responses as untrusted. Where transaction integrity matters, compare results across trusted providers or verify relevant on-chain data. 9. Apply outbound network controls so the runtime can communicate only with approved RPC services. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (17)

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The README explicitly claims private keys are never exposed to the agent, yet the documented API and examples require passing raw private keys into functions such as connectWallet(privateKey) and sendSol(privateKey, ...). In an AI-agent context, this contradiction is dangerous because developers may trust the safety claim and then wire sensitive credentials directly into agent-controlled execution paths, enabling theft or misuse of funds if the agent, logs, prompts, or integrations leak the key.

Known Vulnerable Dependency: ws==8.19.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
97% confidence
Finding
The bundled ws 8.19.0 is flagged for both uninitialized memory disclosure and memory-exhaustion DoS. In a blockchain connectivity skill, websocket connections are a normal feature, so a vulnerable websocket stack materially increases risk because a malicious or compromised RPC endpoint could trigger data leakage or exhaust agent resources.

Known Vulnerable Dependency: ws==7.5.10 — 1 advisory(ies): CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
96% confidence
Finding
The lockfile also contains ws 7.5.10, which is flagged for memory-exhaustion DoS from fragmented websocket input. Because this skill interacts with Solana infrastructure and network services, any vulnerable websocket client/server component increases exposure to remote resource exhaustion, especially if the agent connects to untrusted or attacker-influenced endpoints.

Intent-Code Divergence

Medium
Confidence
87% confidence
Finding
The README markets dry-run as the safe default, but nearby examples show real transactions can be sent with skipConfirmation: true, undermining the stated human-in-the-loop control. In an autonomous agent setting, this normalizes disabling a critical safeguard and could lead to irreversible transfers without meaningful review if an agent is induced to execute the example pattern.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The transaction API is documented as accepting a raw private key, but the README does not give a clear, prominent warning that this credential grants full control over funds and must never be exposed to agent prompts, logs, telemetry, or untrusted plugins. In the context of an AI-agent skill, this is especially dangerous because agent frameworks commonly serialize inputs and outputs, making accidental secret disclosure or misuse much more likely.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README provides a concrete example for bypassing human confirmation on a real blockchain transaction but does not clearly warn that transfers are irreversible and may result in immediate loss of funds. Because this skill is specifically intended for AI agents interacting with Solana, showing the bypass as routine usage increases the chance operators adopt unsafe patterns in high-risk automated workflows.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill declares environment-variable requirements and provides executable integration guidance, but it does not define any explicit tool scope such as permissions or allowed-tools. In an agent platform, that ambiguity can let the runtime or operator grant broader capabilities than intended, increasing the chance of unsafe environment access or misuse of sensitive configuration during blockchain operations.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation includes a real transfer example using `dryRun: false` together with `skipConfirmation: true`, which normalizes bypassing human review for an irreversible on-chain action. Because Solana transfers are final once broadcast, an agent or operator copying this example could unintentionally authorize live asset movement without an adequate warning about permanence, fraud risk, or operational safeguards.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The file advertises that testnet mode is enforced, but the implementation accepts an environment-controlled RPC URL and only checks mainnet at runtime for a warning. In an agent skill that can sign and submit blockchain transactions, this misleading guarantee can cause operators or upstream agents to assume funds are safe while real mainnet transfers remain possible.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
A real transfer can proceed without an actual interactive confirmation mechanism when skipConfirmation is set, and on mainnet the code may only emit a console warning. For an AI-agent-facing wallet skill, this is dangerous because programmatic callers can bypass intended safeguards and trigger irreversible fund movement without meaningful human authorization.

Known Vulnerable Dependency: bn.js==5.2.2 — 1 advisory(ies): CVE-2026-2739 (bn.js affected by an infinite loop)

Low
Category
Supply Chain
Confidence
90% confidence
Finding
The lockfile pins bn.js 5.2.2, which is reported as affected by an infinite-loop issue. Although this is not obviously attacker-introduced, a vulnerable arithmetic library can still enable denial of service if untrusted data reaches the affected code paths through blockchain parsing or serialization logic.

Known Vulnerable Dependency: stream-json==1.9.1 — 1 advisory(ies): CVE-2026-71429 (stream-json: pick/ignore/filter/replace filters are O(depth²) on nested input — )

Low
Category
Supply Chain
Confidence
87% confidence
Finding
stream-json 1.9.1 is reported as having O(depth^2) behavior in certain filters on deeply nested input, which can enable algorithmic-complexity denial of service. Here it is a transitive dependency of jayson, so exploitation depends on whether the affected filters are actually used on attacker-controlled JSON, but the package version itself is vulnerable.

Known Vulnerable Dependency: uuid==8.3.2 — 1 advisory(ies): CVE-2026-41907 (uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided)

Low
Category
Supply Chain
Confidence
80% confidence
Finding
uuid 8.3.2 is flagged for missing buffer bounds checks in specific version-generation functions when a caller supplies a buffer. This is likely a low-probability issue in this skill because it is transitive and may not be exercised, but the dependency version itself is still vulnerable and could cause crashes or memory-safety issues depending on runtime behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"test": "node test.js"
  },
  "dependencies": {
    "@solana/web3.js": "^1.98.0",
    "bs58": "^6.0.0",
    "tweetnacl": "^1.0.3"
  },
Confidence
95% confidence
Finding
The dependency uses a caret range, which allows newer compatible versions to be installed over time. In a blockchain-interacting skill, this increases supply-chain risk because a compromised or breaking upstream release of @solana/web3.js could be pulled in without deliberate review, affecting transaction construction or key-handling logic.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "@solana/web3.js": "^1.98.0",
    "bs58": "^6.0.0",
    "tweetnacl": "^1.0.3"
  },
  "keywords": [
Confidence
95% confidence
Finding
The bs58 dependency is specified with a caret version, so installs may resolve to different future releases. Even for a small utility library, this creates avoidable supply-chain exposure, and in a wallet/blockchain context an unexpected dependency change could affect address/key encoding behavior or introduce malicious code.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "@solana/web3.js": "^1.98.0",
    "bs58": "^6.0.0",
    "tweetnacl": "^1.0.3"
  },
  "keywords": [
    "solana",
Confidence
96% confidence
Finding
tweetnacl is a cryptographic library, and leaving it unpinned permits automatic adoption of future releases without explicit validation. In a Solana connection skill that likely handles signatures or key material, a compromised or flawed update could directly impact cryptographic operations and therefore has somewhat higher security significance than a generic library.

Intent-Code Divergence

Low
Confidence
95% confidence
Finding
The comment suggests the RPC is safely constrained to testnet, but the environment variable override permits arbitrary endpoints, including mainnet or attacker-controlled RPC infrastructure. In this skill context, that discrepancy weakens trust assumptions and can expose transaction handling to unintended networks or manipulated RPC behavior.

Static analysis

No suspicious patterns detected.