Full Report
Flow suffered a major hack of about $3.9M USD. This was not an application but an issue with the blockchain itself. No existing user balances were accessed; the attacker was able to duplicate assets out of thin air. This is the story of vulnerability and how Flow handled it. Cadence is a resource-oriented programming language similar to Move. In many blockchains, such as EVM, tokens are in a ledger (a map of balances), and that's it. In Flow, they are programmable objects that exist in a user's account storage, mimicking a physical dollar. These Resources, mimicking a physical asset, cannot be copied or accidentally discarded; they can only be moved or explicitly destroyed. The resources are great, but rigorous static and dynamic checks are performed to ensure resource integrity. If this could be bypassed, it would be a major problem. In Cadence, Attachments are designed to allow developers to extend a struct or resource type with new functionality. they are values that are just attached to existing resources. When making a transaction, the imported struct and the value types were passed in dynamically. The validation for attachment types was not strict enough, which is the main reason for the issue. Mainly, the fields passed in were not checked against their static declared types, AND the runtime types field information could be specified incorrectly. So, what's the consequence of this? By specifying an attachment with invalid fields, Cadence would believe that a type is a struct when it was actually a Resource during execution. During execution, this allowed the resource to be duplicated repeatedly. By duplicating coins, you could create tokens out of thin air. The verification system, like this, is scary because the consequences of a mistake are so deadly. The authors considered this occurrence and did not perform these checks on some internal types because they should only be creatable by the runtime itself. A small number of these internal types, such as PublicKey used in the exploit, can be created with user code. So, the attacker declared a value as a PublicKey and then encoded a resource-containing structure as a value in its place. This allowed for smuggling a resource inside a struct context. The previous two bugs were nice for copying values. But the resource is a public key type. So, a type-confusion bug was needed to restore the object as a useful resource. On the contract initialization function add(), the static types being used on the call were not validated to match the contract itself. This allowed turning a static public key into a resource. An absolutely crazy chain of three bugs! The vulnerabilities above were fixed with specific checks. They upped the rewards on the bug bounty program and added a regression test for this exploit. To prevent this from happening in the future, several other things were done: Hardened Argument Validation: Prevent rogue fields from being passed in. Extended defensive checks for built-in types: Member access checks are done on all types now. This would have prevented the exploit in the first place. Strict Deployment Semantics: Match between static and dynamic types on the order of contract instantiation. The attacker obtained a small number of each of the 13 tokens. Then, duplicated these small amounts in a sequence 42 times to get much more than the total supply of tokens. The attacker tries to cash out on a centralized exchange but is flagged/stopped due to the massive size. A small number of assets are bridged through Celer, deBridge, and Stargate. So, now what? Are assets gone? Four hours after the exploit took place, the bug was fixed, and validators halted transaction ingestion. Two days later, the counterfeit assets are recovered, destroyed, and the malicious accounts are restricted. Although a lot of tokens were duplicated, the realized damage was about $3.9M via bridged assets prior to the chain halt. How were the tokens recovered? Via a special contract that had Governance authority to delete counterfeit tokens. Funds that were stolen via DEX's were recovered from the attacker-controlled addresses and designated as liquidity pool rebalancing funds. An absolutely crazy exploit and an interesting resource. It's wild to me that Flow could delete resources or change ownership via Governance. Still, it seems like most of the impact was limited by this response so I can respect it.
Analysis Summary
# Incident Report: Flow Blockchain Resource Duplication Exploit
## Executive Summary
On December 27, 2025, the Flow blockchain experienced a major security incident resulting in the counterfeiting of approximately $3.9M USD in bridged assets. An attacker exploited a three-part vulnerability chain within the Cadence runtime to "smuggle" non-copyable Resources into programmable Structs, allowing for the duplication of tokens out of thin air. The Flow Foundation and validators responded by halting the network within six hours, eventually using decentralized governance to destroy the counterfeit assets and restore network integrity without a state rollback.
## Incident Details
- **Discovery Date:** December 27, 2025
- **Incident Date:** December 26, 2025 – December 27, 2025
- **Affected Organization:** Flow Foundation / Flow Network
- **Sector:** Blockchain / Web3 Infrastructure
- **Geography:** Global / Decentralized
## Timeline of Events
### Initial Access
- **Date/Time:** 2025-12-26 23:25 PST (Block 137363398)
- **Vector:** Deployment of malicious smart contracts.
- **Details:** The attacker deployed and configured over 40 malicious smart contracts designed to exploit the Cadence runtime’s type-validation logic.
### Lateral Movement
- **Token Duplication:** At 23:35 PST, the attacker began duplicating 13 different types of tokens. By nesting resources within internal types (like `PublicKey`) that lacked strict runtime checks, the attacker bypassed the "non-copyable" property of Cadence resources.
- **Escalation:** The attacker repeated this duplication sequence 42 times, eventually creating a supply exceeding the legitimate total supply of certain tokens.
### Data Exfiltration/Impact
- **CEX Deposits:** Starting at 23:42 PST, the attacker attempted to move 1.094 billion counterfeit FLOW to centralized exchanges (OKX, Gate.io, MEXC, etc.).
- **Bridging:** Approximately $3.9M USD in value was successfully moved off-chain via bridges including Celer, deBridge, and Stargate before the network halt.
### Detection & Response
- **Network Halt:** 2025-12-27 05:23 PST. Validators executed a coordinated halt (Block 137390190) approx. 6 hours after the start.
- **Recovery:** On 2025-12-29, the network resumed under an "Isolated Recovery Plan." Counterfeit assets were isolated, and a special governance-authorized contract was used to delete the illegitimate tokens.
## Attack Methodology
- **Initial Access:** Smart contract deployment (Unauthorized asset creation).
- **Persistence:** Not applicable (Protocol-level exploit).
- **Privilege Escalation:** Type Confusion. The attacker exploited a lack of strict type checking in "Attachments" and internal types.
- **Defense Evasion:** Smuggling. By declaring a value as a `PublicKey` (a type the runtime assumed only it could create), the attacker hid a "Resource" inside a "Struct" context, which the system then allowed to be copied.
- **Lateral Movement:** Asset duplication across 13 different token types.
- **Exfiltration:** Standard cross-chain bridging and CEX deposits.
- **Impact:** Counterfeiting/Inflation. Creation of assets "out of thin air" rather than stealing existing user balances.
## Impact Assessment
- **Financial:** ~$3.9M USD realized loss via bridged assets; 1.094 Billion FLOW tokens counterfeited (mostly recovered/destroyed).
- **Data Breach:** None. No user private keys or balances were compromised.
- **Operational:** Network downtime of approximately 48 hours; transaction ingestion halted.
- **Reputational:** High. The incident required the use of "centralized" governance powers to delete tokens, though it was supported by the community to maintain economic stability.
## Indicators of Compromise
- **Behavioral indicators:**
- High-frequency calls to `add()` functions with mismatched static/dynamic types.
- Rapid, exponential increases in account balances for multiple tokens within a single transaction block.
- Large-scale deposits of newly minted tokens to CEX-linked addresses shortly after contract deployment.
## Response Actions
- **Containment:** Coordinated validator halt to stop transaction ingestion and bridge exits.
- **Eradication:** Deployment of Cadence runtime v1.8.9 to patch the three-stage exploit chain.
- **Recovery:** Implementation of the "Isolated Recovery Plan" to preserve history while identifying and freezing 98.7% of counterfeit supply.
- **Remediation:** Cooperative agreements with CEXs to return 484M FLOW; governance-led destruction of remaining counterfeit assets.
## Lessons Learned
- **Implicit Trust:** Assuming "internal" types (like `PublicKey`) are safe because they "should" only be created by the runtime is a critical failure point if user code can actually instantiate them.
- **Runtime Rigor:** Resource-oriented safety is only as strong as the type-validation at the boundaries (Initializers and Attachments).
- **Governance Utility:** In extreme "minting" events, the ability for decentralized governance to delete assets is a controversial but effective emergency backstop.
## Recommendations
- **Hardened Argument Validation:** Enforce strict static type verification for all nested fields and attachments.
- **Universal Member Access Checks:** Perform recursive defensive checks on all types, regardless of whether they are "built-in" or user-defined.
- **Strict Deployment Semantics:** Mandate an exact match between static and dynamic types during contract instantiation.
- **Bug Bounty:** Increased rewards for runtime-level vulnerabilities to incentivize white-hat discovery over exploitation.