Shame on you Jupiter exchange, I found a real vulnerability. You acknowledge that it is a vulnerability but you don't want to pay me, you say there is no risk. I demonstrated on a mainnet fork that the vulnerability leads to real loss of funds. You are a bunch of cowards.
Uninitialized Jupiter Lend Rewards Admin PDA Enables Admin Takeover and Fund Loss
📷
@nosferatussubmitted a report toJupiterJune 9, 2026 at 17:39
Vulnerability TypePrivilege Escalation(Smart Contract Access Control)Assetjup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9 - Jupiter LendEndpointhttps://solscan.io/account/jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKarSeverityINFORMATIONALCVSS10.0CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H
Description
Summary
The Jupiter Lend rewards rate model mainnet program exposes init_lending_rewards_admin, which initializes the singleton rewards-admin PDA derived from the constant seed lending_rewards_admin.
The initializer only requires an arbitrary signer and arbitrary non-zero authority / lending_program arguments. On Solana mainnet, the expected singleton PDA is still uninitialized:
text
program: jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar seed: lending_rewards_admin PDA: 9PwkxiYpxX9S9Pc7djGrqPyhr1yd7hpf1u77oEPWf8Mr RPC result: AccountNotFound
An attacker can initialize this PDA first, set themselves as authority, and become the initial rewards admin. That admin can then call rewards-admin functions such as start_rewards, stop_rewards, queue_next_rewards, cancel_queued_rewards, and init_lending_rewards_rate_model.
Affected asset
Jupiter Lend smart contract asset:
jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9
Affected rewards program used by Jupiter Lend:
jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar
Root cause
init_lending_rewards_admin is a first-initializer singleton setup path without a hardcoded governance signer or existing authorized admin constraint. The instruction writes attacker-controlled authority and lending_program values into the singleton admin account.
Relevant code behavior:
rust
pub fn init_lending_rewards_admin( ctx: Context<InitLendingRewardsAdmin>, authority: Pubkey, lending_program: Pubkey, ) -> Result<()> { if authority == Pubkey::default() || lending_program == Pubkey::default() { return Err(ErrorCodes::InvalidParams.into()); } lending_rewards_admin.authority = authority; lending_rewards_admin.lending_program = lending_program; lending_rewards_admin.auths.push(authority); lending_rewards_admin.bump = ctx.bumps.lending_rewards_admin; Ok(()) }
Because the mainnet PDA is missing, this path is reachable by any signer today.
Steps to Reproduce
Derive the rewards-admin PDA for program jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar using seed lending_rewards_admin.
text
PDA = 9PwkxiYpxX9S9Pc7djGrqPyhr1yd7hpf1u77oEPWf8Mr
Confirm on mainnet that the singleton PDA is uninitialized:
text
$ solana account 9PwkxiYpxX9S9Pc7djGrqPyhr1yd7hpf1u77oEPWf8Mr --url
api.mainnet-beta.solana.com --output json Error: AccountNotFound: pubkey=9PwkxiYpxX9S9Pc7djGrqPyhr1yd7hpf1u77oEPWf8Mr
Build init_lending_rewards_admin with:
text
signer = attacker lending_rewards_admin = 9PwkxiYpxX9S9Pc7djGrqPyhr1yd7hpf1u77oEPWf8Mr authority = attacker lending_program = jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9
After initialization, the attacker is present in lending_rewards_admin.auths, so rewards-admin authorization checks pass for the attacker.
Local regression test:
text
$ cargo test -p tests test_lending_rewards_admin_can_be_initialized_by_arbitrary_signer -- --nocapture test oracle::jup_lend::tests::test_lending_rewards_admin_can_be_initialized_by_arbitrary_signer ... ok test result: ok. 1 passed
Economic manipulation test:
text
$ cargo test -p tests test_attacker_started_rewards_increase_lending_exchange_price -- --nocapture token_exchange_price_before=1000000000000 token_exchange_price_after=1001369863013 test result: ok. 1 passed
Mainnet-fork oracle manipulation test using live USDC JupLend state:
text
$ cargo test -p tests test_attacker_can_manipulate_mainnet_usdc_juplend_oracle_price_on_fork -- --nocapture mainnet_usdc_juplend_oracle_before=1046713366194000 mainnet_usdc_juplend_oracle_after=1048224856278000 delta=1511490084000 test result: ok. 1 passed
Mainnet-fork fund-loss test using live Jupiter Vault 68:
text
$ cargo test -p tests test_attacker_can_borrow_extra_usdc_from_live_vault_68_after_juplend_oracle_manipulation_on_fork -- --nocapture pre-manipulation borrow capacity: 909792706 borrow attempted: 909850000 post-manipulation borrow capacity: 911096540 balance_before=0 balance_after=909850000 profit_raw=909850000 test result: ok. 1 passed
The same operate() borrow attempt is rejected before the rewards manipulation and succeeds after the attacker-controlled rewards manipulation on the fork. No mainnet write transaction was executed.
Impact
The confirmed impact is unauthorized takeover of the Jupiter Lend rewards-admin singleton for the deployed mainnet rewards program.
The economic impact is fund loss. On a local mainnet fork using live Jupiter Vault 68 / oracle / liquidity accounts, the attacker-controlled rewards admin inflates the live JupLend oracle enough to make Jupiter Vault accept an otherwise rejected over-borrow and transfer USDC to the attacker.
Observed fund-loss PoC:
text
live_vault_68_config borrow_fee=0 collateral_factor=890 liquidation_threshold=910 borrow_mint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v collateral_amount=1000000000 pre-manipulation borrow capacity=909792706 borrow_attempt=909850000 post-manipulation borrow capacity=911096540 balance_before=0 balance_after=909850000 profit_raw=909850000
This is not only a governance/configuration bug: the attacker can become rewards admin, set attacker-controlled rewards parameters without escrow transfer of the advertised rewards, manipulate the fToken exchange price/JupLend oracle, and extract extra borrow from a live vault path on fork.
The direct redeem/withdraw path was tested separately and failed due to Liquidity accounting, so this report does not claim that direct redeem is the fund-loss route. The proven route is oracle/borrow-capacity manipulation through Jupiter Vault operate() on a mainnet fork.
Severity justification: Critical. The issue is remote, low complexity, requires no prior privileges, and leads to unauthorized smart-contract admin privileges with a demonstrated fund-loss path using live mainnet-fork state.
Attachments (1)
jupiter_lend_rewards_admin_takeover_poc.txt
text/plain