CVE-2026-82409
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, indexer/common.go serializedDataForUpdateAccounts places the attacker-controlled acc.Name value into an Elasticsearch _bulk JSON and NDJSON request without escaping it. The SetAccountName transaction accepts valid UTF-8 account names containing quotes, backslashes, and newlines, and the resulting name is stored in consensus account state. When an indexer processes the account, those characters can break the JSON string, reject a bulk batch, or inject additional bulk actions that create, overwrite, or delete documents in indices writable by the indexer. The persistent state value is replayed by new or historical indexers, and direct access to the indexing host or Elasticsearch port is not required. This issue is fixed in version 1.7.20.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.4
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, indexer/common.go serializedDataForUpdateAccounts places the attacker-controlled acc.Name value into an Elasticsearch _bulk JSON and NDJSON request without escaping it. The SetAccountName transaction accepts valid UTF-8 account names containing quotes, backslashes, and newlines, and the resulting name is stored in consensus account state. When an indexer processes the account, those characters can break the JSON string, reject a bulk batch, or inject additional bulk actions that create, overwrite, or delete documents in indices writable by the indexer. The persistent state value is replayed by new or historical indexers, and direct access to the indexing host or Elasticsearch port is not required. This issue is fixed in version 1.7.20.
- Record
CVE-2026-82409- CVSS 4.0
- 8.4 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L - Weakness
- CWE-116
- Updated
- 2026-11-02
EUVDEUVD-2026-85647
8.4
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, indexer/common.go serializedDataForUpdateAccounts places the attacker-controlled acc.Name value into an Elasticsearch _bulk JSON and NDJSON request without escaping it. The SetAccountName transaction accepts valid UTF-8 account names containing quotes, backslashes, and newlines, and the resulting name is stored in consensus account state. When an indexer processes the account, those characters can break the JSON string, reject a bulk batch, or inject additional bulk actions that create, overwrite, or delete documents in indices writable by the indexer. The persistent state value is replayed by new or historical indexers, and direct access to the indexing host or Elasticsearch port is not required. This issue is fixed in version 1.7.20.
- Record
EUVD-2026-85647- CVSS 4.0
- 8.4 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-7c7c-373r-gfjj
8.4
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery
Component: Elasticsearch indexer (indexer/)
Primary location: indexer/common.go:2395-2407 (serializedDataForUpdateAccounts)
Entry point: SetAccountName native transaction (contract type 12) — core/process/transaction/txProcess.go:688
Description
When the node indexes account updates to Elasticsearch, it builds the ES _bulk painless-script line by splicing the account's name directly into JSON with fmt.Sprintf("%s", ...) and no escaping:
// indexer/common.go:2395-2407 (serializedDataForUpdateAccounts)
serializedData := []byte(fmt.Sprintf(`{"script":{"source":"`+
`ctx._source.name = params.name; ... `+
`","lang": "painless","params":`+
`{"name": "%s", "nonce": %d, "rootHash": "%s", "balance": %d, ...}}}`,
acc.Name, acc.Nonce, acc.RootHash, acc.Balance, ...)) // acc.Name is RAW
acc.Name originates from on-chain account state: indexer/accountInfo.go:31 sets Name: string(userAccount.GetName()). An account name is fully attacker-controlled and only weakly validated when it is set on-chain by the SetAccountName handler:
// core/kapp/accounts/accounts.go:1740
if !utf8.Valid(tc.GetName()) || len(tc.GetName()) > core.MaxNameSize { ... } // MaxNameSize = 100
The only constraints are valid UTF-8 and length ≤ 100 bytes. Double-quote ("), backslash (\), and newline (\n) are all valid UTF-8 and are not rejected. The safe helper converters.JsonEscape() exists and is used for _id fields elsewhere in the same file (common.go:893, :932, :961) but is not applied to the name.
The resulting buffer is POSTed verbatim to Elasticsearch _bulk by elasticClient.DoBulkRequest (indexer/elasticClient.go:128), with the index in the URL. The _bulk body is NDJSON — newline-delimited action/source pairs (indexer/data/buffer.go:45 appends a \n after every entry). Therefore a name containing a quote and newlines can
- inject arbitrary keys/structure into the document,
- break the batch, and
- inject entirely new bulk operations targeting other documents and other indices.
SetAccountName is a first-class transaction contract type (= 12) dispatched natively at txProcess.go:688 via SetAccountName(tx.GetSender(), tc). The attacker names their own account with the payload in one ordinary signed transaction (normal fee, no contract deploy, no VM gas). (It is additionally exposed as a VM built-in KleverSetAccountName, but that path is not needed.)
The name is written into consensus account state (userAccount.SetName, data/state/userAccount.go:76) and replicated to all nodes. The indexer reads it from state, not from the transaction, during each node's own block processing (core/process/block/block.go:1141 SaveBlock / SaveAccounts). Consequently:
- The attacker does not need any access to the node running the indexer, the ES port, or validator status. One broadcast to the network is enough.
- Indexers typically run on observer/gateway nodes that power the public explorer/API — exactly the realistic victim.
- The payload is durable and replayable: a newly stood-up indexer, or a historical re-index (import-DB mode,
cmd/node/startup.go:153), re-reads the name from state and re-fires the injection.
Escalation — from denial-of-indexing to arbitrary ES document CRUD
Elasticsearch _bulk fails a malformed line differently by position: malformed action line → whole-batch HTTP 400 (nothing applies); malformed source line → per-item error (other items still apply). By appending a sacrificial action after the forged op, the serializer's fixed template tail (", "nonce":...}}}) lands in a source position (item-level error), so a clean forged op that precedes it is applied. This yields arbitrary create / overwrite / delete of documents in any index the indexer's ES credentials can write — cross-index via {"index":{"_index":"...", "_id":"..."}}.
…
- Record
GHSA-7c7c-373r-gfjj- CVSS 4.0
- 8.4 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L - Weakness
- CWE-116
- Updated
- 2026-11-02
NVD
8.4
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, indexer/common.go serializedDataForUpdateAccounts places the attacker-controlled acc.Name value into an Elasticsearch _bulk JSON and NDJSON request without escaping it. The SetAccountName transaction accepts valid UTF-8 account names containing quotes, backslashes, and newlines, and the resulting name is stored in consensus account state. When an indexer processes the account, those characters can break the JSON string, reject a bulk batch, or inject additional bulk actions that create, overwrite, or delete documents in indices writable by the indexer. The persistent state value is replayed by new or historical indexers, and direct access to the indexing host or Elasticsearch port is not required. This issue is fixed in version 1.7.20.
- Record
CVE-2026-82409- CVSS 4.0
- 8.4 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-116
- Updated
- 2026-11-02
OSV
8.4
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, indexer/common.go serializedDataForUpdateAccounts places the attacker-controlled acc.Name value into an Elasticsearch _bulk JSON and NDJSON request without escaping it. The SetAccountName transaction accepts valid UTF-8 account names containing quotes, backslashes, and newlines, and the resulting name is stored in consensus account state. When an indexer processes the account, those characters can break the JSON string, reject a bulk batch, or inject additional bulk actions that create, overwrite, or delete documents in indices writable by the indexer. The persistent state value is replayed by new or historical indexers, and direct access to the indexing host or Elasticsearch port is not required. This issue is fixed in version 1.7.20.
- Record
CVE-2026-82409- CVSS 4.0
- 8.4 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L - Weakness
- CWE-116
- Updated
- 2026-11-02
OSVGHSA-7c7c-373r-gfjj
8.4
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery
Component: Elasticsearch indexer (indexer/)
Primary location: indexer/common.go:2395-2407 (serializedDataForUpdateAccounts)
Entry point: SetAccountName native transaction (contract type 12) — core/process/transaction/txProcess.go:688
Description
When the node indexes account updates to Elasticsearch, it builds the ES _bulk painless-script line by splicing the account's name directly into JSON with fmt.Sprintf("%s", ...) and no escaping:
// indexer/common.go:2395-2407 (serializedDataForUpdateAccounts)
serializedData := []byte(fmt.Sprintf(`{"script":{"source":"`+
`ctx._source.name = params.name; ... `+
`","lang": "painless","params":`+
`{"name": "%s", "nonce": %d, "rootHash": "%s", "balance": %d, ...}}}`,
acc.Name, acc.Nonce, acc.RootHash, acc.Balance, ...)) // acc.Name is RAW
acc.Name originates from on-chain account state: indexer/accountInfo.go:31 sets Name: string(userAccount.GetName()). An account name is fully attacker-controlled and only weakly validated when it is set on-chain by the SetAccountName handler:
// core/kapp/accounts/accounts.go:1740
if !utf8.Valid(tc.GetName()) || len(tc.GetName()) > core.MaxNameSize { ... } // MaxNameSize = 100
The only constraints are valid UTF-8 and length ≤ 100 bytes. Double-quote ("), backslash (\), and newline (\n) are all valid UTF-8 and are not rejected. The safe helper converters.JsonEscape() exists and is used for _id fields elsewhere in the same file (common.go:893, :932, :961) but is not applied to the name.
The resulting buffer is POSTed verbatim to Elasticsearch _bulk by elasticClient.DoBulkRequest (indexer/elasticClient.go:128), with the index in the URL. The _bulk body is NDJSON — newline-delimited action/source pairs (indexer/data/buffer.go:45 appends a \n after every entry). Therefore a name containing a quote and newlines can
- inject arbitrary keys/structure into the document,
- break the batch, and
- inject entirely new bulk operations targeting other documents and other indices.
SetAccountName is a first-class transaction contract type (= 12) dispatched natively at txProcess.go:688 via SetAccountName(tx.GetSender(), tc). The attacker names their own account with the payload in one ordinary signed transaction (normal fee, no contract deploy, no VM gas). (It is additionally exposed as a VM built-in KleverSetAccountName, but that path is not needed.)
The name is written into consensus account state (userAccount.SetName, data/state/userAccount.go:76) and replicated to all nodes. The indexer reads it from state, not from the transaction, during each node's own block processing (core/process/block/block.go:1141 SaveBlock / SaveAccounts). Consequently:
- The attacker does not need any access to the node running the indexer, the ES port, or validator status. One broadcast to the network is enough.
- Indexers typically run on observer/gateway nodes that power the public explorer/API — exactly the realistic victim.
- The payload is durable and replayable: a newly stood-up indexer, or a historical re-index (import-DB mode,
cmd/node/startup.go:153), re-reads the name from state and re-fires the injection.
Escalation — from denial-of-indexing to arbitrary ES document CRUD
Elasticsearch _bulk fails a malformed line differently by position: malformed action line → whole-batch HTTP 400 (nothing applies); malformed source line → per-item error (other items still apply). By appending a sacrificial action after the forged op, the serializer's fixed template tail (", "nonce":...}}}) lands in a source position (item-level error), so a clean forged op that precedes it is applied. This yields arbitrary create / overwrite / delete of documents in any index the indexer's ES credentials can write — cross-index via {"index":{"_index":"...", "_id":"..."}}.
…
- Record
GHSA-7c7c-373r-gfjj- CVSS 4.0
- 8.4 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L - Weakness
- CWE-116
- Updated
- 2026-11-02
OSVGO-2026-6585 No score
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery in github.com/klever-io/klever-go
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery in github.com/klever-io/klever-go
- Record
GO-2026-6585- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/klever-io/klever-goGo |
through 1.7.19 | 1.7.20 |
github.com/klever-io/klever-goGo |
before 1.7.20 | 1.7.20 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-82409 is a high vulnerability (CVSS 8.4) in Klever-io Klever-go, published September 23, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in github.com/klever-io/klever-go 1.7.20.
References
github.com/klever-io/klever-go/security/advisories/GHSA-7c7c-373r-gfjj
github.com/klever-io/klever-go/commit/f00366768b24be18fa7ae9de2e1905caa8b350af
github.com/klever-io/klever-go/commit/f54ea730cc80a3ba4f906586a7d221b281548190
github.com/klever-io/klever-go/releases/tag/v1.7.20
github.com/advisories/GHSA-7c7c-373r-gfjj
github.com/CVEProject/cvelistV5/tree/main/cves/2026/82xxx/CVE-2026-82409.json
nvd.nist.gov/vuln/detail/CVE-2026-82409
github.com/klever-io/klever-go
Left out here: the rest of 2 source descriptions. The JSON record has them.