{"record":{"id":"b1c8edda363e3b53","repo":"linera-io/linera-protocol","slug":"unauthenticatedclaimowner","errorCode":"UnauthenticatedClaimOwner","errorMessage":"ExecutionError::UnauthenticatedClaimOwner","messagePattern":"ExecutionError::UnauthenticatedClaimOwner","errorType":"exception","errorClass":"ExecutionError","httpStatus":null,"severity":"error","filePath":"linera-execution/src/system.rs","lineNumber":769,"sourceCode":"        ensure!(\n            amount > Amount::ZERO,\n            ExecutionError::IncorrectTransferAmount\n        );\n        self.debit(&source, amount).await?;\n        self.credit_or_send_message(source, recipient, amount).await\n    }\n\n    /// Claims `amount` from `source`'s account on `target_id` and transfers it to `recipient`.\n    pub async fn claim(\n        &mut self,\n        authenticated_owner: Option<AccountOwner>,\n        authenticated_application_id: Option<ApplicationId>,\n        source: AccountOwner,\n        target_id: ChainId,\n        recipient: Account,\n        amount: Amount,\n    ) -> Result<Option<OutgoingMessage>, ExecutionError> {\n        ensure!(\n            authenticated_owner == Some(source)\n                || authenticated_application_id.map(AccountOwner::from) == Some(source),\n            ExecutionError::UnauthenticatedClaimOwner\n        );\n        ensure!(amount > Amount::ZERO, ExecutionError::IncorrectClaimAmount);\n\n        let current_chain_id = self.context().extra().chain_id();\n        if target_id == current_chain_id {\n            // Handle same-chain claim locally by processing the withdraw operation directly\n            self.debit(&source, amount).await?;\n            self.credit_or_send_message(source, recipient, amount).await\n        } else {\n            // Handle cross-chain claim with Withdraw message\n            let message = SystemMessage::Withdraw {\n                amount,\n                owner: source,\n                recipient,\n            };","sourceCodeStart":751,"sourceCodeEnd":787,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-execution/src/system.rs#L751-L787","documentation":"Claim withdraws tokens from an owner's account (possibly on another chain) and forwards them to a recipient. The claimed source owner must be authenticated: either the transaction signer equals source, or the calling application's ID equals source (applications may claim their own funds). This error means neither held, so the claim was rejected before any funds moved or any Withdraw message was created.","triggerScenarios":"Submitting SystemOperation::Claim { owner, target_id, recipient, amount } where owner is neither the block's authenticated signer nor the calling application; an application calling runtime.claim(...) over a user's remote account without that user's signature on the operation.","commonSituations":"Reclaiming funds from another chain with the wrong wallet or key; applications attempting to sweep user balances without user authorization; mixing up the owner and recipient parameters when constructing the Claim operation.","solutions":["Sign the claim operation with the key of the owner whose funds are being claimed.","If an application performs the claim, set source to the application's own account (its ApplicationId mapped to AccountOwner) rather than a user account.","Verify the owner field in the Claim matches the authenticated signer of the block before submitting."],"exampleFix":"// before: claim submitted with neither signer nor app matching the claimed owner\nlet op = SystemOperation::Claim { owner, target_id, recipient, amount };\nclient.submit(op).await?; // UnauthenticatedClaimOwner\n\n// after: sign the block with the claimed owner's key first\nlet block = client.prepare_block(op).sign(&owner_key).send().await?;","handlingStrategy":"validation","validationCode":"// The claimed owner must be authenticated before submitting a Claim\nensure!(\n    authenticated_owner == Some(source)\n        || application_account == Some(source),\n    \"claim must be signed by (or originate from) the claimed owner\"\n);","typeGuard":"fn is_unauthenticated_claim(e: &ExecutionError) -> bool {\n    matches!(e, ExecutionError::UnauthenticatedClaimOwner)\n}","tryCatchPattern":"match result {\n    Err(ExecutionError::UnauthenticatedClaimOwner) => {\n        // Signer/app does not match the claimed owner: re-sign as that\n        // owner or claim from the application's own account.\n    }\n    Err(e) => return Err(e.into()),\n    Ok(value) => { /* ... */ }\n}","preventionTips":["Sign claim operations with the key of the owner whose funds are claimed.","In applications, restrict runtime.claim to the app's own account unless the user signed the operation.","Distinguish the owner (funds being claimed) from the recipient when building the Claim."],"tags":["linera","claim","authentication","authorization","cross-chain"],"backgroundTag":"unauthorized-transfer-signer","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}