For the complete documentation index, see llms.txt. This page is also available as Markdown.

89373 sc high delayed previous period claims can leave current cover without collateral

Submitted on Aug 18th 2026 at 20:54:40 UTC by @Paludo0x for Audit Comp | Firelight

  • Report ID: #89373

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/immunefi-team/audit-comp-firelight/blob/v1_audit_ready/contracts/core/CoverOrderAllocator.sol

  • Impacts:

    • Protocol insolvency

Description

Brief/Intro

Firelight can activate cover for a new period while the same collateral remains payable to a claim from the previous period. Capacity counts vault assets recorded at the new period start and the First Loss Buffer balance read at commit. It does not reserve the liability that remains payable from the prior period. A valid delayed claim can consume that collateral after the new cover becomes active. The new buyer has paid premium and holds valid cover, but the protocol can lack funds for a later valid claim.

Vulnerability Details

Firelight permits an incident from the previous period to be reported and paid during the current period. CoverOrderAllocator does not include that continuing liability when it calculates current period capacity.

Step by step vulnerability explanation

  1. IncidentManager::createIncident() accepts a capture timestamp while its period remains inside the payout window:

An event from period P can therefore be reported during period P plus one. The report may arrive after cover for P plus one was committed and settled.

  1. CoverOrderAllocator::_computeAvailableCapacity() values the vault only at the start of the current period:

This value includes collateral that may still pay a period P claim. No value represents prior period cover or its remaining payout exposure.

  1. CoverOrderAllocator::commitAllocation() stores capacity after checking only that calculated maximum:

The check passes even when period P cover can still consume the same collateral. It therefore approves two valid obligations against one balance.

  1. CoverOrderAllocator::_settleCoverOrder() uses the stored commitment, collects premium, and mints the receipt NFT:

Settlement does not reserve previous period liability. Once settlement starts, the commitment cannot be replaced or cancelled.

  1. FirelightVault::payout() allows the old claim during the next period and removes assets from the shared vault:

A valid period P claim can consume the assets that supported capacity for P plus one. Current period cover remains active because payout does not update its stored commitment.

  1. A later claim for P plus one is limited by the remaining assets. IncidentManager::approveCurrentAssessment() closes the incident even when the vault returns zero:

Waiting until period P plus two does not restore the missing collateral. A claim captured during period P plus one remains inside its payout window during P plus two, but still depends on the same depleted shared balance. If approval happens during P plus one, IncidentManager closes the incident even when vault payout is zero and stores no unpaid balance for retry.

No role must submit an invalid loss. Each assessment can stay within its own settled allocation and match its own cover period.

Attack path

  1. A depositor supplies vault collateral before period P. The First Loss Buffer is funded and has granted its normal allowance.

  2. The allocator settles valid cover for period P.

  3. During P, the curator creates a valid order for period P plus one.

  4. Period P plus one starts without a reported incident from P.

  5. The allocator commits the opening capacity and settles the new order for P plus one.

  6. A valid event from P is reported during its allowed payout window.

  7. The approved period P claim consumes the First Loss Buffer and the vault assets.

  8. Only after the new order is settled, another valid event occurs during P plus one.

  9. The second event is approved against the settled order for P plus one.

  10. The protocol pays only the remaining collateral, possibly zero, and closes the incident.

Impact Details

  • Affected asset: contracts/core/CoverOrderAllocator.sol is the primary root. FirelightVault.sol and IncidentManager.sol execute the resulting underpayment.

  • Severity: Critical protocol insolvency. Valid current period cover can remain active after its supporting collateral pays a valid previous period claim.

  • Funds at risk: the current period settled cover left without backing.

  • Who loses: the current buyer pays premium and receives the cover NFT, but the payout receiver can receive less than the approved loss.

  • Recovery: IncidentManager closes the incident after the available payment. The unpaid amount has no normal retry path.

  • Trigger: no exact timestamp, block ordering, invalid assessment, or malicious role action is required.

  • Known issue difference: the public issue says aggregate claims are not capped against total cover sold. Here total losses do not exceed cover sold. Each claim stays within a different settled order and its correct period. Current capacity instead reuses collateral already supporting prior cover.

  • Audit difference: the published Macro audit discusses leverage and settlement availability. It does not describe reuse of collateral across adjacent payout windows.

Reserve the maximum remaining liability from the previous period before calculating capacity for the current period. A conservative implementation can start from prior settled cover:

A complete implementation should reduce this reserve after payouts and release it when the prior payout window closes. The reserve must exist before cover settlement because an old incident may be reported later.

Proof of Concept

PoC Explanation

deployCrossPeriodCapacity() imports the original deployVault fixture. It deploys the real vault, allocator, CoverNFT, and IncidentManager contracts behind their normal proxies. The test uses the developer MockPriceFeed only to keep the vault asset price at one dollar.

The setup deposits 1,000,000 asset units into the vault. It also funds the First Loss Buffer with 100,000 units and grants IncidentManager its normal allowance.

fundAndCreateOrder() creates one valid order for each adjacent period and gives each buyer enough premium approval.

commitAndSettle() uses the production Merkle proof format. It commits capacity, collects premium, mints the real CoverNFT, and returns the settlement block timestamp.

createAndApproveIncident() follows the normal incident flow. It creates, confirms, assesses, submits, and approves one claim within its settled allocation.

crossPeriodCapacity.poc.js::activates current period cover that the vault cannot pay() advances one hour into each period. It settles the current cover before reporting the delayed claim. After that payment, it advances another 60 seconds before recording the current event. The test asserts that this capture timestamp is after settlement and belongs to the current period.

Run:

Actual output:

Was this helpful?