Summary
Klever: Marketplace settlement mints KLV when referral % + royalty % exceed the bid (negative seller share silently skipped)
When a marketplace order is settled (MarketBuy / BuyItNow, and auction Claim), the buyer's
payment is split three ways, referral, royalties, and the seller (market-order owner)
remainder:
marketOwnerAmount = CurrentBid − referralAmount − royaltiesAmount
Referral and royalties are paid out unconditionally, but the seller remainder is only paid
when positive (computeMarketOwnerAmount returns Ok and pays nothing when the amount is<= 0). When referral% + royalty% exceeds 100% of the bid, marketOwnerAmount goes negative
and is silently skipped, so the marketplace pays out more KLV / sale currency than the buyer
paid in, minting the difference out of thin air.
The combined ceiling royalty% + referral% <= 100% is checked once, at listing time (Sell).
But the two percentages are sourced asymmetrically at settlement:
- referral % is snapshotted into the order at
Sell(MarketOrderData.ReferralPercentage); - royalty % is never snapshotted, it is read live from the asset at buy time
(asset.Royalties.MarketPercentage).
So the listing-time invariant is a time-of-check/time-of-use guarantee only. After a valid
listing, the asset owner raises the royalty MarketPercentage via AssetTrigger → UpdateRoyalties;
at the next buy the live royalty plus the snapshotted referral exceed 100%, and the settlement mints
the overflow. The minted funds land in attacker-controlled referral / royalty addresses.
This was actively exploited on mainnet (see Evidence), minting tens of millions of KLV before
the emergency guard was deployed.
Affected component
- Repository:
klever-io/klever-go(node). - Settlement / mint site:
core/kapp/market/market.go,executeBuyMarket(L575+),computeReferralAmount(L361+),computeRoyaltiesAmount(L490+),computeRoyaltiesFixedDeposit(L443+),computeMarketOwnerAmount(L540+). - TOCTOU sources:
Sellcombined check (market.go:908), order snapshot of referral but not
royalty (market.go:997), live royalty mutation viacore/kapp/kda/trigger.go,handleUpdateRoyaltiesNFTandSFT(L613+, setsasset.Royalties.MarketPercentageat L670). - Reachable from both
Buy(BuyItNow,market.go:204+) and auctionClaim
(market.go:705,market.go:731). - Pre-fix: not gated by any fork flag, exploitable on mainnet. The fix is gated behind the new
FixMarketBuyOverflowactivation-epoch flag.
Root cause
1. Settlement pays referral + royalty unconditionally, seller remainder only if positive
core/kapp/market/market.go, executeBuyMarket (L575+):
referralAmount, _ := tools.ComputePercentageI64(marketOrder.CurrentBid,
int64(marketOrder.ReferralPercentage), ...) // L583: SNAPSHOT referral %
royaltiesAmount, _ := tools.ComputePercentageI64(marketOrder.CurrentBid,
int64(asset.Royalties.MarketPercentage), ...) // L587: LIVE royalty %
marketOwnerAmount := marketOrder.CurrentBid - referralAmount - royaltiesAmount // L591: can go negative
// ---- FIX (FixMarketBuyOverflow), added by the patch ----
if m.forkController.FixMarketBuyOverflow() && marketOwnerAmount < 0 { // L593-596
ctx.Receipts().AddError(ctx.ContractID(), common.ErrFieldInvalidRoyalties, common.ErrInvalidValue.Error())
return transaction.Transaction_AmountInvalid, common.ErrInvalidValue
}
m.computeReferralAmount(ctx, marketOrder, referralAmount, currencyID) // pays referral in full
m.computeRoyaltiesFixedDeposit(ctx, marketOrder, asset) // pays fixed royalty (KLV)
m.computeRoyaltiesAmount(ctx, marketOrder, asset, currencyID, royaltiesAmount) // pays % royalty in full
m.computeMarketOwnerAmount(ctx, marketOrder, currencyID, marketOwnerAmount) // <-- skips when <= 0
computeMarketOwnerAmount (L540-542), the silent skip:
func (m *marketKapp) computeMarketOwnerAmount(... marketOwnerAmount int64) (... , error) {
if marketOwnerAmount <= 0 {
return transaction.Transaction_Ok, nil // negative seller share dropped, NO error
}
// ... AddToBalance(marketOwnerAmount) ...
}
Meanwhile computeReferralAmount (L376) and computeRoyaltiesAmount (L515) each AddToBalance(...)
the full computed amount with no matching debit from the buyer beyond the singlebidderAcc.SubFromBalance(amount) taken in Buy (market.go:301).
Conservation breaks: buyer is debited bid once; recipients are creditedreferralAmount + royaltiesAmount. When that sum > bid, the surplus(referralAmount + royaltiesAmount − bid) is minted.
2. The combined ≤100% invariant is enforced only at listing time
Sell (market.go:908) correctly rejects a listing whose combined cut exceeds 100%:
if asset.Royalties.MarketPercentage + marketplace.ReferralPercentage > core.HundredPercent {
return transaction.Transaction_ParameterInvalid, common.ErrInvalidValue
}
…and snapshots referral into the order, but not royalty (market.go:997-998):
marketOrder := &kapps.MarketOrderData{
// ...
ReferralPercentage: marketplace.ReferralPercentage, // snapshotted
RoyaltiesFixedDeposit: asset.Royalties.MarketFixed, // snapshotted
// NOTE: asset.Royalties.MarketPercentage is NOT snapshotted -> read live at buy
}
MarketOrderData has no field for the royalty percentage (kapps/market.pb.go), so settlement
always re-reads it live from the (mutable) asset.
3. Royalty % is mutable after listing
core/kapp/kda/trigger.go, handleUpdateRoyaltiesNFTandSFT (L613+) lets the asset owner overwriteasset.Royalties.MarketPercentage (L670) with only a per-field <= 100% check (CheckValid100Params,
L651), it has no knowledge of any outstanding marketplace listing's snapshotted referral. So the
owner can list at, e.g., referral 100% / royalty 0% (sum 100%, passes Sell), then raise royalty to
100%, making the buy-time sum 200%.
The shipped emergency-guard source documents this exact vector:
"The royalty percentage is read live at buy time, so a listing made now can be weaponised later
via UpdateRoyalties." (common/emergencyGuard.go)
Net effect: referralAmount + royaltiesAmount = bid + bid = 2·bid; marketOwnerAmount = −bid
(skipped); bid KLV minted per settlement, paid to attacker-controlled addresses.
Proof of Concept
A. Committed regression test (deterministic, runnable today)
core/kapp/market/market_test.go, TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation.
It builds an order with ReferralPercentage = 100% and an asset with MarketPercentage = 100%
(the attacker is both the referral and the royalty address), then settles a bid of25,600,000 KLV (25600000000000 base units):
go test ./core/kapp/market/ -run TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation -v
FixDisabled_MintsKLVFromThinAir: settlement returnsOk; the attacker address ends with2·bidcredited while onlybidwas paid in, i.e.bidKLV minted.FixEnabled_RejectsInflation: withFixMarketBuyOverflowon, settlement returnsTransaction_AmountInvalidand the attacker balance stays0, no payout runs.
B. End-to-end on a local node (the real attack path)
A single-node local network is sufficient. The exploit is four transactions from one ordinary
funded account; nothing privileged is required.
- Create an NFT collection you own, with
royalties.marketPercentage = 0and a royalties
address you control. - Create a marketplace with
referralPercentage = 10000(100%) and a referral address you
control (CreateMarketplace). - List one NFT for sale (
Sell) on that marketplace. TheSellcheck passes because0 (royalty) + 10000 (referral) = 10000 = HundredPercent. The order snapshotsReferralPercentage = 10000. - Raise the royalty on the asset to 100% (
AssetTrigger / UpdateRoyalties,marketPercentage = 10000). Allowed: the per-field check passes and the live combined invariant
is never re-evaluated against the open listing. - Buy the listing (
MarketBuy) from a second account (or settle the auction viaClaim).referralAmount = bid,royaltiesAmount = bid,marketOwnerAmount = −bid(skipped). Your
referral + royalty addresses receive2·bid; the buyer paidbid;bidKLV is minted.
Because the attacker controls buyer, seller, referral and royalty addresses, the only real cost is
transaction fees; the cycle is repeatable until supply targets are met.
Evidence
Regression test (local, verbatim)
=== RUN TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation
=== RUN TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixDisabled_MintsKLVFromThinAir
=== RUN TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixEnabled_RejectsInflation
--- PASS: TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation (0.00s)
--- PASS: TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixDisabled_MintsKLVFromThinAir (0.00s)
--- PASS: TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixEnabled_RejectsInflation (0.00s)
PASS
ok github.com/klever-io/klever-go/core/kapp/market 0.279s
FixDisabled asserts the attacker balance equals 2·bid = 51,200,000 KLV for a single settlement
(bid = 25,600,000 KLV), with bid of that minted. FixEnabled asserts rejection and a 0
balance.
Mainnet exploitation (observed)
The bug was exploited in production, and was detected and characterised externally by the
community monitoring project KleverPuls / kpulse.tech before the root
cause was known internally. Over a ~24h window kpulse isolated a single wallet (opened
2026-06-04, ~204 transactions in ~24h, funded only by a ~242K KLV KuCoin withdrawal, no
treasury/foundation funding) that:
- self-issued 3 NFT collections named "InflationPOC" and wash-traded one (
NFLATION-ESGO/1)
29 times through self-created marketplaces, ~$450K of artificial, economically empty NFT
volume; - swapped the proceeds KLV → USDC / USDT / WBTC / WETH on KleverSwap and bridged ~$72K of value
to Ethereum via wrapped-asset burns over 24h (USDC −12,487 ≈ $12.5K; USDT −26,362 ≈ $26.4K; WBTC
−0.31 ≈ $19.6K; WETH −7.55 ≈ $14K); - surfaced a spurious "1.86B KLV outflow" headline that kpulse correctly identified as a
wash-trade receipt-doubling artifact with small real net KLV flow.
That "doubling of marketplace receipts" is precisely the on-chain signature of this bug: each
abusive settlement pays out a referral cut (bid) plus a royalty cut (bid) while the buyer paid
only bid once, the market contract emits ~2× the value it took in, which is the mint. The
attacker's self-issued collection and self-created marketplaces are exactly the self-dealing setup the
regression test reproduces (the test reuses the real on-chain identifiers: collectionID = "NFLATION-ESGO", asset 1, market name "Inflation Market").
kpulse could not determine the cause from on-chain data alone and flagged the activity for
confirmation; the Klever core team then traced it to the referral+royalty settlement defect described
above and shipped the emergency guard + protocol fix.
Each abusive settlement minted one bid of KLV; the observed bid was 25,600,000 KLV, repeated and
funnelled through a short hop chain before being swapped and bridged. The emergency guard
(common/emergencyGuard.go) blocks the following observed sender public keys (hex):
| Public key (hex) | Address | Role (observed) |
|---|---|---|
54ea28e527d4136508be955374afa54a8c25c19a48c674f412f7ce02db0f4e1b |
klv12n4z3ef86sfk2z97j4fhfta9f2xztsv6frr8faqj7l8q9kc0fcdsfjfqez |
root / minter |
bb687dbba23e1844fec674a32cb8809f0d3207506c53fc3d637e40dc56708d63 |
klv1hd58mwaz8cvyflkxwj3jewyqnuxnyp6sd3flc0tr0eqdc4ns343skngdjq |
collector hop (~125M KLV) |
77388d3dfe6cd88e8da723254c11abf3d9cccb6fb77b000e5038fc3ff92b964d |
klv1wuug6007dnvgard8yvj5cydt70vuejm0kaasqrjs8r7rl7ftjexsglalf6 |
direct recipient (25.6M, idle) |
a196789b026f996867f08317cc6c5a4eb9ad3a59b1be3716420bc8692d4c3048 |
klv15xt83xczd7vkselssvtucmz6f6u66wjekxlrw9jzp0yxjt2vxpyq2nawrw |
hop-2 recipient (25M) |
The single-bid per-settlement size (25.6M KLV) matches the "direct recipient, 25.6M" entry, and the
~125M at the collector hop is consistent with roughly five abusive settlements.
Who can exploit it / prerequisites
- Any account that can pay the one-time collection-create + marketplace-create fees and tx
fees. No roles, admin, or allowlist. - Any client. The settlement is triggered by standard
MarketBuy/Claimcontracts POSTed to
the public, unauthenticated/transaction/sendRPC (network/api/transaction/routes.go,SendTX/BroadcastTX). TheoperatorCLI, the SDKs, or a hand-signedcurlall work. - Deterministic; the abusive state is reached with one extra
UpdateRoyaltiesafter a normal listing.
Layer 0, emergency guard (deployed first, fork-proof), GHSA-p7gw rc1
common/emergencyGuard.go + data/transaction/emergencyGuard.go: matching transactions are kept
out of blocks this node proposes (core/process/block/preprocess/transactions.go) and refused at
the node API (node.go SendTransaction / SendBulkTransactions). It never changes block
validity, so a partial-fleet rollout cannot fork the chain. It blocks the known attacker senders
(all contract types) plus all MarketBuy, Sell, and CreateMarketplace / ConfigMarketplace
operations while the protocol fix rolls out. Enforcement is by proposer cooperation, not protocol ,
coverage equals the share of block producers running the guard.
Layer 1, protocol fix (consensus, epoch-gated), GHSA-p7gw rc2
core/kapp/market/market.go:593 rejects the settlement when marketOwnerAmount < 0, before any
payout runs, returning Transaction_AmountInvalid. Gated behind the new FixMarketBuyOverflow
activation-epoch flag (config/enableEpochs.*, core/fork/forks.go, core/interface.go) so
historical blocks reprocess identically. Covered byTestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation.
Recommended hardening (defense in depth)
- Snapshot the royalty % into
MarketOrderDataatSell(as referral already is) and pay from
the snapshot, eliminating the TOCTOU entirely; or re-evaluate the combinedreferral% + royalty% <= 100%invariant at settlement. - Treat a negative seller remainder as a hard error everywhere, and only treat exactly
0as a
no-op skip (computeMarketOwnerAmount), so a future regression aborts the tx instead of minting. - After splitting a payment pool, assert conservation (
referral + royalties + ownerShare == bid)
so any drift aborts the transaction.
Notes
- Triggered by both BuyItNow (
Buy) and auction settlement (Claim). - The same family of "pay full cut, silently drop the negative remainder" minting also exists in the
royalty-split paths and is tracked separately under GHSA-cgc5-v3f2-8m2v (split-royaltyuint32
overflow). This advisory covers the top-level referral+royalty > bid case; theFixMarketBuyOverflowguard here only checksmarketOwnerAmount, not intra-split over-payments.
Acknowledgments
- KleverPuls / kpulse.tech, community monitoring project that first
detected and characterised the exploitation in the wild. kpulse isolated the attacker wallet and
its "InflationPOC" collections, identified the marketplace wash-trading ofNFLATION-ESGO/1and the
KleverSwap → bridge off-ramp (~$72K to Ethereum), and flagged the anomalous doubling of
marketplace receipts, the exact on-chain signature of this bug, prompting the incident response
that led to this fix. The root cause was then identified and remediated by the Klever core team.
Source
- Vulnerable / fixed code:
core/kapp/market/market.go:540-545,575-596,908,997-998,core/kapp/kda/trigger.go:613-676,core/process/kda/assetHelper.go:101,tools/converters.go:102,core/constants.go:18. - Emergency guard:
common/emergencyGuard.go,data/transaction/emergencyGuard.go,core/process/block/preprocess/transactions.go,node/node.go. - Fork flag:
config/enableEpochs.go,config/node/enableEpochs.yaml,core/fork/forks.go,core/interface.go. - Regression test:
core/kapp/market/market_test.go
(TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation).
Impact
- Unbounded inflation of KLV (and of any sale currency used for the listing), repeatable for only
transaction fees, by any account that creates its own collection + marketplace. - The minted KLV is created by direct
AddToBalanceto attacker addresses (no trackedMint), so
the asset's booked supply does not change, the inflation is off the books and only detectable
by summing balances / auditing receipts (it surfaces on-chain as doubled marketplace receipts). - Realized impact (observed): the attacker minted KLV via ~29 self-dealt settlements, swapped to
stable/wrapped assets on KleverSwap, and off-ramped ~$72K to Ethereum via the bridge (USDC,
USDT, WBTC, WETH) before the emergency guard halted the activity, alongside ~$450K of artificial
NFT wash-trade volume. - Total loss of economic integrity for all KLV / token holders.
CVE-2026-54754 has a CVSS score of 9.6 (Critical). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.7.19); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Shipped as a layered response (embargoed):
Frequently Asked Questions
- What is CVE-2026-54754? CVE-2026-54754 is a critical-severity security vulnerability in github.com/klever-io/klever-go (go), affecting versions < 1.7.19. It is fixed in 1.7.19.
- How severe is CVE-2026-54754? CVE-2026-54754 has a CVSS score of 9.6 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of github.com/klever-io/klever-go are affected by CVE-2026-54754? github.com/klever-io/klever-go (go) versions < 1.7.19 is affected.
- Is there a fix for CVE-2026-54754? Yes. CVE-2026-54754 is fixed in 1.7.19. Upgrade to this version or later.
- Is CVE-2026-54754 exploitable, and should I be worried? Whether CVE-2026-54754 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-54754 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-54754? Upgrade
github.com/klever-io/klever-goto 1.7.19 or later.