> For the complete documentation index, see [llms.txt](https://reports.immunefi.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://reports.immunefi.com/firelight-sep.2026-or-audit-competition/88395-sc-high-claim-state-poisoning-in-blocklist-rescue-permanently-freezes-a-later-withdrawal.md).

# 88395 sc high claim state poisoning in blocklist rescue permanently freezes a later withdrawal

**Submitted on Aug 13th 2026 at 00:59:52 UTC by @jerrysd for** [**Audit Comp | Firelight**](https://immunefi.com/audit-competition/audit-comp-firelight-1)

* **Report ID:** #88395
* **Report Type:** Smart Contract
* **Report severity:** High
* **Target:** <https://github.com/immunefi-team/audit-comp-firelight/blob/v1\\_audit\\_ready/contracts/core/FirelightVault.sol>
* **Impacts:**
  * Permanent freezing of funds

## Description

## Brief/Intro

`rescueWithdrawFromBlocklisted()` permanently marks the source `(period, account)` as claimed. If the account is unblocked before that period rolls over, `redeem()` and `withdraw()` still accept a new request for the same future period and burn the user's shares. At maturity, both the normal claim and the only rescue path reject the newly recorded entitlement with `AlreadyClaimedPeriod`. The accepted principal is permanently frozen in the vault.

## Vulnerability Details

The rescue path moves all current pool shares away from `from` and sets a terminal flag:

```solidity
withdrawSharesOf[periods[i]][to] += _withdrawOf;
withdrawSharesOf[periods[i]][from] = 0;
isWithdrawClaimed[periods[i]][from] = true;
```

However, `_requestWithdraw()` never checks that flag before adding new shares to the same key:

```solidity
uint256 period = currentPeriod() + 1;
withdrawSharesOf[period][receiver] += sharesWithdraw;
pendingWithdrawAssets += assets;
_update(owner, address(0), shares);
```

The claim and rescue exits both reject that new balance:

```solidity
if (isWithdrawClaimed[period][sender]) revert AlreadyClaimedPeriod(period);
// ...
if (isWithdrawClaimed[periods[i]][from]) revert AlreadyClaimedPeriod(periods[i]);
```

## Impact Details

An unblocked user can lose any new principal withdrawn to the poisoned account during the same vault period. The vault burns the shares and retains the corresponding FXRP, but exposes no call that can claim, move, cancel, or reset the entitlement. The demonstrated 60 FXRP is arbitrary and scales to the user's remaining position.

## References

<https://github.com/immunefi-team/audit-comp-firelight/blob/v1\\_audit\\_ready/contracts/core/FirelightVault.sol>

## Link to Proof of Concept

<https://gist.github.com/JRBusiness/dfeb73aeb4042fe4a9beeedd57ecd3da>

## Proof of Concept

## Proof of Concept

Run from the frozen `v1_audit_ready` branch:

```bash
npx hardhat test --no-compile rescuedWithdrawalClaimPoison.js
```

The test deposits 100 FXRP, rescues a 40 FXRP future withdrawal through the documented blocklist flow, unblocks the user, and has the user redeem its remaining 60 FXRP into the same future period. The 40 FXRP rescue recipient claims successfully. The 60 FXRP later request remains recorded and funded, but both recovery paths revert:

```
[claim-poison setup] period=1 firstRescued=40000000 laterAccepted=60000000 sourceClaimed=true
[claim-poison maturity] rescuedClaim=40000000 frozen=60000000 pending=60000000 vaultBalance=60000000
[claim-poison claim] reverted=AlreadyClaimedPeriod
[claim-poison recovery] rescue-retry=reverted AlreadyClaimedPeriod
1 passing
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://reports.immunefi.com/firelight-sep.2026-or-audit-competition/88395-sc-high-claim-state-poisoning-in-blocklist-rescue-permanently-freezes-a-later-withdrawal.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
