Skip to content

Commit ef54525

Browse files
authored
Merge branch 'main' into atlas-guide-update
2 parents 5410e69 + f4e2fb7 commit ef54525

18 files changed

Lines changed: 1893 additions & 79 deletions

public/changelog.json

Lines changed: 1126 additions & 0 deletions
Large diffs are not rendered by default.
162 KB
Loading
177 KB
Loading

src/content/ccip/concepts/architecture/onchain/aptos/components.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,9 @@ The `ccip::receiver_registry` is a module where developers can register their cu
175175
## RMN (Risk Management Network) Remote
176176

177177
The `ccip::rmn_remote` module is a critical security component deployed on every CCIP-enabled chain. Various CCIP modules (like `OnRamp`, `OffRamp`, `Token Pools`) query this component to verify the status of other chains in the network. It maintains an onchain list of "cursed" (i.e., blocklisted) chains. If a source or destination chain is cursed, CCIP transactions involving that chain are halted.
178+
179+
<Aside type="note">
180+
Based on user demand, CCIP's architecture is evolving to provide enhanced support for modular security and
181+
configurable compliance capabilities. As part of this transition, the Risk Management Network is being adapted to
182+
align with this broader, more flexible architecture.
183+
</Aside>

src/content/ccip/concepts/architecture/onchain/evm/components.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,9 @@ The RMN Contract is deployed on every chain where CCIP is integrated. Its key fu
180180
1. **Cursing Mechanism**
181181
- **Curse Initiation**: When the CCIP Owner manually initiates a curse, the `curse()` function is invoked to mark the appropriate subjects as cursed.
182182
- **Curse Detection**: Onchain components (such as the Router, OnRamp, OffRamp, and TokenPool) call the `isCursed()` function on the RMNRemote contract to detect global curses or curses targeting a remote chain.
183+
184+
<Aside type="note">
185+
Based on user demand, CCIP's architecture is evolving to provide enhanced support for modular security and
186+
configurable compliance capabilities. As part of this transition, the Risk Management Network is being adapted to
187+
align with this broader, more flexible architecture.
188+
</Aside>

src/content/ccip/concepts/architecture/onchain/svm/components.mdx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,8 @@ The Risk Management Network (RMN) adds an additional security layer to CCIP by m
201201
- Once cursed, any CCIP transaction that performs the verification check for that chain selector (or the global curse) will revert, preventing cross-chain message processing.
202202
- The owner calls `uncurse` to lift the curse when the threat is resolved, restoring normal CCIP operations.
203203

204-
<Aside type="caution" title="Phased Deployments">
205-
Certain blockchains may be integrated with CCIP in a phased approach, with the Committing and Executing DONs deployed
206-
first and the Risk Management Network added in a later update. In cases of a phased deployment, merkle roots in
207-
relevant Commit Stores are always considered blessed until the Risk Management Network has been deployed for that
208-
blockchain. Please see the [CCIP Directory](/ccip/directory) for which integrations utilize a phased approach. Please
209-
review [CCIP Service Responsibility](/ccip/service-responsibility) for more information.
204+
<Aside type="note">
205+
Based on user demand, CCIP's architecture is evolving to provide enhanced support for modular security and
206+
configurable compliance capabilities. As part of this transition, the Risk Management Network is being adapted to
207+
align with this broader, more flexible architecture.
210208
</Aside>

src/content/ccip/llms-full.txt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,12 @@ The RMN Contract is deployed on every chain where CCIP is integrated. Its key fu
15271527
- **Curse Initiation**: When the CCIP Owner manually initiates a curse, the `curse()` function is invoked to mark the appropriate subjects as cursed.
15281528
- **Curse Detection**: Onchain components (such as the Router, OnRamp, OffRamp, and TokenPool) call the `isCursed()` function on the RMNRemote contract to detect global curses or curses targeting a remote chain.
15291529

1530+
<Aside type="note">
1531+
Based on user demand, CCIP's architecture is evolving to provide enhanced support for modular security and
1532+
configurable compliance capabilities. As part of this transition, the Risk Management Network is being adapted to
1533+
align with this broader, more flexible architecture.
1534+
</Aside>
1535+
15301536
---
15311537

15321538
# Onchain Architecture - Upgradability (EVM)
@@ -1915,12 +1921,10 @@ The Risk Management Network (RMN) adds an additional security layer to CCIP by m
19151921
- Once cursed, any CCIP transaction that performs the verification check for that chain selector (or the global curse) will revert, preventing cross-chain message processing.
19161922
- The owner calls `uncurse` to lift the curse when the threat is resolved, restoring normal CCIP operations.
19171923

