Building on Avalanche? Ava Labs can cover up to 75% of your audit, and we take 10% off our quote. Avalanche audits: up to 75% covered. See how

Work & reportsReport

CleopetraSolana Trading Bot Security Review

Guvenkaya reviewed the Cleopetra Solana trading bot, including its Telegram bot, scheduled jobs, database layer and helpers.

Cleopetra logo

Date of engagement: 13th May 2025 - 26th May 2025

  • Timur GüvenkayaPrincipal advisor

Engagement Dashboard

Audit results

Guvenkaya conducted a security assessment of the Cleopetra Solana Trading Bot from 13th May 2025 to 26th May 2025. During this engagement, a total of 12 findings were reported. 6 of the findings were medium and the remaining were either low or informational severity. All major issues are either fixed or acknowledged by the Cleopetra team.

Engagement record

Client
Cleopetra
System
Cleopetra Solana trading bot
Review period
13th May 2025 - 26th May 2025
Reviewed baseline
f22cf033a335d2717245fbc8ba7cd327a0215eb2
Paths in scope
10
Principal advisor
Timur Güvenkaya

Timeline

  1. Review start13th May 2025
  2. Draft report26th May 2025
  3. Final report21st June 2025

Findings by severity

  • Critical 0
  • High 0
  • Medium 6
  • Low 3
  • Informational 3

Finding status at final report

12Raised
9Fixed
1Scheduled
2Acknowledged

Remediation complexity

12Simple

Impact

3Severe
5Moderate
4Negligible

Likelihood

1Likely
3Possible
8Rare

Advisory and Client

The advisory

Guvenkaya specializes in security for digital assets and financial infrastructure, from smart contracts and protocols to applications, infrastructure, custody and cryptography.

Our team, including the sitting Heads of Security at Gauntlet and Agora, brings experience from Kraken, OpenZeppelin, Binance, ZKsync, Nillion, Invicti and EY. Across the team: 200+ security reviews, $30B+ in volume secured, 13 published cryptography research papers, and credentials including OSCP, OSWE and CISM.

  • Every reviewer has a public bio and is named before you sign. We match expertise to your system, with a principal leading scoping and checking findings.

The client

Cleopetra is a seamless platform for liquidity provisioning on Solana DEXes, directly from Telegram. Enter a token, pick an LP strategy of your choice and create the position - the agent finds optimal pools and auto-rebalance positions when needed to maximise fees and reduce impermanent loss.

Scope and Review Boundaries

Review depths

Deep
Line-by-line review with explicit attack hypotheses, adversarial reasoning about state transitions, and reproduction attempts for each hypothesis that survived reading.
Supporting
The code was read for its effect on a reviewed flow. Behaviour outside that effect was not assessed.
Targeted
Entry points were exercised or triaged against the flow's security properties rather than read in full.

This is the code we read, and how closely. 10 paths are in scope. Each path links to the file at the reviewed commit.

PathReview depthScope note
src/botDeepBot
src/cronDeepCron
src/dbDeepDB
src/helpersDeepHelpers
src/config.tsDeepConfig
src/constants.tsDeepConstants
src/index.tsDeepIndex
src/providers.tsDeepProviders
src/types.tsDeepTypes
src/utils.tsDeepUtils

Out of scope

The audit will include reviewing the code for security vulnerabilities. The audit does not include a review of the tests, deployment setup, architecture, and dependencies.

Assessment Framework

Guvenkaya rates findings by likelihood and impact so remediation can be prioritized by technical risk, business context, and the decision the engagement is meant to support.

Rating chain applied to every finding

  1. 01 Likelihood Likely, possible or rare
  2. 02 Impact Severe, moderate or negligible
  3. 03 Severity Derived from the matrix below
  4. 04 Remediation complexity Simple, moderate or difficult
  5. 05 Status Not fixed, fixed, acknowledged or scheduled

Impact

ImpactWhat it means here
SevereExploitation could result in critical loss or compromise, such as full system control, substantial financial loss, or severe reputational damage.
ModerateExploitation may lead to limited data loss, partial compromise, moderate financial impact, or noticeable degradation of services.
NegligibleExploitation has minimal impact, such as minor data exposure without significant consequences or slight inconvenience without substantial disruption.

Likelihood

