{"id":"CVE-2026-82405","aliases":["EUVD-2026-85648","GHSA-97cv-x867-6xhm","GO-2026-6582"],"status":"published","title":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller","description":"Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the KleverUpdateAccountPermission built-in authorizes replacement of a target account's permissions by checking attacker-controlled vmInput.RecipientAddr instead of authenticated vmInput.CallerAddr. An attacker-controlled contract can choose a victim account with configured permissions as RecipientAddr, and contractHasValidPermission can accept the victim's default self-signer as authorization. UpdatePermission can then replace the victim's entire permission set with attacker-supplied Owner permissions, enabling asset theft or permanent lockout without a victim key or signature. Accounts without stored permissions and the native transaction path are not affected. This issue is fixed in version 1.7.20.","severity":{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00427,"percentile":0.37465,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-863"],"published":"2026-09-23T19:26:12Z","modified":"2026-10-28T02:30:48Z","fixes":[{"ecosystem":"Go","package":"github.com/klever-io/klever-go","fixed_in":["1.7.20"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/klever-io/klever-go","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.7.19"},{"fixed":"1.7.20"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/klever-io/klever-go","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.7.20"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/klever-io/klever-go","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.7.20"}]}]}],"products":[{"vendor":"klever-io","product":"klever-go"}],"references":[{"url":"https://github.com/klever-io/klever-go/security/advisories/GHSA-97cv-x867-6xhm","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/klever-io/klever-go/commit/c58740eb74d7ee8f07db1e18a7d6214b5559ba32","tags":["x_refsource_MISC","fix","web"]},{"url":"https://gist.github.com/mabdullah22/a41f90aa5ba86bbebf121f739bd5f5e9","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/klever-io/klever-go/releases/tag/v1.7.20","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-97cv-x867-6xhm"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/82xxx/CVE-2026-82405.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-82405","tags":["advisory"]},{"url":"https://github.com/klever-io/klever-go","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-82405","title":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller","description":"Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the KleverUpdateAccountPermission built-in authorizes replacement of a target account's permissions by checking attacker-controlled vmInput.RecipientAddr instead of authenticated vmInput.CallerAddr. An attacker-controlled contract can choose a victim account with configured permissions as RecipientAddr, and contractHasValidPermission can accept the victim's default self-signer as authorization. UpdatePermission can then replace the victim's entire permission set with attacker-supplied Owner permissions, enabling asset theft or permanent lockout without a victim key or signature. Accounts without stored permissions and the native transaction path are not affected. This issue is fixed in version 1.7.20.","status":"published","published":"2026-09-23T19:26:12.967Z","modified":"2026-09-24T15:05:37.458Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T13:40:35.004553Z"}],"euvd":[{"id":"EUVD-2026-85648","description":"Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the KleverUpdateAccountPermission built-in authorizes replacement of a target account's permissions by checking attacker-controlled vmInput.RecipientAddr instead of authenticated vmInput.CallerAddr. An attacker-controlled contract can choose a victim account with configured permissions as RecipientAddr, and contractHasValidPermission can accept the victim's default self-signer as authorization. UpdatePermission can then replace the victim's entire permission set with attacker-supplied Owner permissions, enabling asset theft or permanent lockout without a victim key or signature. Accounts without stored permissions and the native transaction path are not affected. This issue is fixed in version 1.7.20.","status":"published","published":"2026-09-23T19:26:12Z","modified":"2026-09-24T15:05:37Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T17:27:56.686358Z"}],"ghsa":[{"id":"GHSA-97cv-x867-6xhm","title":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller","description":"### Description\n\nThe VM built-in function `KleverUpdateAccountPermission` (registered always-active, `creator.go:381-390` / `core/vmconstants.go:234`) rewrites an account's entire permission set. Its authorization check uses `vmInput.RecipientAddr` **attacker-controlled**  instead of the authenticated `vmInput.CallerAddr`. The sibling handler `kleverChangeOwnerAddress.go:86` uses `vmInput.CallerAddr` correctly, so the safe pattern exists in-repo; this handler deviates. The native transaction path (`txProcess.go:833`) is safe  it uses `tx.GetSender()`.\n\nMechanism:\n1. **Wrong variable:** `CallerAddr` is never referenced in the handler; auth is `contractHasValidPermission(target.GetPermissions(), RecipientAddr)`, which returns true if `RecipientAddr` is a signer with `Weight \u003e= Threshold` in the *target* account's permissions and the permission grants `UpdateAccountPermissionContractType`.\n2. **RecipientAddr is attacker-controlled:** when a contract calls a built-in via `ExecuteOnDestContextWithTypedArgs` (`baseOps.go:1967`), `prepareIndirectContractCallInput` (`baseOps.go:2485`) sets `RecipientAddr = destination` (contract-chosen) and `CallerAddr = the calling contract`. The blockchain hook (`blockChainHook.go:454/467`) dispatches on `input.Function` and passes the input through unchanged; no guard forces `RecipientAddr == CallerAddr` and there is no SC-destination validation on this path.\n3. **Self-signer default satisfies the check:** `createDefaultOwnerPermission` (`accounts.go:1848`) makes an account its own signer (weight 1, threshold 1, Owner type), and `CheckPermissionGrantedForContracts` returns true for Owner, so `contractHasValidPermission(V.perms, V) == true`. (More generally, `RecipientAddr` can be set to *any* of V's signer addresses meeting threshold all public on-chain.) Accounts with no stored permissions have empty `GetPermissions()` and are immune.\n4. **Overwrite is unrestricted:** `UpdatePermission(V, attackerContract)` (`accounts.go:1863`) replaces V's permission set with attacker-supplied signers; if the attacker supplies an Owner-type permission, no default is appended and V's prior control is fully evicted.\n\n### Code walkthrough\n\n**(a) The vulnerable handler** — `core/kapp/builtInFunctions/kleverUpdateAccountPermission.go`:\n```go\nfunc (e *kleverUpdateAccountPermission) ProcessBuiltinFunction(vmInput *vmcommon.ContractCallInput) (*vmcommon.VMOutput, error) {\n    ...\n    address := vmInput.NextArg()                       // Arguments[0] — attacker-chosen target account V\n    contract, err := e.getUpdateAccountPermissionContract(vmInput) // Arguments[1] — attacker-chosen new permissions\n    ...\n    acc, err := e.accountsCacher.LoadUser(address)      // loads V\n    ...\n    // BUG: authorizes against vmInput.RecipientAddr (attacker-controlled), NOT vmInput.CallerAddr\n    if !e.contractHasValidPermission(acc.GetPermissions(), vmInput.RecipientAddr) {   // L91\n        return nil, errors.New(\"invalid permission operation\")\n    }\n    // overwrites V's entire permission set with attacker-supplied signers\n    resultCode, err := e.kappController.GetAccountsKApp().UpdatePermission(address, contract)\n    ...\n}\n```\n\n**(b) The check just name-matches `recipientAddr` against V's own signers** — same file:\n```go\nfunc (e *kleverUpdateAccountPermission) contractHasValidPermission(permissions []*state.Permission, recipientAddr []byte) bool {\n    for _, permission := range permissions {\n        for _, signer := range permission.Signers {\n            if !bytes.Equal(signer.Address, recipientAddr) {   // recipientAddr, not the authenticated caller\n                continue\n            }\n            if signer.Weight \u003e= permission.Threshold \u0026\u0026\n                permission.CheckPermissionGrantedForContracts(transaction.TXContract_UpdateAccountPermissionContractType) {\n                return true\n            }\n        }\n    }\n    return false\n}\n```\n\n**(c) The dispatch makes `RecipientAddr` attacker-controlled** — `kvm/vmhost/vmhooks/baseOps.go:2464` `prepareIndirectContractCallInput` (invoked when a contract calls the built-in via `ExecuteOnDestContext`):\n```go\ncontractCallInput := \u0026vmcommon.ContractCallInput{\n    VMInput: vmcommon.VMInput{\n        CallerAddr: sender,        // the calling contract (authenticated) — NOT used by the handler\n        Arguments:  data,          // attacker-chosen: [V, attackerPermissions]\n        ...\n    },\n    RecipientAddr: destination,    // the contract's chosen `dest` argument — attacker sets this to V\n    Function:      string(function),\n}\n```\n\n**(d) Every account with configured permissions is its own signer** — `core/kapp/accounts/accounts.go:1848` `createDefaultOwnerPermission` (appended by `UpdatePermission` when no Owner permission is supplied):\n```go\nreturn \u0026state.Permission{\n    Type:      state.Permission_Owner,   // Owner grants ALL contract types incl. type 22\n    Threshold: 1,\n    Signers: []*state.Key{\n        { Address: ownerAcc.AddressBytes(), Weight: 1 },   // the account signs for itself\n    },\n}\n```\nSo `contractHasValidPermission(V.perms, RecipientAddr=V)` finds V's own address as a `Weight 1 \u003e= Threshold 1` Owner signer → returns `true`.\n\n**(e) Contrast — the sibling handler does it correctly** — `core/kapp/builtInFunctions/kleverChangeOwnerAddress.go:86`:\n```go\ncallerAddress := vmInput.CallerAddr                        // authenticated caller\n...\nif !bytes.Equal(callerAddress, acc.GetOwnerAddress()) {    // checks the CALLER, not RecipientAddr\n    return nil, ErrOperationNotPermitted\n}\n```\n\n**Putting it together — the attacker's contract call:**\n```\nExecuteOnDestContext(\n    gas, dest = V,                       // → RecipientAddr = V\n    value = 0,\n    function = \"KleverUpdateAccountPermission\",\n    args = [ V, attackerOwnerPermsWithOnlyAttackerKey ],   // Arguments[0]=V, Arguments[1]=new perms\n)\n```\n→ `CallerAddr = attackerContract` (ignored), `RecipientAddr = V`, `contractHasValidPermission(V.perms, V) == true` → V's permissions overwritten with the attacker's key as sole Owner signer. The attacker never held a key of V and provided no signature from V.\n\n### POC\n\nput the following poc testcase under /core/kapp/builtInFunctions/ \n\nPOC Code: https://gist.github.com/mabdullah22/a41f90aa5ba86bbebf121f739bd5f5e9\n\nRun:\n```\ncd klever-go\nGOTOOLCHAIN=auto go test ./core/kapp/builtInFunctions/ -run TestPoC_PermTakeover -v\n```\nOutput:\n```\nTAKEOVER CONFIRMED: caller=\"attacker-contract\" (attacker SC) rewrote account V=\"victim-account-V\"; new sole owner signer=\"attacker-key-EVIL\"\n--- PASS: TestPoC_PermTakeover\n--- PASS: TestPoC_PermTakeover_NoStoredPermsIsSafe\n```\nThe harm asserted is the takeover itself: after the call, V's permission set is a single Owner permission whose sole signer is the attacker's key; V's original owner signer is gone.\n\n### Impact\n\nFull takeover of **any account that has configured permissions**  i.e. every multisig / advanced-permission account , by an attacker who deploys a cheap smart contract and supplies only public on-chain addresses (no keys, no signatures from the victim). After takeover the attacker controls all of the victim's operations → theft or permanent lock of all the account's assets. Reachable via a permissionlessly-deployed contract (the plain-tx path is safe, so it is Critical-via-contract, not fully no-contract). No fork flag gates it.\n\nSeverity **Critical**: Impact High (full account/asset compromise)\n\n### Recommendation\n\nAuthorize against the authenticated caller, mirroring `kleverChangeOwnerAddress`:\n```go\nif !e.contractHasValidPermission(acc.GetPermissions(), vmInput.CallerAddr) { ... }\n```\nReconcile the SC-call authority model: on the built-in path `CallerAddr` is the calling contract, so a contract should only be able to update permissions of accounts that legitimately list *it* as an authorized signer — never an arbitrary victim. Consider also requiring the target account (`Arguments[0]`) to equal the authorized caller's account, matching the native `tx.GetSender()` model.","status":"published","published":"2026-09-23T19:27:08Z","modified":"2026-09-23T19:27:09Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T14:41:20.575159Z"}],"nvd":[{"id":"CVE-2026-82405","description":"Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the KleverUpdateAccountPermission built-in authorizes replacement of a target account's permissions by checking attacker-controlled vmInput.RecipientAddr instead of authenticated vmInput.CallerAddr. An attacker-controlled contract can choose a victim account with configured permissions as RecipientAddr, and contractHasValidPermission can accept the victim's default self-signer as authorization. UpdatePermission can then replace the victim's entire permission set with attacker-supplied Owner permissions, enabling asset theft or permanent lockout without a victim key or signature. Accounts without stored permissions and the native transaction path are not affected. This issue is fixed in version 1.7.20.","status":"published","published":"2026-09-23T20:17:16.37Z","modified":"2026-09-24T16:17:12.153Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/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","level":"high","assessor":"secondary"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T14:12:51.036099Z"}],"osv":[{"id":"CVE-2026-82405","title":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller","description":"Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the KleverUpdateAccountPermission built-in authorizes replacement of a target account's permissions by checking attacker-controlled vmInput.RecipientAddr instead of authenticated vmInput.CallerAddr. An attacker-controlled contract can choose a victim account with configured permissions as RecipientAddr, and contractHasValidPermission can accept the victim's default self-signer as authorization. UpdatePermission can then replace the victim's entire permission set with attacker-supplied Owner permissions, enabling asset theft or permanent lockout without a victim key or signature. Accounts without stored permissions and the native transaction path are not affected. This issue is fixed in version 1.7.20.","status":"published","published":"2026-09-23T19:26:12.967Z","modified":"2026-10-28T02:30:48.067693304Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T15:45:18.131258Z"},{"id":"GHSA-97cv-x867-6xhm","title":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller","description":"### Description\n\nThe VM built-in function `KleverUpdateAccountPermission` (registered always-active, `creator.go:381-390` / `core/vmconstants.go:234`) rewrites an account's entire permission set. Its authorization check uses `vmInput.RecipientAddr` **attacker-controlled**  instead of the authenticated `vmInput.CallerAddr`. The sibling handler `kleverChangeOwnerAddress.go:86` uses `vmInput.CallerAddr` correctly, so the safe pattern exists in-repo; this handler deviates. The native transaction path (`txProcess.go:833`) is safe  it uses `tx.GetSender()`.\n\nMechanism:\n1. **Wrong variable:** `CallerAddr` is never referenced in the handler; auth is `contractHasValidPermission(target.GetPermissions(), RecipientAddr)`, which returns true if `RecipientAddr` is a signer with `Weight \u003e= Threshold` in the *target* account's permissions and the permission grants `UpdateAccountPermissionContractType`.\n2. **RecipientAddr is attacker-controlled:** when a contract calls a built-in via `ExecuteOnDestContextWithTypedArgs` (`baseOps.go:1967`), `prepareIndirectContractCallInput` (`baseOps.go:2485`) sets `RecipientAddr = destination` (contract-chosen) and `CallerAddr = the calling contract`. The blockchain hook (`blockChainHook.go:454/467`) dispatches on `input.Function` and passes the input through unchanged; no guard forces `RecipientAddr == CallerAddr` and there is no SC-destination validation on this path.\n3. **Self-signer default satisfies the check:** `createDefaultOwnerPermission` (`accounts.go:1848`) makes an account its own signer (weight 1, threshold 1, Owner type), and `CheckPermissionGrantedForContracts` returns true for Owner, so `contractHasValidPermission(V.perms, V) == true`. (More generally, `RecipientAddr` can be set to *any* of V's signer addresses meeting threshold all public on-chain.) Accounts with no stored permissions have empty `GetPermissions()` and are immune.\n4. **Overwrite is unrestricted:** `UpdatePermission(V, attackerContract)` (`accounts.go:1863`) replaces V's permission set with attacker-supplied signers; if the attacker supplies an Owner-type permission, no default is appended and V's prior control is fully evicted.\n\n### Code walkthrough\n\n**(a) The vulnerable handler** — `core/kapp/builtInFunctions/kleverUpdateAccountPermission.go`:\n```go\nfunc (e *kleverUpdateAccountPermission) ProcessBuiltinFunction(vmInput *vmcommon.ContractCallInput) (*vmcommon.VMOutput, error) {\n    ...\n    address := vmInput.NextArg()                       // Arguments[0] — attacker-chosen target account V\n    contract, err := e.getUpdateAccountPermissionContract(vmInput) // Arguments[1] — attacker-chosen new permissions\n    ...\n    acc, err := e.accountsCacher.LoadUser(address)      // loads V\n    ...\n    // BUG: authorizes against vmInput.RecipientAddr (attacker-controlled), NOT vmInput.CallerAddr\n    if !e.contractHasValidPermission(acc.GetPermissions(), vmInput.RecipientAddr) {   // L91\n        return nil, errors.New(\"invalid permission operation\")\n    }\n    // overwrites V's entire permission set with attacker-supplied signers\n    resultCode, err := e.kappController.GetAccountsKApp().UpdatePermission(address, contract)\n    ...\n}\n```\n\n**(b) The check just name-matches `recipientAddr` against V's own signers** — same file:\n```go\nfunc (e *kleverUpdateAccountPermission) contractHasValidPermission(permissions []*state.Permission, recipientAddr []byte) bool {\n    for _, permission := range permissions {\n        for _, signer := range permission.Signers {\n            if !bytes.Equal(signer.Address, recipientAddr) {   // recipientAddr, not the authenticated caller\n                continue\n            }\n            if signer.Weight \u003e= permission.Threshold \u0026\u0026\n                permission.CheckPermissionGrantedForContracts(transaction.TXContract_UpdateAccountPermissionContractType) {\n                return true\n            }\n        }\n    }\n    return false\n}\n```\n\n**(c) The dispatch makes `RecipientAddr` attacker-controlled** — `kvm/vmhost/vmhooks/baseOps.go:2464` `prepareIndirectContractCallInput` (invoked when a contract calls the built-in via `ExecuteOnDestContext`):\n```go\ncontractCallInput := \u0026vmcommon.ContractCallInput{\n    VMInput: vmcommon.VMInput{\n        CallerAddr: sender,        // the calling contract (authenticated) — NOT used by the handler\n        Arguments:  data,          // attacker-chosen: [V, attackerPermissions]\n        ...\n    },\n    RecipientAddr: destination,    // the contract's chosen `dest` argument — attacker sets this to V\n    Function:      string(function),\n}\n```\n\n**(d) Every account with configured permissions is its own signer** — `core/kapp/accounts/accounts.go:1848` `createDefaultOwnerPermission` (appended by `UpdatePermission` when no Owner permission is supplied):\n```go\nreturn \u0026state.Permission{\n    Type:      state.Permission_Owner,   // Owner grants ALL contract types incl. type 22\n    Threshold: 1,\n    Signers: []*state.Key{\n        { Address: ownerAcc.AddressBytes(), Weight: 1 },   // the account signs for itself\n    },\n}\n```\nSo `contractHasValidPermission(V.perms, RecipientAddr=V)` finds V's own address as a `Weight 1 \u003e= Threshold 1` Owner signer → returns `true`.\n\n**(e) Contrast — the sibling handler does it correctly** — `core/kapp/builtInFunctions/kleverChangeOwnerAddress.go:86`:\n```go\ncallerAddress := vmInput.CallerAddr                        // authenticated caller\n...\nif !bytes.Equal(callerAddress, acc.GetOwnerAddress()) {    // checks the CALLER, not RecipientAddr\n    return nil, ErrOperationNotPermitted\n}\n```\n\n**Putting it together — the attacker's contract call:**\n```\nExecuteOnDestContext(\n    gas, dest = V,                       // → RecipientAddr = V\n    value = 0,\n    function = \"KleverUpdateAccountPermission\",\n    args = [ V, attackerOwnerPermsWithOnlyAttackerKey ],   // Arguments[0]=V, Arguments[1]=new perms\n)\n```\n→ `CallerAddr = attackerContract` (ignored), `RecipientAddr = V`, `contractHasValidPermission(V.perms, V) == true` → V's permissions overwritten with the attacker's key as sole Owner signer. The attacker never held a key of V and provided no signature from V.\n\n### POC\n\nput the following poc testcase under /core/kapp/builtInFunctions/ \n\nPOC Code: https://gist.github.com/mabdullah22/a41f90aa5ba86bbebf121f739bd5f5e9\n\nRun:\n```\ncd klever-go\nGOTOOLCHAIN=auto go test ./core/kapp/builtInFunctions/ -run TestPoC_PermTakeover -v\n```\nOutput:\n```\nTAKEOVER CONFIRMED: caller=\"attacker-contract\" (attacker SC) rewrote account V=\"victim-account-V\"; new sole owner signer=\"attacker-key-EVIL\"\n--- PASS: TestPoC_PermTakeover\n--- PASS: TestPoC_PermTakeover_NoStoredPermsIsSafe\n```\nThe harm asserted is the takeover itself: after the call, V's permission set is a single Owner permission whose sole signer is the attacker's key; V's original owner signer is gone.\n\n### Impact\n\nFull takeover of **any account that has configured permissions**  i.e. every multisig / advanced-permission account , by an attacker who deploys a cheap smart contract and supplies only public on-chain addresses (no keys, no signatures from the victim). After takeover the attacker controls all of the victim's operations → theft or permanent lock of all the account's assets. Reachable via a permissionlessly-deployed contract (the plain-tx path is safe, so it is Critical-via-contract, not fully no-contract). No fork flag gates it.\n\nSeverity **Critical**: Impact High (full account/asset compromise)\n\n### Recommendation\n\nAuthorize against the authenticated caller, mirroring `kleverChangeOwnerAddress`:\n```go\nif !e.contractHasValidPermission(acc.GetPermissions(), vmInput.CallerAddr) { ... }\n```\nReconcile the SC-call authority model: on the built-in path `CallerAddr` is the calling contract, so a contract should only be able to update permissions of accounts that legitimately list *it* as an authorized signer — never an arbitrary victim. Consider also requiring the target account (`Arguments[0]`) to equal the authorized caller's account, matching the native `tx.GetSender()` model.","status":"published","published":"2026-09-23T19:27:08Z","modified":"2026-10-01T20:56:19.906249303Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T15:46:43.524801Z"},{"id":"GO-2026-6582","title":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller in github.com/klever-io/klever-go","description":"Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller in github.com/klever-io/klever-go","status":"published","published":"2026-10-01T20:23:38Z","modified":"2026-10-01T20:45:14.079819826Z","updated_at":"2026-11-02T15:47:03.11707Z"}]}}
