T09 · Insecure Skill Coding Practices
Error
- Location
- references/compressed-pdas.md:270
- Finding
- Missing Owner Authorization in the Compressed Account Update Template## Vulnerability Details **File Location**: `references/compressed-pdas.md:270-303` **Vulnerability Type**: Missing object-level authorization **Risk Level**: High ### Vulnerable Code ```rust pub fn update_account<'info>( ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>, proof: ValidityProof, current_account: MyCompressedAccount, account_meta: CompressedAccountMeta, new_message: String, ) -> Result<()> { let light_cpi_accounts = CpiAccounts::new( ctx.accounts.signer.as_ref(), ctx.remaining_accounts, crate::LIGHT_CPI_SIGNER, ); // new_mut consumes input state and creates output state let mut my_compressed_account = LightAccount::<MyCompressedAccount>::new_mut( &crate::ID, &account_meta, current_account, )?; my_compressed_account.message = new_message.clone(); msg!( "Updated compressed account message to: {}", my_compressed_account.message ); LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) .with_light_account(my_compressed_account)? .invoke(light_cpi_accounts)?; Ok(()) } ``` ### Technical Analysis `MyCompressedAccount` contains an `owner` field, but the update handler does not verify that the stored owner equals `ctx.accounts.signer.key()`. The signer requirement proves only that some key authorized the transaction; it does not prove that the signer owns the compressed account being modified. The validity proof establishes that the supplied compressed-account state exists in the relevant Merkle tree. It does not provide application-level authorization. Likewise, `LightAccount::new_mut` reconstructs and transitions the supplied state but does not replace a program-specific owner check. Because compressed-account state and its metadata can be obtained through public blockchain RPC s ...[truncated 1334 chars]
- Remediation
- ## Remediation Suggestions - Verify ownership before constructing the mutable light account: ```rust require_keys_eq!( current_account.owner, ctx.accounts.signer.key(), ErrorCode::Unauthorized ); ``` - Derive the expected compressed address from signer-bound seeds and the approved address tree, then verify that `account_meta.address` equals that derived address. - Do not rely on proof possession, transaction fee payment, or an arbitrary signer as authorization. - Apply explicit authorization checks to every mutable or destructive operation, including update, close, burn, and reinitialization. - Add negative tests proving that a second keypair cannot update an account owned by the original creator. - Where delegated updates are required, implement an explicit and narrowly scoped delegation record rather than weakening the owner check.