LikelihoodWhat it means here
LikelyThe vulnerability is easily discoverable and not overly complex to exploit.
PossibleThe vulnerability presents some challenges either in discovery or in the complexity of the attack.
RareThe vulnerability is either very difficult to discover or complex to exploit, or both.

This matrix provides a nuanced view, taking into account both the ease of discovering a vulnerability and the complexity involved in exploiting it.

Attack Complexity \ Discovery EaseObviousConcealedHidden
ComplexPossibleRareRare
ModerateLikelyPossibleRare
StraightforwardLikelyPossiblePossible

Severity

SeverityWhat it means here
CriticalAssigned to vulnerabilities with severe impact and a likely likelihood of exploitation.
HighFor vulnerabilities with either severe impact but only a possible likelihood, or moderate impact with a likely likelihood.
MediumUsed for vulnerabilities with severe impact but a rare likelihood, moderate impact with a possible likelihood, or negligible impact with a likely likelihood.
LowFor vulnerabilities with moderate impact and rare likelihood, or negligible impact with a possible likelihood.
InformationalThe lowest severity rating, typically for vulnerabilities with negligible impact and a rare likelihood of exploitation.
Likelihood \ ImpactSevereModerateNegligible
LikelyCriticalHighMedium
PossibleHighMediumLow
RareMediumLowInformational

Remediation complexity

This measures how difficult it is to remediate a finding once it has been identified.

Remediation complexityWhat it means here
SimplePatches or fixes are readily available and easily implemented.
ModerateRequires some time and resources to remediate, but well within the capabilities of most organizations.
DifficultRemediation requires significant resources, specialized skills, or substantial changes to systems or architecture.

Finding status

This tracks the client's response to each finding and whether remediation has been completed, accepted, or scheduled.

StatusWhat it means here
Not FixedIndicates that the vulnerability has been identified but no remedial action has been taken yet. This status is crucial for newly discovered vulnerabilities or those awaiting prioritization.
FixedThis status is applied when the vulnerability has been successfully remediated. It implies that appropriate measures, such as patching, configuration changes, or architectural modifications, have been implemented to resolve the issue.
AcknowledgedThis status is used for vulnerabilities that have been recognized, but for various reasons (such as risk acceptance, cost, or other business decisions), have not been fixed. It indicates that the risk posed by the vulnerability is known and has been consciously accepted.
ScheduledThis status indicates that the vulnerability has been acknowledged and a plan is in place to fix it in the future. It signifies that while remediation hasn't yet occurred, the issue has been prioritized and is part of the planned development roadmap.

Findings Summary

12All findings
0 Critical
0 High
6 Medium
3 Low
3 Informational

Findings register

FindingImpactLikelihoodSeverityRemediation complexityStatus
GUV-1: Incorrect Use of Async in Reward DistributionModeratePossibleMediumSimpleFixed
GUV-2: Possible Loss of Funds Through Invalid State UpdateModeratePossibleMediumSimpleFixed
GUV-3: Insecure Private Key Export FlowSevereRareMediumSimpleFixed
GUV-4: No Encryption-at-rest for Sensitive User DataSevereRareMediumSimpleScheduled
GUV-5: Database Tampering RiskSevereRareMediumSimpleFixed
GUV-6: Potential Private Key Leakage in Error LoggingModeratePossibleMediumSimpleFixed
GUV-7: Incorrect Units for changing priority feesNegligibleLikelyLowSimpleFixed
GUV-8: Potential Race Condition in Mass OperationsModerateRareLowSimpleAcknowledged
GUV-9: Insufficient Onchain Transaction ConfirmationModerateRareLowSimpleAcknowledged
GUV-10: Preventing Possible Insecure Direct Object ReferenceNegligibleRareInformationalSimpleFixed
GUV-11: Lack Of Health/Execution Logging and Error RecoveryNegligibleRareInformationalSimpleFixed
GUV-12: Inadequate Confirmation for Token Recipient and AmountNegligibleRareInformationalSimpleFixed

Detailed Findings

GUV-1: Incorrect Use of Async in Reward Distribution

Severity
Medium
Impact
Moderate
Likelihood
Possible
Remediation Complexity
Simple
Status
Fixed

Description

