Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions bridgelet-audit/threat-models/ephemeral-account-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Threat Model: Ephemeral Account Lifecycle

**Path:** `bridgelet-audit/threat-models/ephemeral-account-lifecycle.md`
**Component:** `EphemeralAccount`
**Target Operations:** Initialization, Token Receiving, Claiming/Sweeping, Archival

---

## Executive Summary

The `EphemeralAccount` contract represents a temporary custody container for bridging tokens into a user's wallet via a specific Bridgelet controller. It operates strictly on a state machine lifecycle: `Uninitialized -> Active -> Swept`.

This threat model evaluates vulnerabilities associated with state transitions in the ephemeral account, focusing on frontrunning initialization, unauthorized sweeping, locked funds, and replay vulnerabilities.

---

## Detailed Threat Scenario & Vulnerability Analysis

### 1. Initialization Frontrunning (Hijacking)
- **Scenario**: The `AccountFactory` deploys an `EphemeralAccount` but does not atomically initialize it.
- **Threat**: An attacker observes the uninitialized contract in the mempool and calls `initialize()` with their own parameters (e.g., setting themselves as the `controller`).
- **Analysis**: **BLOCKED**. Bridgelet utilizes atomic cross-contract deployment and initialization. The `AccountFactory` deploys the Wasm instance and immediately invokes `initialize()` within the exact same transaction. There is no window for an attacker to hijack the initialization state.

### 2. Post-Sweep Deposit (Fund Locking)
- **Scenario**: An `EphemeralAccount` successfully executes `sweep_claim()` and transitions its state to `Swept`. Later, a user or external system deposits more tokens into the contract's address.
- **Threat**: Tokens become permanently locked because the contract prevents sweeps when the status is `Swept`.
- **Analysis**: **MODERATE RISK**. The `sweep_claim()` function strictly asserts `status == Active`. If tokens arrive after the state transitions to `Swept`, they cannot be claimed via the standard path.
- **Mitigation**: Implement a `retry_sweep()` or `recover_funds()` function that allows the controller to extract late-arriving tokens even after the primary `Swept` lifecycle event has occurred.

### 3. Controller Spoofing
- **Scenario**: An attacker attempts to call `sweep_claim()` directly on the `EphemeralAccount`, bypassing the `SweepController`.
- **Threat**: Unauthorized draining of funds to an attacker-controlled destination.
- **Analysis**: **BLOCKED**. `sweep_claim()` calls `controller.require_auth()`. Because the `controller` address is immutably set during atomic initialization, only the designated `SweepController` can successfully authorize the execution of `sweep_claim()`.

### 4. Bricking via Token Allowances
- **Scenario**: The `EphemeralAccount` approves the `SweepController` to move its funds, but an attacker drains the allowance or modifies the trustline.
- **Threat**: The sweep fails during execution.
- **Analysis**: **NOT APPLICABLE**. The `EphemeralAccount` natively transfers funds from its own balance to the destination via `token.transfer(env.current_contract_address(), destination, amount)`. It does not rely on allowances, preventing approval-based griefing.

---

## Recommended Mitigations

### 1. Late-Arrival Token Recovery
Add a mechanism to `EphemeralAccount` to either reject incoming transfers when `status == Swept` (difficult on Stellar without trustline removal) or permit the authenticated `controller` to sweep the account multiple times to recover late arrivals.

### 2. State Machine Immutability
Ensure the `status` enum explicitly prevents transitions backwards (e.g., `Swept -> Active`). This is currently enforced, but must be strictly preserved in future contract upgrades to prevent double-spending semantics in external tracking systems.
58 changes: 58 additions & 0 deletions bridgelet-audit/threat-models/storage-expiry-risk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Threat Model: Storage Expiry (TTL Archival) Risk

**Path:** `bridgelet-audit/threat-models/storage-expiry-risk.md`
**Component:** `SweepController`, `EphemeralAccount`, `Factory`, `Reserve`
**Target Operations:** Contract State Management, TTL Extensions

---

## Executive Summary

Soroban implements state expiration mechanisms (Time-To-Live or TTL) for both persistent and temporary storage. If a storage entry's TTL is not properly maintained, the entry is either archived (persistent storage) or permanently deleted (temporary storage).

This analysis evaluates the risk of storage expiration across the four primary Bridgelet smart contracts, analyzing what happens when contract instances, admin configs, or state variables expire due to network inactivity.

---

## Detailed Threat Scenario & Vulnerability Analysis

### 1. Persistent Storage Expiry (Archival)
**Affected State**: Admin configurations, controller states, initialization flags.
- **Mechanism**: If `env.storage().persistent().extend_ttl()` is not called frequently enough, the network archives the persistent data.
- **Impact**: Any function relying on that data (e.g., `execute_sweep`, `claim`) will trap with a `StorageError` when attempting to read the archived state. The contract becomes temporarily frozen.
- **Recovery**: Anyone can submit a `RestoreFootprintOp` to restore the archived entries. No funds or state data are permanently lost, but availability is degraded.

