Skip to content

Commit b279383

Browse files
committed
fix: identity signer leak
1 parent 9b46926 commit b279383

2 files changed

Lines changed: 29 additions & 8 deletions

File tree

rust/main/chains/hyperlane-sealevel/src/mailbox.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,11 @@ impl SealevelMailbox {
342342
let account_metas = self.get_account_metas(instruction).await?;
343343

344344
// Ensure dynamically provided account metas are safe to prevent theft from the payer.
345-
// The identity signer (if configured and distinct from payer) is allowed through as a
346-
// trusted co-signer (e.g. required by TrustedRelayer ISMs).
347-
let identity = self.get_signer_if_separate().map(|s| s.pubkey());
348-
sanitize_dynamic_accounts(
349-
account_metas,
350-
&self.get_payer()?.pubkey(),
351-
identity.as_ref(),
352-
)
345+
// Identity is NOT passed here: neither the ISM-getter nor handle paths should trust
346+
// an external program to request the relayer's identity as a signer. Only the ISM
347+
// verify fixpoint loop (get_ism_verify_account_metas) passes identity, after it has
348+
// already converged on a stable account set from a known ISM program.
349+
sanitize_dynamic_accounts(account_metas, &self.get_payer()?.pubkey(), None)
353350
}
354351

355352
async fn get_process_payload(

rust/main/chains/hyperlane-sealevel/src/utils.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,28 @@ mod test {
129129
assert!(!result[0].is_signer);
130130
assert!(result[1].is_signer);
131131
}
132+
133+
/// Regression test: ISM-getter and handle paths pass `None` as identity, so a malicious
134+
/// recipient program cannot trick the relayer into co-signing with its identity keypair
135+
/// by returning it with `is_signer: true` in the account metas simulation.
136+
#[test]
137+
fn test_sanitize_dynamic_accounts_strips_identity_signer_when_none() {
138+
use solana_sdk::instruction::AccountMeta;
139+
140+
let payer = Pubkey::new_unique();
141+
let identity = Pubkey::new_unique();
142+
143+
// Simulate a malicious recipient returning the relayer's identity as a signer.
144+
let account_metas = vec![
145+
AccountMeta::new_readonly([0u8; 32].into(), false),
146+
AccountMeta::new_readonly(identity, true),
147+
];
148+
149+
// `None` identity — as used by get_non_signer_account_metas_with_instruction_bytes.
150+
let result = sanitize_dynamic_accounts(account_metas, &payer, None).unwrap();
151+
152+
// Both accounts must have is_signer stripped, including the identity key.
153+
assert!(!result[0].is_signer);
154+
assert!(!result[1].is_signer);
155+
}
132156
}

0 commit comments

Comments
 (0)