Security & liveness releases: Bor v2.8.1 / v2.8.2 and Heimdall v0.8.1 / v0.8.2 / v0.9.0
Over the last few weeks we shipped a series of releases across both Polygon PoS
clients — Bor (execution) and Heimdall (consensus). Several of these addressed
security and network-liveness issues. As is standard practice for sensitive
fixes, they were first rolled out privately to protect the network during the
upgrade window, and then made public once the fleet was safe.
This post summarizes what each release fixed, what the impact could have been,
and what node operators should do. None of these issues caused a disruption on
mainnet — they were addressed proactively. We’re sharing the details now that
the fixes are public.
Bor v2.8.1 — peer-to-peer hardening
A maintenance release focused on making the networking layer more robust. The
main security-relevant change:
- **Bounded `GetWitnessMetadata` requests.** A peer could send a single request
listing an unbounded number of block hashes, forcing the receiving node to do
an unlimited amount of work per request. Bor now caps the number of hashes it
will service in one request, matching the limits that already exist for block
headers, bodies, and receipts. This closes a cheap resource-exhaustion vector
against individual nodes.
The release also includes two robustness fixes: state-sync transactions now run
with a well-defined execution context, and empty receipt lists are returned in
their canonical form over the wire, avoiding malformed peer-to-peer responses.
**Impact if unfixed:** a malicious peer could pressure individual nodes with
oversized requests. Not consensus-affecting.
-–
Bor v2.8.2 — prevent oversized blocks
The network enforces a maximum size for blocks relayed between peers. Under
heavy transaction load, a block producer could pack a block that crossed that
limit. Such a block can’t be relayed to peers, so it never gets imported —
which means the producer effectively fails to produce that block.
**The fix:** the producer now tracks the encoded size of the block as it adds
transactions and stops before crossing the limit, so it never seals a block
that can’t propagate.
**Impact if unfixed:** under sustained load, an affected producer could stall
its own block production. This is a liveness issue, not a consensus-correctness
one.
-–
Heimdall v0.8.1 — fee-withdraw validator gate
This release tightens who is allowed to withdraw accumulated fees. Previously,
the fee-withdraw transaction wasn’t restricted to validators. v0.8.1 adds a rule
that only **current validators** can submit a fee-withdraw transaction,
preventing non-validator addresses from interacting with that flow.
Because this changes a transaction-validity rule, it’s activated at a
coordinated hardfork height so the whole validator set adopts it at the same
moment:
- **Mainnet:** block `46,361,000` (2026-06-02, 14:00 UTC)
- **Amoy:** block `35,914,000` (2026-05-28, 14:00 UTC)
**Impact if unfixed:** an authorization gap around fee withdrawals. Closing it
hardens the economic integrity of the fee flow.
-–
Heimdall v0.8.2 — fix proposer stall under load
When a validator’s turn comes to propose a block, it assembles a batch of
transactions and checks that the batch fits within the consensus engine’s size
limit. The proposer was measuring that size differently from how the consensus
engine measures it when it validates the proposal. Under transaction load, a
proposer could build a batch that passed its own check but exceeded the engine’s
limit once fully encoded — causing the proposer to fail to produce its block.
Because this is triggerable by ordinary transaction load (and is cheap to
trigger, since the offending transactions never need to commit), repeated
occurrences could stall block production.
**The fix:** the proposer now measures proposal size using exactly the same unit
the consensus engine uses to validate it, so it can no longer overshoot the
limit.
**Impact if unfixed:** a permissionless way to disrupt block production — a
liveness risk.
-–
Heimdall v0.9.0 — Zurich hardfork (consensus hardening)
v0.9.0 activates the **Zurich hardfork** and is **mandatory for all mainnet
operators** before block `47,880,000` (estimated 2026-06-25, ~14:00 UTC). Nodes
on older versions will fall out of consensus at that height.
Alongside its feature set, Zurich brings a round of consensus hardening. The
security- and liveness-relevant pieces:
- **Deterministic state-sync processing, with a corrected visibility rule.**
Zurich makes the processing of L1→L2 bridge events (state syncs) deterministic
across all validators by tying event visibility to block height instead of
wall-clock time. During development we identified and fixed an edge case in
the visibility logic where a bridge event could be permanently skipped — which
would have frozen all subsequent state syncs (i.e. bridge deposits would stop
being processed). The fix shipped before the hardfork activated, so the issue
never reached production.
- **Archive-replay correctness.** We also fixed a case where a node syncing from
genesis across the upgrade boundary could reconstruct a slightly different
state for pre-upgrade blocks and reject a valid block. Fixed before
activation.
- **Defense-in-depth caps and budgets.** A set of changes that bound how much
work any single proposal, vote, or transaction can demand:
- **Per-block cap on side transactions**, enforced symmetrically by both the
proposer and the validators that verify the proposal. - **Time budgets** on proposal construction and vote-extension construction,
so a node returns partial results rather than overrunning consensus
timeouts. - **Cap on the number of outputs in a single bank transfer**, keeping
per-transaction work bounded under the flat-fee model. - **Stricter checkpoint validation** (the account root hash must be exactly 32
bytes). - **Producer votes restricted to the active validator set.**
- **Determinism hardening** for checkpoint signature aggregation and milestone
selection, so every validator computes identical results.
**Impact if unfixed:** the state-sync visibility bug was a latent liveness risk
to the bridge; the remaining items harden consensus against edge cases and
resource pressure. All were resolved before the hardfork went live.
-–
What to upgrade
| Release | Who needs it | Urgency |
|---|---|---|
| Bor v2.8.1 | All nodes | Recommended |
| Bor v2.8.2 | Block producers / validators | Recommended for producers |
| Heimdall v0.8.1 | All validators | Before the activation height (already passed on mainnet/Amoy) |
| Heimdall v0.8.2 | All nodes | Recommended |
| Heimdall v0.9.0 | All mainnet nodes | **Mandatory before block 47,880,000**block height already passed on mainnet/Amoy succesfully) |
Notes:
- Heimdall v0.9.0’s optional Bor gRPC transport requires Bor **v2.8.3** or newer
on the connected Bor node; the default HTTP transport works with earlier Bor
versions.
- v0.9.0 also changes a configuration field for L1 fees (EIP-1559). Check the
release notes for the required `app.toml` change before restarting.
As always, please run the latest releases and reach out on the validator
channels if you hit anything during the upgrade. Thanks to everyone who
contributed reports and helped validate these fixes.