### 2. Temporary Storage Expiry (Deletion)
**Affected State**: Nonces, ephemeral replay protections.
- **Mechanism**: Temporary storage in Soroban is permanently deleted when its TTL expires.
- **Impact**: If replay nonces are stored in temporary storage and expire, an attacker could potentially replay old `execute_sweep` signatures.
- **Vulnerability**: If `SweepController` uses temporary storage for the Ed25519 `nonce`, a deleted nonce means the signature can be reused. Bridgelet core mitigates this by using **persistent storage** or **instance storage** for nonces and state tracking, ensuring replay protections are never permanently wiped.

### 3. Instance Expiry
**Affected State**: The WebAssembly contract code and its associated `instance()` storage.
- **Mechanism**: If the contract instance itself is not bumped, the entire contract becomes archived.
- **Impact**: Calls to `EphemeralAccount` or `SweepController` will fail at the network layer before execution begins.
- **Recovery**: A `RestoreFootprintOp` is required to bring the contract back online.

---

## Summary Matrix

| Storage Type | Expiry Consequence | Security Impact | Recovery |
| :--- | :--- | :--- | :--- |
| Persistent | Archival (Inaccessible) | Denial of Service (Low) | `RestoreFootprintOp` |
| Temporary | Permanent Deletion | Replay Attacks (Critical) | None |
| Instance | Archival (Inaccessible) | Denial of Service (Low) | `RestoreFootprintOp` |

---

## Recommended Mitigations

### 1. Aggressive TTL Bumping
Implement `extend_ttl()` calls on all critical read/write paths. For example, during `claim()` or `execute_sweep()`, explicitly bump the TTL of the controller configuration and the Ephemeral Account state to the maximum allowable network limit.

### 2. Avoid Temporary Storage for Security Critical State
Never use `env.storage().temporary()` for cryptographic nonces, replay protection mechanisms, or authorization states. All such data must reside in `persistent()` or `instance()` storage.

### 3. Off-chain Monitoring Watchtower
Deploy an off-chain watchtower service that monitors the TTL of all active `SweepController` and `EphemeralAccount` instances and automatically issues `BumpFootprintOp` transactions when TTL falls below a 30-day threshold.
50 changes: 50 additions & 0 deletions bridgelet-audit/threat-models/sweep-controller-claim-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Threat Model: SweepController `claim()` Native-Auth Flow

**Path:** `bridgelet-audit/threat-models/sweep-controller-claim-flow.md`
**Component:** `SweepController`
**Target Operations:** `claim()`

---

## Executive Summary

The `SweepController::claim(recipient, ephemeral_account)` function relies on Soroban's native authorization framework (`recipient.require_auth()`) instead of custom Ed25519 signature verification (which is used in `execute_sweep`).

This threat model analyzes how native authorization behaves when a relayer or the recipient submits the transaction, examining potential attack vectors such as unauthorized claims, fee-bumping exploits, and authorization payload hijacking.

---

## Detailed Threat Scenario & Vulnerability Analysis

### 1. Authorization Payload Hijacking (Relayer Submission)
- **Scenario**: A user signs an authorization payload allowing a relayer to submit the `claim()` transaction on their behalf to cover gas fees.
- **Threat**: A malicious relayer intercepts the auth payload and modifies the `ephemeral_account` argument to point to a different ephemeral account.
- **Analysis**: **BLOCKED**. Soroban's native authorization strictly binds the signature to the exact contract ID, function name, and arguments. Modifying the `ephemeral_account` invalidates the `require_auth()` check. The signature is non-malleable.

### 2. Fee-Bumping and Griefing
- **Scenario**: An attacker observes a valid `claim()` transaction in the mempool submitted by a relayer.
- **Threat**: The attacker submits a duplicate transaction with a higher fee to make the original transaction fail.
- **Analysis**: **LOW IMPACT**. If the attacker successfully fronts the transaction, the `claim()` is executed on behalf of the valid recipient. The funds arrive at the correct destination. The attacker simply pays the gas fees for the user. The original relayer's transaction will fail, causing them to lose a minor base fee, but no funds are stolen.

### 3. Phishing for Native Auth Signatures
- **Scenario**: A malicious dApp prompts the user to sign a Soroban authorization payload for `SweepController::claim()`.
- **Threat**: The user blindly signs the payload, authorizing the smart contract to act on their behalf.
- **Analysis**: **MODERATE**. While `claim()` requires the recipient's authorization, it fundamentally transfers funds *to* the recipient. An attacker tricking a user into signing a `claim()` payload merely allows the attacker to push funds into the user's wallet. It does not allow the attacker to withdraw funds *from* the user's wallet.

