Feb 14, 2026·6 min read·22 visits
CometBFT's 'BFT Time' calculation failed to verify that timestamps came from active validators. An attacker can skew the block time forward, breaking smart contracts and vesting logic.
A critical consensus vulnerability in CometBFT allows malicious proposers to manipulate the blockchain's internal clock ('BFT Time') by injecting invalid signatures into block commits. This 'Tachyon' attack bypasses validator set verification during time calculation, enabling attackers to artificially advance block timestamps and potentially trigger premature vesting or disrupt time-sensitive smart contracts.
In the world of distributed systems, 'time' is a messy hallucination we all agree to share. For blockchains built on the Cosmos stack, that hallucination is maintained by CometBFT (formerly Tendermint). It doesn't rely on NTP or a central atomic clock; instead, it uses a deterministic algorithm called BFT Time. This is supposed to be the bedrock of the chain—a weighted median of timestamps provided by validators in the previous block.
Why does this matter? Because everything in DeFi relies on it. Unbonding periods, vesting schedules, oracle updates, and governance timeouts all query this ctx.BlockTime(). If you control the clock, you don't just control the history; you control the future assets of the chain.
Enter GHSA-C32P-WCQJ-J677, alias 'Tachyon'. It turns out that the mechanism CometBFT used to calculate this median time was a little too trusting. It assumed that the data fed into the time calculation was strictly from the VIP list (the active validator set). It wasn't. This vulnerability is the equivalent of letting a random passerby yell the time into the room, and the blockchain deciding, 'Yeah, sounds about right.'
The core of the issue lies in how MedianTime is calculated. In CometBFT, the timestamp for Block $H$ is derived from the Precommit votes of Block $H-1$. The logic is sound in theory: take the weighted median of the timestamps from the validators who signed the previous block. This prevents a single malicious validator with 1% voting power from setting the time to the year 3000.
However, the implementation had a gaping logic error. The function responsible for calculating this median iterated through the signatures in the LastCommit structure, but it failed to strictly enforce membership checks against the active validator set during the calculation phase.
A malicious block proposer could craft a LastCommit that included signatures with valid structures but wild timestamps. Because MakeBlock (the block construction method) didn't force MedianTime to validate that every timestamp contributor was actually allowed to vote, the algorithm happily ingested these outliers. The math works perfectly—it calculates the median—but the input data is poisoned. It's a classic case of 'Garbage In, Garbage Out,' except the garbage is a timestamp set to unlock your vesting tokens three years early.
Let's look at the smoking gun in state/state.go. The vulnerability stems from a lack of error propagation and validation context.
Before the Fix:
The MedianTime function likely returned a time value without complaining, even if the input data was sketchy. The MakeBlock function would simply take this value and stamp it onto the new block.
After the Fix (Commit bf8274f):
The developers introduced a hard stop. MedianTime now cross-references the signatures against state.LastValidators. If a signature claims to be from Index 5, the code checks if the address actually matches the validator at Index 5. Crucially, MakeBlock now handles errors returned by MedianTime.
Here is the diff that saves the ecosystem:
// state/state.go
func (state State) MakeBlock(...) (*types.Block, error) {
// ... snip ...
// The fix introduces error handling here
ts, err := MedianTime(lastCommit, state.LastValidators)
if err != nil {
// Now we panic/error out instead of creating a broken block
return nil, fmt.Errorf("error making block while calculating median time: %w", err)
}
timestamp = ts
// ... snip ...
}Furthermore, deep in types/validation.go, checks were added to ensure that commit.Signatures[i] corresponds strictly to the validator at that index. If the proposer tries to inject a 'ghost' signature to skew the time, the validation logic now throws a ErrInvalidCommitSignature.
So, how do we weaponize this? You need to be a Validator with proposing rights (or have compromised one). The attack vector isn't about breaking cryptography; it's about abusing the state machine's logic.
The Attack Chain:
LastCommit for the previous block. They collect real signatures from honest validators (to ensure the block passes validity checks), but they interleave or append crafted entries. These entries contain timestamps set far in the future.MedianTime function includes them in the weighted list.block.timestamp, see that the 'future' has arrived, and release funds. The attacker unbonds instantly or triggers time-based arbitrage opportunities.The severity of this bug cannot be overstated for an application-specific blockchain. In standard web apps, a wrong clock might mess up your logs. In crypto, time is money.
The fact that this was labeled 'Critical' and 'Catastrophic' by the maintainers suggests they found a working PoC that could reliably trigger these conditions in a live network.
The remediation is straightforward but essential: Validation must happen at the source.
The patch ensures that MedianTime is no longer a passive calculator. It acts as a gatekeeper. It explicitly verifies that every timestamp contributing to the median comes from a legitimate, active validator. If the Proposer tries to slip in a fake vote to skew the math, the function returns an error, the block creation fails, and the attack is neutralized.
Action Plan:
v0.37.18 or v0.38.21.ErrInvalidCommitSignature or failures in MakeBlock. This could indicate someone tried to exploit this against your node.CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
CometBFT CometBFT | < 0.37.18 | 0.37.18 |
CometBFT CometBFT | 0.38.0 - 0.38.20 | 0.38.21 |
| Attribute | Detail |
|---|---|
| CWE | CWE-665: Improper Initialization |
| Attack Vector | Network (Consensus Layer) |
| CVSS | 9.0 (Critical) |
| Impact | Consensus Failure / Logic Manipulation |
| Components | state/state.go, types/validation.go |
| Fix Commit | bf8274fcdbcab2bc652660ae627196a90a6efb97 |
The product does not initialize or incorrectly initializes a resource, which may leave the resource in an unexpected state.