The distributeRewardsJob uses Map.forEach(async () => ) to group referrers and send notifications. But forEach doesn’t wait for async code to finish. This means important steps like building the reward batches and notifying users might still be running while the function moves on to sending transactions and updating state.

This can cause multiple issues, such as:

  • Some users might not get the rewards they earned.
  • Shared data might be changed at the same time from different places, leading to bugs or incorrect results.
  • The link between who referred whom and what rewards they got might break.
  • Rewards could be lost, database records might be wrong, and users could lose trust in the system.

Proof of concept: GUV-1 PoC

Recommendation

Replace all map.forEach(async ... usage for asynchronous workflows with sequential for...of loops or bulk Promise.all() approach. Carefully construct batches first, then proceed to transactional stages.

Remediation

The Cleopetra team has fixed this issue in this PR: #93

GUV-2: Possible Loss of Funds Through Invalid State Update

Severity
Medium
Impact
Moderate
Likelihood
Possible
Remediation Complexity
Simple
Status
Fixed

Description

Reward distribution job attempt to sign and send multiple transactions in a loop, pushing each signature to an array. However, there is no granular verification or handling for failed transactions per user/referrer. If sendAndConfirmTransaction fails for a batch or individual reward payout, downstream logic still marks rewards as processed and notifies users, even if the transaction failed on-chain.

Proof of concept: GUV-2 PoC

Recommendation

Track transaction success/failure and only mark as processed those fees which actually receive confirmed on-chain rewards and for failed transactions, implement retry logic or mark for audit/reprocessing.

Remediation

The Cleopetra team has fixed this issue in this PR: #93

GUV-3: Insecure Private Key Export Flow

Severity
Medium
Impact
Severe
Likelihood
Rare
Remediation Complexity
Simple
Status
Fixed

Description

The exportPrivateKey command allows users to export their (Privy-based) private keys directly through a Telegram message with:

/src/bot/composers/wallet/exportPrivateKey.ts
await ctx.reply('✅ Successfully exported private key - '${privateKey}' Import private key into wallets like Phantom (tap to copy) and delete this message once you are done.', { parse_mode: "Markdown" });

Exported keys are sent as plaintext in a chat message, putting them at risk from Telegram/Telegram client interception and device compromise. The export implementation in /src/helpers/privy.ts is invoked directly on request, bypassing secure delivery or split-secret protections.

Recommendation

it is imperative to implement a mandatory secondary authentication system PIN OR a user-defined passphrase before allowing any sensitive operations such as private key export.

During the initial wallet setup, require users to create a secure PIN OR passphrase and enforce a multi-step confirmation flow:

  • Prompt users to re-enter their PIN/passphrase before proceeding.
  • Display a strong security warning with a deliberate confirmation step (e.g., type “CONFIRM”).

The Cleopetra team has fixed this issue in this PR: #99

GUV-4: No Encryption-at-rest for Sensitive User Data

Severity
Medium
Impact
Severe
Likelihood
Rare
Remediation Complexity
Simple
Status
Scheduled

Description

The PostgreSQL schema for users stores privyWalletAddress,privyWalletId, and possibly private key export activity (didExportPvtKey) in plaintext (see /src/db/schema.ts). There is no evidence of encryption-at-rest, field-level encryption for sensitive columns, or defense-in-depth for backend storage. While wallet private keys are not directly stored, IDs are. If the database is compromised (insider threat, credential leak, SQLi), attackers may tamper with the wallet ids and drain user funds.

Recommendation

  • Encrypt sensitive user columns at rest with application-level encryption (encryption/decryption key separate from DB, e.g. KMS-backed).
  • Ensure database is encrypted on-disk using PostgreSQL's built-in encryption or cloud-provided at-rest encryption.
  • Only expose Privy wallet IDs for short-lived server processes; avoid long-term storage unless required.

Remediation

The Cleopetra Team has scheduled the fix.

GUV-5: Database Tampering Risk

Severity
Medium
Impact
Severe
Likelihood
Rare
Remediation Complexity
Simple
Status
Fixed

Description

The current implementation of the bot stores both Privy wallet IDs and addresses in the database. This creates a security risk where an attacker who gains database access could modify wallet addresses while keeping the original wallet IDs. This would allow them to execute transactions that are signed by legitimate wallets but paid from attacker-controlled addresses for example when user calls closePosition function that would affect all position management operations including closing positions and can exploited by anyone with database write access.

Recommendation

  • Encrypt sensitive user columns at rest with application-level encryption (encryption/decryption key separate from DB, e.g. KMS-backed).
  • Ensure database is encrypted on-disk using PostgreSQL's built-in encryption or cloud-provided at-rest encryption.
  • Only expose Privy wallet IDs for short-lived server processes; avoid long-term storage unless required.
DatabaseTrigger
CREATE OR REPLACE FUNCTION prevent_privy_wallet_id_update()RETURNS TRIGGER AS $$BEGIN  IF NEW.privy_wallet_id IS DISTINCT FROM OLD.privy_wallet_id THEN    RAISE EXCEPTION 'privy_wallet_id is immutable and cannot be changed';  END IF;  RETURN NEW;END;$$ LANGUAGE plpgsql;

Remediation

The Cleopetra Team has fixed the by adding a trigger which restricts update for privy_wallet_id column and restrict regular db users to alter/drop that function.

GUV-6: Potential Private Key Leakage in Error Logging

Severity
Medium
Impact
Moderate
Likelihood
Possible
Remediation Complexity
Simple
Status
Fixed

Description

The private key export functionality contains a security issue in the exportPrivateKey function where sensitive information about private key operations is might be logged in error messages and this concerning because the error message from the underlying operation could potentially contain sensitive information about the private key export process

exportPrivateKey
console.log(          'an error occurred while exporting pvt key for ${walletId} wallet - ${e.message}'        );        throw err;

Recommendation

Remove or sanitize the error logging to avoid exposing sensitive information in private key export functionality OR use a dedicated error logging service that can automatically sanitize sensitive information.

Remediation

The Cleopetra team has fixed this issue in this PR: #93

GUV-7: Incorrect Units for changing priority fees

Severity
Low
Impact
Negligible
Likelihood
Likely
Remediation Complexity
Simple
Status
Fixed

Description

The changePriorityFees accepts a SOL amount typed by the user, parses it with parseFloat, stores the raw value directly in users.computeUnitsPrice, and later re-uses it as lamports/µ-lamports inside transaction-building logic:

/src/bot/composers/settings/changePriorityFees.ts
const priorityFees = parseFloat(ctx.message.text);await db.update(usersTable).set({ computeUnitsPrice: priorityFees })

The value is then consumed as:

/src/helpers/solana.ts
microLamports: cuPrice * 0.3,     // expects µ-lamports per CUlamports: cuPrice * 0.7           // Jito tip transfer (expects lamports)

since cuPrice is supposed to represent micro-lamports / CU (i.e. 1e-6 SOL units). The same value is blindly multiplied by 0.7 and used as a direct lamport transfer to the Jito tip account. For the default MEDIUM_PRIORITY the bot tips 0.07 SOL per transaction (100 000 lamports × 0.7), more than 700× the intended 0.1 lamport/CU.

Recommendation

Store computeUnitsPrice exclusively in µ-lamports (integer). Convert SOL → µ-lamports when accepting human input: Math.floor(inputSol * LAMPORTS_PER_SOL / 1_000_000).

Remediation

The Cleopetra team has fixed this issue in this PR: #93

GUV-8: Potential Race Condition in Mass Operations

Severity
Low
Impact
Moderate
Likelihood
Rare
Remediation Complexity
Simple
Status
Acknowledged

Description

When performing mass-operations ("close all positions", "claim all fees"), the bot executes actions in parallel (Promise.all) on a mutable ctx.session.positions array. If array/context is modified mid-operation (by another event, user spam, concurrent callback, or user race), there could be:

  • Operations on non-existent/unstable state (double close, duplicate claim, non-existent index).
  • State desync leading to incorrect user feedback, failed tx, or even race-triggered wallet logic bugs.

Example - Close All Positions (portfolio:close_all:yes callback):

CloseAllPositions
const promises = ctx.session.positions.map((position, i) => {  return new Promise<void>(async (resolve, _) => {    // ... position closing logic ...  });});await Promise.all(promises);

There's no locking mechanism to prevent concurrent mass operations the array could be modified by other operations while these mass operations are running

Recommendation

create a lock mechanism to prevent concurrent mass operations, below is example fix for Close All Positions it's recommended to do the same for all Mass Operations like:

  • Claim All Fees
  • Mass DM

Remediation

The Cleopetra Team has acknowledged the issue.

GUV-9: Insufficient Onchain Transaction Confirmation

Severity
Low
Impact
Moderate
Likelihood
Rare
Remediation Complexity
Simple
Status
Acknowledged

Description

Transactions are sent and confirmation is checked (confirmTransaction, parseTxn) for "confirmed" status, but in Solana, "confirmed" is not durable finality ("finalized" is safest). The bot proceeds to update database state (positions, fees, etc.) and notifies users immediately on "confirmed", which may be reverted or dropped in rare Solana edge cases (such as forks or cluster issues). There is no check for "finalized" status, and database updates are not re-synced if transactions revert later.

Example
const metadata = await connection.getParsedTransaction(sig, {  commitment: "confirmed",  maxSupportedTransactionVersion: 0,});

Recommendation

Use "finalized" commitment instead of "confirmed".

Remediation

The Cleopetra Team has acknowledged the issue.

GUV-10: Preventing Possible Insecure Direct Object Reference

Severity
Informational
Impact
Negligible
Likelihood
Rare
Remediation Complexity
Simple
Status
Fixed

Description

While the current implementation in our Telegram bot doesn't present an immediate IDOR vulnerability (as parameters are controlled through Telegram's interface), it's a security best practice to implement defense in depth by enforcing strict ownership verification for all database operations. This approach ensures that even if the application's interface changes or new access points are added in the future, the underlying data access layer maintains proper security controls.

Examples from the current implementation missing explicit ownership verification:

Example
const positionInfo = await db.query.positionsTable.findFirst({              where: eq(positionsTable.address, positionAddress)            }); await db              .update(positionsTable)              .set({                rebalancingEnabled: !positionInfo.rebalancingEnabled              })              .where(eq(positionsTable.address, ctx.session.positionAddress));

Recommendation

This how the recommended implementation would look like

PotentialSolution
const positionInfo = await db.query.positionsTable.findFirst({  where: and(    eq(positionsTable.address, positionAddress),    eq(positionsTable.owner, ctx.session.wallet)  )});

Remediation

The Cleopetra team has fixed this issue in this PR: #93

GUV-11: Lack Of Health/Execution Logging and Error Recovery

Severity
Informational
Impact
Negligible
Likelihood
Rare
Remediation Complexity
Simple
Status
Fixed

Description

In the cronjobs Logging is limited to console.log statements, with some errors only sent as Discord messages. There is no structured health monitoring, persistent error reporting/retries, or success/failure audit logs outside of ephemeral console/Discord. Issues may therefore go undetected or unresolved:

  • Missed/failed cron runs.
  • Partial batch failures.
  • Recurring errors causing fund or data integrity issues

Recommendation

  • Integrate DataDog or Sentry for comprehensive monitoring using the typescript SDK.
  • Add structured logging with consistent format including timestamps.
  • Implement retry mechanisms with exponential backoff Use existing retryFuncWrapper

Remediation

The Cleopetra team has fixed this issue in this PR: #93

GUV-12: Inadequate Confirmation for Token Recipient and Amount

Severity
Informational
Impact
Negligible
Likelihood
Rare
Remediation Complexity
Simple
Status
Fixed

Description

Before final transmission of funds (SOL or SPL tokens) to the user-provided vault address: The bot does not explicitly show a "confirm transfer" summary of critical details (recipient, token/mint, amount/decimal) once more before sending. Users may make mistakes due to typos, malware-autofill, or clipboard hijacking, leading to fund loss.

Recommendation

  • Insert an explicit human-confirmation step after collecting all transfer details (recipient, token, amount), before executing the blockchain transaction. Require the user to click a "confirm" button, displaying full destination address and parsed amount.
  • Provide a last-moment "cancel" option in the confirmation step.

Remediation

The Cleopetra team has fixed this issue in this PR: #96

The web version and the PDF (20 pages) are generated from the same source. The report as first delivered is kept in the public reports archive.

Tell us what you need to secure.

Describe your system, main concern, and deadline. We’ll reply with scoping questions and a proposed next step.

Discuss your scope