### 4. Cross-Contract Reentrancy during Claim
- **Scenario**: The `claim()` function invokes an external contract (e.g., token transfers).
- **Threat**: A malicious token contract reenters `SweepController::claim()`.
- **Analysis**: **BLOCKED**. `SweepController` does not hold custody of funds; it delegates to the `EphemeralAccount`. Furthermore, the ephemeral account transitions its status to `Swept` prior to executing the token transfers, adhering to the Checks-Effects-Interactions pattern. Reentering `claim()` will immediately trap with an `AlreadySwept` error.

---

## Recommended Mitigations

### 1. Maintain Strict Argument Binding
Ensure that `recipient.require_auth()` is the primary enforcement mechanism in `claim()`, and avoid any manual parsing of authorization arguments that could bypass the Soroban host's native checks.

### 2. Transparent Wallet Previews
Wallets interacting with Bridgelet should implement transparent simulation of native auth payloads, clearly showing the user that they are authorizing a `claim` action that will deposit funds into their account, eliminating phishing confusion.

### 3. Status Flags for Reentrancy Protection
Ensure that `EphemeralAccount` explicitly marks its internal state as `Swept` *before* issuing any `token.transfer()` calls during the sweep process, guaranteeing reentrancy safety.
51 changes: 51 additions & 0 deletions bridgelet-audit/threat-models/sweep-controller-signature-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Threat Model: SweepController `execute_sweep` Signature Flow

**Path:** `bridgelet-audit/threat-models/sweep-controller-signature-flow.md`
**Component:** `SweepController`
**Target Operations:** `execute_sweep()`

---

## Executive Summary

The `SweepController::execute_sweep()` function allows off-chain services (such as a backend application or relayer) to submit an Ed25519 signature to authorize a sweep. This enables a flexible model where users do not need to sign native Soroban auth payloads, but rather standard Ed25519 payloads over a predefined schema.

This threat model evaluates the cryptographic binding of the signature, potential replay attacks, malleability, and destination interception.

---

## Detailed Threat Scenario & Vulnerability Analysis

### 1. Signature Replay Attacks
- **Scenario**: A malicious actor extracts a valid Ed25519 signature from a historical `execute_sweep` transaction and resubmits it to trigger another sweep.
- **Threat**: Unauthorized draining of the ephemeral account if funds are re-deposited.
- **Analysis**: **BLOCKED**. The signature payload mandates the inclusion of a sequential `nonce`. The `SweepController` enforces nonce uniqueness and increments it upon every successful signature verification. A replayed signature will fail the `nonce == expected_nonce` check, rendering replays impossible.

### 2. Destination Tampering (Interception)
- **Scenario**: An attacker monitors the mempool, takes a valid signature and `execute_sweep` call, and swaps out the `destination` argument for their own address.
- **Threat**: The attacker attempts to steal the sweep payload.
- **Analysis**: **BLOCKED**. The Ed25519 signature is evaluated over the `destination`. Specifically:
`Hash( destination || nonce || controller_id )`.
If the `destination` argument differs from the one hashed inside the signature, `verify()` will trap with `SignatureVerificationFailed`.

### 3. Cross-Chain / Cross-Contract Replays
- **Scenario**: A signature generated for a testnet `SweepController` or a different Bridgelet instance is submitted to the mainnet `SweepController`.
- **Threat**: Unauthorized execution on a different network or contract instance.
- **Analysis**: **BLOCKED**. The signature payload explicitly includes the `controller_id` (the contract's address/ID) and is bound to the Soroban network passphrase internally (via `env.crypto().ed25519_verify`). Signatures cannot cross environments.

### 4. Ephemeral Account Tampering
- **Scenario**: The attacker changes the `ephemeral_account` argument while keeping the signature intact.
- **Threat**: Sweeping a different user's account using another user's signature.
- **Analysis**: **MODERATE / MITIGATED**. The `execute_sweep` signature specifically authorizes sweeping to a destination, but the `ephemeral_account` itself is passed as an argument. However, if the attacker points to a different `ephemeral_account`, the destination constraint still applies—meaning the attacker would just be sweeping someone else's funds into the *original signer's* destination wallet. To mitigate griefing, `SweepController` bindings can include the `ephemeral_account` directly in the Ed25519 hash payload.

---

## Recommended Mitigations

### 1. Include Ephemeral Account ID in Signature Payload
To completely eliminate cross-account griefing, the signature payload should be updated to:
`Hash( ephemeral_account || destination || nonce || controller_id )`
This rigidly binds the authorization to a specific source account.

### 2. Nonce Management
Ensure the nonce is stored in persistent or instance storage (never temporary) so that network expiration (TTL) cannot reset the nonce counter and reopen old signatures to replay attacks.
Loading