{"answers":{"affected":"3 open source packages ship an affected version, including github.com/mezo-org/mezod.","exploited":"There is no known exploitation of GHSA-6447-269v-g68m in the wild and no public exploit code.","fix":"Upgrade github.com/mezo-org/mezod to 8.0.0 or later.","what":"GHSA-6447-269v-g68m is a vulnerability in github.com/mezo-org/mezod, published on 6 May 2026. Any signed-in user can exploit it over the network, with no action from the victim."},"fix_prompt":"Check this codebase for GHSA-6447-269v-g68m and fix it if it is affected.\n\nWhat it is: GHSA-6447-269v-g68m is a vulnerability in github.com/mezo-org/mezod, published on 6 May 2026. Any signed-in user can exploit it over the network, with no action from the victim.\nExploitation: There is no known exploitation of GHSA-6447-269v-g68m in the wild and no public exploit code.\n\nAffected packages and fixed versions:\n- github.com/mezo-org/mezod (Go): affected all versions; no fix listed\n- github.com/mezo-org/mezod (Go): affected all versions; fixed in 8.0.0\n- github.com/mezo-org/mezod (Go): affected all versions; fixed in 8.0.0\n\nTask:\n1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.\n2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.\n3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.\n4. Run the tests and summarise what changed and why.\n5. If nothing in the project is affected, say so and change nothing.\n\nReference: https://insights.sbomify.com/GHSA-6447-269v-g68m/ (Markdown: https://insights.sbomify.com/GHSA-6447-269v-g68m/index.md)","id":"GHSA-6447-269v-g68m","markdown":"https://insights.sbomify.com/GHSA-6447-269v-g68m/index.md","name":"GHSA-6447-269v-g68m","record":{"aliases":["GO-2026-5165"],"analysis":{"component_type":"server_software","fix_type":"patch","impacts":["data_modify","security_bypass"],"network_reachable":"yes","non_default_config":false,"product_category":"Mezo blockchain validator","requires_auth":"low_privilege","summary":"A logic flaw in the Mezo validator allows attackers to drain L1 bridge tokens by exploiting state synchronization issues during ERC-20 burns.","user_interaction":"not_required","vuln_class":"business_logic"},"analysis_generated_by":"qwen3.8-flash-next","assigner":null,"cpe":[],"cvss":{"score":null,"severity":null,"source":null,"vector":null,"version":null},"cwes":[],"description":"**Note: the fixed version of the validator client has been deployed for some time.**\n\n### Impact\n\nPotential full drain of L1 bridge without changing bridged balance on Mezo.\n\n## Brief/Intro\n\nA malicious user can steal all ERC-20 tokens locked in the L1 bridge by repeatedly calling the `bridgeOut` precompile from a crafted contract. The precompile's ERC-20 burn executes in an **inner StateDB** that commits to a cache context, but the **outer StateDB** still holds stale pre-burn storage slots in its `dirtyStorage`. When the outer transaction commits, it overwrites the inner burn with stale values -- **restoring the attacker's balance and allowance** while the `AssetsUnlocked` event has already been persisted to the bridge store. The Ethereum sidecar observes this event and attests the unlock on L1, releasing real tokens to the attacker. The attacker keeps their Mezo balance intact and can repeat the drain every block.\n\n## Vulnerability Details\n\nMezo's EVM uses a dual-context state architecture for precompile calls. When a precompile is invoked, `Contract.Run()` calls `stateDB.CacheContext()` which creates a `cachedCtx` branched from the base `ctx`. Cosmos-side state changes (like ERC-20 burns) happen on `cachedCtx`, while standard EVM storage operations (SLOAD/SSTORE) operate on the outer StateDB's `dirtyStorage` backed by `baseCtx`.\n\nThe `bridgeOut` precompile handles two token types with fundamentally different mechanisms:\n\n- **BTC (`burnBitcoin`)**: Burns via `x/bank` (Cosmos native), then explicitly records a `journal.SubBalance` entry. `syncJournalEntries()` propagates this to the outer StateDB, keeping both contexts in sync.\n- **ERC-20 (`burnERC20`)**: Burns via `ExecuteContractCall`, which creates an **entirely new inner StateDB** operating on `cachedCtx`. This inner StateDB executes `burnFrom` (decreasing balance, supply, and allowance slots), commits its changes to `cachedCtx`, and is discarded. **No journal entry is created.** The outer StateDB is never informed of these storage changes.\n\nThis creates a critical asymmetry. After `burnERC20` returns, the outer StateDB's `dirtyStorage` still contains the **pre-burn allowance** (written by `approve` earlier in the same transaction), and has **never loaded** the balance or supply slots. When the attacker triggers a subsequent `transfer(sink, 1)` in the same transaction, the outer StateDB performs a `GetCommittedState` on the balance slot. This reads from `baseCtx` -- which holds the **stale pre-burn value** because `cachedCtx` changes haven't been flushed to `baseCtx` yet. The stale balance minus 1 is written to `dirtyStorage`.\n\nAt end-of-transaction, `StateDB.Commit()` executes in this order:\n\n```go\nfunc (s *StateDB) Commit() error {\n    if s.flushCache != nil {\n        s.flushCache()    // Step 1: flush cachedCtx → baseCtx (inner burn lands)\n    }\n    return s.commit(s.ctx) // Step 2: write dirtyStorage → baseCtx (stale values overwrite)\n}\n```\n\nStep 1 flushes the inner burn into `baseCtx`. Step 2 then iterates every slot in `dirtyStorage` and calls `keeper.SetState` on `baseCtx` -- **unconditionally**, without comparing against current values:\n\n```go\nfunc (s *StateDB) commit(ctx sdk.Context) error {\n    for _, addr := range s.journal.sortedDirties() {\n        obj := s.stateObjects[addr]\n        // ...\n        for _, key := range obj.dirtyStorage.SortedKeys() {\n            s.keeper.SetState(ctx, obj.Address(), key, obj.dirtyStorage[key].Bytes())\n        }\n    }\n    return nil\n}\n```\n\nThe stale allowance slot in `dirtyStorage` (from the `approve` before `bridgeOut`) overwrites the zeroed-out allowance that the inner burn wrote. The stale balance slot (from the `transfer` after `bridgeOut`) overwrites the zeroed-out balance. **The burn is erased.**\n\nMeanwhile, `SaveAssetsUnlocked` was called on `cachedCtx` during the precompile execution, persisting the `AssetsUnlockedEvent` to the bridge module's KV store. This event is flushed to `baseCtx` at Step 1 and survives Step 2 (different KV store prefix, not touched by `dirtyStorage`). The Ethereum sidecar observes this event and calls `AttestBridgeOut` on the L1 `MezoBridge` contract, releasing real tokens to the attacker's L1 address.\n\nThe attack is **repeatable per-block**. Each execution costs only 1 wei (the `transfer` trigger) while draining the full `bridgeOut` amount from L1. There is no supply assertion for ERC-20 tokens (only `abtc` is checked in `verifyBTCSupply`), so no end-of-block invariant catches the mismatch.\n\n## Impact Details\nCRITICAL - Using a fixed balance of e.g., 10,000 USDC on mezo an attacker can completly drain the USDC balance on ETH mainnet bridge via repeated exploit transactions on mezo.\n\nThe Mezo bridge on L1 (0xF6680EA3b480cA2b72D96ea13cCAF2cFd8e6908c) holds approximatively ~1,753,958.4 USD worth of assets that can be stolen, taking into account offramp limit safeguard. Below are the details of asset that can be drained:\n\n- cbBTC (mcbBTC: 0x6a7CD8E1384d49f502b4A4CE9aC9eb320835c5d7):\n\nlimit: 900000000 (9 cbBTC)\ndecimals: 8\nL1 bridge balance: 25.01605546 cbBTC\nVALUE AT RISK: (9 cbBTC) 633,190.53 USD\n\n- T (mT: 0xaaC423eDC4E3ee9ef81517e8093d52737165b71F):\n\nlimit: 60000000000000000000000000 (60,000,000 T)\ndecimals: 18\nL1 bridge balance: 426,294,138.843419368493748432 T\nVALUE AT RISK: (60,000,000 T) 397,537.57 USD AT RISK\n\n- USDC (mUSDC: 0x04671C72Aab5AC02A03c1098314b1BB6B560c197):\n\nlimit: 10000000000000 (10,000,000 USDC)\ndecimals: 6\nL1 bridge balance: 508,549.393027 USDC\nVALUE AT RISK: 508,549.39 USDC AT RISK\n\n- USDT (mUSDT: 0xeB5a5d39dE4Ea42C2Aa6A57EcA2894376683bB8E):\n\nlimit: 10000000000000 (10,000,000 USDT)\ndecimals: 6\nL1 bridge balance: 85,247.963024 USDT\nVALUE AT RISK: 85,247.96 USDT\n\n- xSolvBTC (mxSolvBTC: 0xdF708431162Ba247dDaE362D2c919e0fbAfcf9DE)\n\nlimit: 90000000000000000000 (90 xSolvBTC)\ndecimals: 18\nL1 bridge balance: 1.103740676354553591 xSolvBTC\nVALUE AT RISK: 1.10374068 xSolvBTC (76,765.16 USD)\n\n- SolvBTC (msolvBTC: 0xa10aD2570ea7b93d19fDae6Bd7189fF4929Bc747)\n\nlimit 90000000000000000000 (90 solvBTC)\ndecimals: 18\nL1 bridge balance: 0.496396263084933802 SolvBTC\nVALUE AT RISK: 0.49639626 solvBTC (34,920.48 USD)\n\n- FunctionBTC (mFBTC: 0x812fcC0Bb8C207Fd8D6165a7a1173037F43B2dB8)\n\nlimit: 900000000 (9 FBTC)\ndecimals: 8\nL1 bridge balance: 0.18064148 FBTC\nVALUE AT RISK: 0.18064148 FBTC (12,741.73 USD)\n\n- USDe (mUSDe: 0xdf6542260a9F768f07030E4895083F804241F4C4)\n\nlimit: 1000000000000000000000000 (1,000,000.00 USDe)\ndecimals: 18\nL1 bridge balance: 4,123.909662607937020208 USDe\nVALUE AT RISK: 4,123.20 USDe\n\n- swBTC (mswBTC: 0x29fA8F46CBB9562b87773c8f50a7F9F27178261c)\n\nlimit: 900000000 (9 swBTC)\ndecimals: 8\nL1 bridge balance: 0.02709065 swBTC\nVALUE AT RISK: 0.02709065 (2,904.74 USD)\n\n- DAI (mDAI: 0x1531b6e3d51BF80f634957dF81A990B92dA4b154)\n\nlimit: 1000000000000000000000000 (1,000,000.00 DAI)\ndecimals: 18\nL1 bridge balance: 882.421484670245389691 DAI\nVALUE AT RISK: 882.38 DAI\n\n**Total assets currently at risk taking into consideration limits per ERC20 token and available drainable balance on the bridge: 1,753,958.4 USD of assets on L1.**\n\n## References\n\n- `x/evm/statedb/statedb.go:677-684` -- `Commit()` flushes cache context then writes `dirtyStorage`, allowing stale outer slots to overwrite inner burn results. -\u003e https://github.com/mezo-org/mezod/blob/17af5fdf29a6884d52e5ba6a1ee788c1f6e2a5ab/x/evm/statedb/statedb.go#L677-L684\n- `x/evm/statedb/statedb.go:655-674` -- `commit()` writes every `dirtyStorage` entry to the KV store unconditionally, without checking if the value has been superseded by the inner context flush. -\u003e https://github.com/mezo-org/mezod/blob/17af5fdf29a6884d52e5ba6a1ee788c1f6e2a5ab/x/evm/statedb/statedb.go#L655-L674\n- `x/evm/statedb/state_object.go:236-244` -- `GetCommittedState()` reads from `s.db.ctx` (the base context), returning stale pre-burn values for slots not yet flushed from `cachedCtx`. -\u003e https://github.com/mezo-org/mezod/blob/17af5fdf29a6884d52e5ba6a1ee788c1f6e2a5ab/x/evm/statedb/state_object.","ecosystems":["Go"],"exploits":{"exploitdb":null,"first_seen":null,"github_poc":null,"max_poc_stars":null,"metasploit":null,"nuclei":null,"refs":[],"tier":null},"has_fix":true,"headline":"Mezo: ERC-20 bridgeOut burn can be erased by a stale StateDB overwrite leading to full L1 bridge drain","id":"GHSA-6447-269v-g68m","indexable":true,"kev":null,"kind":"advisory","links":{"ecosystem":"Go","product":null,"products":[],"type":"business_logic","year":"2026"},"n_packages":3,"n_references":null,"packages":[{"ecosystem":"Go","fixed":[],"introduced":["0"],"last_affected":[],"package":"github.com/mezo-org/mezod","purl":"pkg:golang/github.com/mezo-org/mezod","source":"GO-2026-5165"},{"ecosystem":"Go","fixed":["8.0.0"],"introduced":["0"],"last_affected":[],"package":"github.com/mezo-org/mezod","purl":null,"source":"GHSA-6447-269v-g68m"},{"ecosystem":"Go","fixed":["8.0.0"],"introduced":["0"],"last_affected":[],"package":"github.com/mezo-org/mezod","purl":"pkg:golang/github.com/mezo-org/mezod","source":"GHSA-6447-269v-g68m"}],"products":null,"published":"2026-05-06","related":[],"sources":[{"class":"attribution","licence":"CC BY 4.0","name":"GitHub Advisory Database","prefix":"GHSA"},{"class":"attribution","licence":"CC BY 4.0","name":"Go Vulnerability Database","prefix":"GO"}],"ssvc":{"automatable":null,"exploitation":null,"technical_impact":null},"state":null,"updated":null,"vendors":null},"summary":"A logic flaw in the Mezo validator allows attackers to drain L1 bridge tokens by exploiting state synchronization issues during ERC-20 burns.","url":"https://insights.sbomify.com/GHSA-6447-269v-g68m/"}