1918-
<Aside type="caution" title="Phased Deployments">
1919-
Certain blockchains may be integrated with CCIP in a phased approach, with the Committing and Executing DONs deployed
1920-
first and the Risk Management Network added in a later update. In cases of a phased deployment, merkle roots in
1921-
relevant Commit Stores are always considered blessed until the Risk Management Network has been deployed for that
1922-
blockchain. Please see the [CCIP Directory](/ccip/directory) for which integrations utilize a phased approach. Please
1923-
review [CCIP Service Responsibility](/ccip/service-responsibility) for more information.
1924+
<Aside type="note">
1925+
Based on user demand, CCIP's architecture is evolving to provide enhanced support for modular security and
1926+
configurable compliance capabilities. As part of this transition, the Risk Management Network is being adapted to
1927+
align with this broader, more flexible architecture.
19241928
</Aside>
19251929

19261930
---
@@ -2249,6 +2253,12 @@ The `ccip::receiver_registry` is a module where developers can register their cu
22492253

22502254
The `ccip::rmn_remote` module is a critical security component deployed on every CCIP-enabled chain. Various CCIP modules (like `OnRamp`, `OffRamp`, `Token Pools`) query this component to verify the status of other chains in the network. It maintains an onchain list of "cursed" (i.e., blocklisted) chains. If a source or destination chain is cursed, CCIP transactions involving that chain are halted.
22512255

2256+
<Aside type="note">
2257+
Based on user demand, CCIP's architecture is evolving to provide enhanced support for modular security and
2258+
configurable compliance capabilities. As part of this transition, the Risk Management Network is being adapted to
2259+
align with this broader, more flexible architecture.
2260+
</Aside>
2261+
22522262
---
22532263

22542264
# Onchain Architecture - Upgradability (Aptos)

src/content/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts.mdx

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -525,13 +525,170 @@ The deployment and configuration process involves these steps:
525525
While the `setForwarderAddress()` function allows updating to `address(0)`, this disables the critical security check and allows **anyone** to call your `onReport()` function with arbitrary data. The function emits a `SecurityWarning` event if you attempt this. Only use `address(0)` for testing if you fully understand the implications.
526526
</Aside>
527527

528-
### Replay protection
528+
### Replay attacks
529529

