GitHub Security Advisory: GHSA-725g-w329-g7qr
Package: kora-lib (RUST)
Severity: MODERATE (CVSS 0.0)
Vulnerable versions: < 2.0.5
Patched in: 2.0.5

Summary: kora-lib: Token-2022 Transfer Fee Not Deducted During Payment Verification

Details:
## Summary

When a user pays transaction fees using a Token-2022 token with a `TransferFeeConfig` extension, Kora's `verify_token_payment()` credits the full raw transfer `amount` as the payment value. However, the on-chain SPL Token-2022 program withholds a portion of that amount as a transfer fee, so the paymaster's destination account only receives `amount - transfer_fee`. This means the paymaster consistently credits more value than it actually receives, resulting in systematic financial loss.

## Severity

**High**

## Affected Component

- **File:** `crates/lib/src/token/token.rs`
- **Function:** `verify_token_payment()`
- **Lines:** 529–654 (specifically 633–639)

## Root Cause

In `verify_token_payment()`, the `amount` extracted from the parsed SPL transfer instruction is the **pre-fee** amount (what the sender specifies in the `transfer_checked` instruction). The function passes this raw amount to `calculate_token_value_in_lamports()` to determine how many lamports the payment is worth. It never subtracts the Token-2022 transfer fee.

The fee estimation path (`fee.rs:analyze_payment_instructions`) correctly accounts for transfer fees by calculating them and adding them to the total fee. But the verification path does not perform the inverse subtraction, creating an asymmetry.

## Vulnerable Code

```rust
// crates/lib/src/token/token.rs:529-654
pub async fn verify_token_payment(
    transaction_resolved: &mut VersionedTransactionResolved,
    rpc_client: &RpcClient,
    required_lamports: u64,
    expected_destination_owner: &Pubkey,
) -> Result<bool, KoraError> {
    let config = get_config()?;
    let mut total_lamport_value = 0u64;

    // ...

    for instruction in transaction_resolved
        .get_or_parse_spl_instructions()?
        .get(&ParsedSPLInstructionType::SplTokenTransfer)
        .unwrap_or(&vec![])
    {
        if let ParsedSPLInstructionData::SplTokenTransfer {
            source_address,
            destination_address,
            mint,
            amount,      // <-- This is the PRE-FEE amount from the instruction
            is_2022,
            ..
        } = instruction
        {
            // ... destination validation ...

            // LINE 633-639: Uses raw *amount without deducting transfer fee
            let lamport_value = TokenUtil::calculate_token_value_in_lamports(
                *amount,        // <-- BUG: Should be (amount - transfer_fee)
                &token_mint,
                config.validation.price_source.clone(),
                rpc_client,
            )
            .await?;

            total_lamport_value = total_lamport_value
                .checked_add(lamport_value)
                .ok_or_else(|| {
                    KoraError::ValidationError("Payment accumulation overflow".to_string())
                })?;
        }
    }

    Ok(total_lamport_value >= required_lamports)
}
```

For comparison, the transfer fee calculation exists elsewhere in the codebase and is used during fee e