{"uuid": "2cca00a1-20c7-4e02-8644-7ec28525c762", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-v86g-q9cm-h8wp", "type": "seen", "source": "https://gist.github.com/prasanna8585/ffd112b1a125ca4c5533fdce45ef57c1", "content": "Reported by: Prasanna Dabi (prasanna8585) Affected project: bifrost-io/bifrost \u2014 vtoken-minting and slpx pallets Status: Unpatched at time of publication. Vendor did not engage after private disclosure (2026-05-27) or CERT/CC coordination outreach (2026-07-13 \u2013 2026-08-31). CWE: CWE-862 (Missing Authorization) / CWE-863 (Incorrect Authorization) Suggested CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N (7.1, High)\n\nSummary\n\nThe vtoken_minting::mint extrinsic (call_index 0) and the slpx::mint extrinsic (call_index 0) both accept a user-supplied channel_id parameter and pass it directly into the channel-commission pallet's record_mint_amount function with no validation that:\n\nchannel_id corresponds to a registered channel in Channels storage, or\nthe caller is authorized to mint on behalf of that channel.\n\nThis allows any signed account to attribute its own mint volume to any already-registered channel, inflating that channel's PeriodChannelVtokenMint and consequently its ChannelVtokenShares. When the commission clearing cycle runs (on_initialize \u2192 update_channel_vtoken_shares \u2192 clear_channel_commissions), the inflated channel receives a disproportionate share of the protocol's hosting-fee commissions, paid out of real funds from the CommissionPalletId account \u2014 diverting revenue that should go to the Bifrost treasury (BifrostCommissionReceiver) to the attacker or a colluding channel operator.\n\nTechnical Details\n\nrecord_mint_amount (pallets/channel-commission/src/lib.rs:1098) blindly records the mint against whatever channel_id was supplied:\n\nrust\nfn record_mint_amount(channel_id: Option, vtoken: CurrencyId,\n    amount: BalanceOf) -&gt; Result&lt;(), DispatchError&gt; {\n    // updates PeriodVtokenTotalMint unconditionally\n    if let Some(channel_id) = channel_id {\n        // NO CHECK: does channel_id exist in Channels storage?\n        // NO CHECK: is the caller authorized for this channel?\n        PeriodChannelVtokenMint::::mutate(channel_id, vtoken, |m| {\n            m.1 = m.1.checked_add(amount)?; Ok(())\n        })?;\n    }\n    Ok(())\n}\n\nBoth entry points reach this function unguarded:\n\npallets/vtoken-minting/src/lib.rs:867-881 \u2014 mint extrinsic accepts an arbitrary channel_id\npallets/vtoken-minting/src/impls.rs:670-676 \u2014 do_mint passes channel_id straight through to record_mint_amount\npallets/slpx/src/lib.rs:595-614 \u2014 slpx::mint reaches the same unvalidated path via VtokenMintingInterface::mint\nProof of Concept / Quantitative Example\n\nNo special access is required beyond a signed account and knowledge of a registered channel_id (observable on-chain via ChannelRegistered events).\n\nVtokenMinting.mint(\n    currency_id: DOT,\n    currency_amount: 10_000_000_000_000,  // 10 DOT\n    remark: [],\n    channel_id: Some(3)   // arbitrary, no validation\n)\n\nQuantitative example: suppose total vtoken minting in a period is 1,000,000 vDOT. Channel A (legitimate) minted 100,000 vDOT \u2014 a 10% share. An attacker mints 900,000 vDOT of their own tokens through channel_id = A. Channel A's recorded mint becomes 1,000,000 of 1,900,000 total channel-attributed mints \u2014 Channel A's commission share jumps from 10% to approximately 52.6%, at the Bifrost treasury's expense. The attacker's own minting is entirely legitimate (their own tokens); only the commission attribution is forged, at no capital risk to the attacker.\n\nImpact\nProtocol revenue theft \u2014 an attacker who controls or colludes with a registered channel's receive_account can redirect real commission tokens (from SLP hosting fees) away from the Bifrost treasury.\nCommission dilution for honest channels \u2014 inflating one channel's mint volume proportionally dilutes and redistributes all other channels' earned commissions.\nNo capital at risk for the attacker \u2014 minting itself is legitimate; only the commission attribution is forged.\nSuggested Fix\n\nValidate channel_id against Channels storage before recording the mint volume:\n\nrust\nif let Some(channel_id) = channel_id {\n    ensure!(\n        Channels::::contains_key(channel_id),\n        Error::::ChannelNotFound\n    );\n    PeriodChannelVtokenMint::::mutate(channel_id, vtoken, |m| {\n        m.1 = m.1.checked_add(amount)?; Ok(())\n    })?;\n}\n\nAdditionally, consider adding a caller-to-channel authorization mapping so that only accounts explicitly authorized by a channel can mint on its behalf \u2014 this would prevent an attacker from inflating a legitimate, registered channel's volume even without colluding with that channel.\n\nDisclosure Timeline\nDate\tEvent\n2026-05-27\tReported privately via GitHub Security Advisory (GHSA-v86g-q9cm-h8wp) and via Bugrap (Serial a0fddaa0-59dc-11f1-9a48-2bb7d4fac303)\n2026-06-07\tFollow-up sent on the Bugrap report; no response\n2026-07-13\tEscalated to CERT/CC for coordination assistance\n2026-07-20 \u2013 2026-08-31\tCERT/CC repeatedly attempted to bring Bifrost into the coordination case; vendor did not join\n2026-08-31\tCERT/CC confirmed the vendor will not be joining the case and offered to prepare a CVE to release alongside this disclosure\n2026-09-04\tPublic disclosure (this document); CVE assignment coordinated with CERT/CC", "creation_timestamp": "2026-09-04T16:16:02.606817Z"}