530-
The `KeystoneForwarder` contract includes built-in replay protection that prevents successful reports from being executed multiple times. By requiring the forwarder address at construction time, `ReceiverTemplate` ensures your consumer benefits from this protection automatically.
530+
CRE reports carry DON signatures that any compatible `KeystoneForwarder` will accept. This creates two distinct replay vectors that workflow authors must explicitly protect against by embedding protective metadata in their report payloads and verifying it in their consumer contracts.
531+
532+
#### Cross-chain replay
533+
534+
**The risk**: While publishing a single signed report to multiple chains simultaneously enables patterns like [Proof of Reserve (PoR)](/data-feeds/smartdata#proof-of-reserve-feeds) or feed-style publish-once-post-many, it also means **anyone holding a valid report can replay it on any chain that recognizes the DON's signing keys**.
535+
536+
The forwarder validates cryptographic signatures but those signatures do not commit to a specific chain—without additional protection in your consumer contract, a replayed report can land on an unintended chain.
537+
538+
<ClickToZoom
539+
src="/images/cre/crosschain-replay-attack-vector-diagram.png"
540+
alt="Cross-chain replay attack vector diagram"
541+
/>
542+
543+
**The mitigation**: Embed the target chain selector in the report payload. The consumer contract decodes this value and rejects reports not intended for the current chain. Chain selectors are `uint64` identifiers used throughout the CRE platform to identify blockchain networks — see [Chain Selectors](/cre/reference/sdk/evm-client-ts#chain-selectors) for the full list of constants and the `ChainSelectorFromName` helper.
544+
545+
**Workflow (embed chain selector in the report payload):**
546+
547+
```go
548+
// Define your report struct with a ChainSelector field.
549+
// ChainSelector is a uint64 — the same type used when instantiating evm.Client.
550+
type PaymentReport struct {
551+
Recipient common.Address
552+
Amount *big.Int
553+
ChainSelector uint64 // Target chain — used by the consumer to reject cross-chain replays
554+
}
555+
556+
paymentReport := PaymentReport{
557+
Recipient: common.HexToAddress(config.Recipient),
558+
Amount: big.NewInt(100_000_000), // e.g., 100 USDC (6 decimals)
559+
ChainSelector: config.ChainSelector, // e.g., 16015286601757825753 for Ethereum Sepolia
560+
}
561+
562+
// ABI-encode paymentReport and pass to runtime.GenerateReport() as normal
563+
```
564+
565+
**Consumer contract (verify the embedded chain selector):**
566+
567+
```solidity
568+
// SPDX-License-Identifier: MIT
569+
pragma solidity ^0.8.26;
570+
571+
import { ReceiverTemplate } from "./ReceiverTemplate.sol";
572+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
573+
574+
contract ChainRestrictedConsumer is ReceiverTemplate {
575+
IERC20 public immutable i_token;
576+
uint64 public immutable i_expectedChainSelector;
577+
578+
error UnexpectedChainSelector(uint64 received, uint64 expected);
579+
580+
constructor(
581+
address _forwarderAddress,
582+
address _token,
583+
uint64 _expectedChainSelector
584+
) ReceiverTemplate(_forwarderAddress) {
585+
i_token = IERC20(_token);
586+
i_expectedChainSelector = _expectedChainSelector;
587+
}
588+
589+
function _processReport(bytes calldata report) internal override {
590+
(address recipient, uint256 amount, uint64 chainSelector) = abi.decode(
591+
report,
592+
(address, uint256, uint64)
593+
);
594+
595+
if (chainSelector != i_expectedChainSelector) {
596+
revert UnexpectedChainSelector(chainSelector, i_expectedChainSelector);
597+
}
598+
599+
i_token.transfer(recipient, amount);
600+
}
601+
}
602+
```
603+
604+
#### Same-chain replay on failure
605+
606+
**The risk**: While allowing failed deliveries to be retried without requiring a new signed report enables permissionless recovery from transient failures, it also means the forwarder does not mark reverted transmissions as used.
607+
608+
**A malicious actor can exploit this window**: after your workflow has already reacted to a failure (for example, scheduled a corrective action), an attacker can replay the original signed report once conditions recover, causing double-execution.
609+
610+
<ClickToZoom src="/images/cre/same-chain-replay-on-failure.png" alt="Same-chain replay on failure diagram" />
611+
612+
{/* prettier-ignore */}
613+
<Aside type="caution" title="Attack scenario: double payment">
614+
1. A cron workflow attempts to pay a wallet $100 USDC. The consumer contract **reverts** (insufficient funds).
615+
1. CRE returns a reverted transaction hash. The workflow records the failure and plans to correct the balance on the next run.
616+
1. Funds are replenished — by the owner, another workflow, or a user deposit.
617+
1. An attacker (or a bot) replays the original signed report. The consumer now has funds and the payment **executes again** — the recipient is paid twice.
618+
</Aside>
619+
620+
**The mitigation**: Embed the scheduled execution timestamp in the report payload. The consumer contract stores the last accepted timestamp and rejects any report with a timestamp equal to or earlier than the stored value. Once a later execution has been accepted, earlier failed reports can never land.
621+
622+
{/* prettier-ignore */}
623+
<Aside type="note" title="Use the trigger's scheduled time, not wall-clock time">
624+
The timestamp must be deterministic across all DON nodes so they agree during consensus. Use the cron trigger's scheduled execution slot time rather than `time.Now()`. Refer to the [cron trigger reference](/cre/reference/sdk/triggers/cron-trigger-go) for the exact field name on the trigger payload.
625+
</Aside>
626+
627+
**Workflow (embed scheduled execution timestamp in the report payload):**
628+
629+
```go
630+
// Use the trigger's scheduled slot time — deterministic across all DON nodes.
631+
// Refer to the cron trigger reference for the exact field name on cron.Payload.
632+
scheduledAt := trigger.ScheduledAt.Unix()
633+
634+
// Define your report struct with a ScheduledAt field
635+
type PaymentReport struct {
636+
Recipient common.Address
637+
Amount *big.Int
638+
ScheduledAt *big.Int // Monotonic execution timestamp — used to reject stale replays
639+
}
640+
641+
paymentReport := PaymentReport{
642+
Recipient: common.HexToAddress(config.Recipient),
643+
Amount: big.NewInt(100_000_000), // e.g., 100 USDC (6 decimals)
644+
ScheduledAt: big.NewInt(scheduledAt),
645+
}
646+
647+
// ABI-encode paymentReport and pass to runtime.GenerateReport() as normal
648+
```
649+
650+
**Consumer contract (reject reports from earlier executions):**
651+
652+
```solidity
653+
// SPDX-License-Identifier: MIT
654+
pragma solidity ^0.8.26;
655+
656+
import { ReceiverTemplate } from "./ReceiverTemplate.sol";
657+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
658+
659+
contract ScheduledPaymentConsumer is ReceiverTemplate {
660+
IERC20 public immutable i_token;
661+
uint256 public s_lastAcceptedTimestamp;
662+
663+
error ReportTooOld(uint256 reportTimestamp, uint256 lastAccepted);
664+
665+
event PaymentProcessed(address indexed recipient, uint256 amount, uint256 scheduledAt);
666+
667+
constructor(address _forwarderAddress, address _token) ReceiverTemplate(_forwarderAddress) {
668+
i_token = IERC20(_token);
669+
}
670+
671+
function _processReport(bytes calldata report) internal override {
672+
(address recipient, uint256 amount, uint256 scheduledAt) = abi.decode(
673+
report,
674+
(address, uint256, uint256)
675+
);
676+
677+
if (scheduledAt <= s_lastAcceptedTimestamp) {
678+
revert ReportTooOld(scheduledAt, s_lastAcceptedTimestamp);
679+
}
680+
681+
s_lastAcceptedTimestamp = scheduledAt;
682+
683+
i_token.transfer(recipient, amount);
684+
emit PaymentProcessed(recipient, amount, scheduledAt);
685+
}
686+
}
687+
```
531688

532689
{/* prettier-ignore */}
533-
<Aside type="note" title="Failed reports can be retried">
534-
If a report fails (reverts), the forwarder's replay protection allows it to be retried. This is safe because reverts undo all state changes, ensuring no duplicate effects occur in your contract.
690+
<Aside type="tip" title="Combining both protections">
691+
For maximum safety, embed both `chainSelector` and `scheduledAt` in a single report struct. This protects against cross-chain and same-chain replay simultaneously with one encoding step.
535692
</Aside>
536693

537694
### Additional validation layers
@@ -562,6 +719,10 @@ The forwarder address provides baseline security, but you can add additional val
562719
- **Single workflow**: Use `setExpectedWorkflowId()` to restrict to one specific workflow (highest security)
563720
- **Multiple workflows from same owner**: Use `setExpectedAuthor()` to restrict to workflows you own
564721
- **Multiple workflows from different owners**: Implement custom validation logic in your `onReport()` override
722+
1. **Protect against replay attacks** - For any workflow that performs state-changing actions (payments, minting, position updates):
723+
- Embed the **target chain selector** in the report payload and verify it in `_processReport` to prevent cross-chain replay
724+
- Embed a **monotonic execution timestamp** from the cron trigger and reject reports with a timestamp ≤ the last accepted value to prevent same-chain replay on failure
725+
- See [Replay attacks](#replay-attacks) for complete code examples
565726
1. **Keep your owner key secure** - The owner can update all permission settings
566727
1. **Test permission configurations** - Verify your security settings work as expected before production deployment
567728
1. **Workflow name validation** - Can be used with `setExpectedWorkflowName()` but requires `setExpectedAuthor()` to also be configured for security

src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ This report is designed to be passed directly to either:
126126
- `evm.Client.WriteReport()` for onchain delivery
127127
- `http.Client` for offchain delivery
128128

129+
{/* prettier-ignore */}
130+
<Aside type="caution" title="Protect against replay attacks before submitting">
131+
If your workflow performs state-changing actions (payments, minting, position updates), embed protective metadata in the payload you pass to `runtime.GenerateReport()`:
132+
133+
- **Chain selector**: Include the target chain selector so the consumer contract can reject reports replayed on a different chain.
134+
- **Execution timestamp**: Include the cron trigger's scheduled slot time so the consumer can reject stale reports that were previously reverted and are being replayed by an attacker.
135+
136+
See [Replay attacks](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts#replay-attacks) in the Building Consumer Contracts guide for full code examples.
137+
138+
</Aside>
139+
129140
### 4. Submit the report
130141

131142
Now that you have a generated report, choose where to send it:

src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-structs.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ This report is designed to be passed directly to either:
122122
- `evm.Client.WriteReport()` for onchain delivery
123123
- `http.Client` for offchain delivery
124124

125+
{/* prettier-ignore */}
126+
<Aside type="caution" title="Protect against replay attacks before submitting">
127+
If your workflow performs state-changing actions (payments, minting, position updates), add protective fields to your struct before encoding:
128+
129+
- **Chain selector**: Include the target chain selector so the consumer contract can reject reports replayed on a different chain.
130+
- **Execution timestamp**: Include the cron trigger's scheduled slot time so the consumer can reject stale reports that were previously reverted and are being replayed by an attacker.
131+
132+
See [Replay attacks](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts#replay-attacks) in the Building Consumer Contracts guide for full code examples.
133+
134+
</Aside>
135+
125136
The report can now be [submitted onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) or [sent via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http).
126137

127138
## Manual encoding

0 commit comments

Comments
 (0)