Skip to content

fix(registry): make owner Signer to prevent update_field signer privilege escalation (#4)#6

Open
nanookclaw wants to merge 1 commit intocascade-protocol:mainfrom
nanookclaw:fix/register-agent-owner-signer-escalation
Open

fix(registry): make owner Signer to prevent update_field signer privilege escalation (#4)#6
nanookclaw wants to merge 1 commit intocascade-protocol:mainfrom
nanookclaw:fix/register-agent-owner-signer-escalation

Conversation

@nanookclaw
Copy link
Copy Markdown

Problem

Closes #4.

register_agent fails with Cross-program invocation with unauthorized signer or writable account when both conditions are true:

  1. owner != payer (minting on behalf of a different wallet)
  2. additional_metadata is provided (non-null, non-empty)

Root cause

Step 2g calls spl_token_metadata_interface::instruction::update_field with owner as the update_authority. That instruction marks the update_authority as a required signer:

AccountMeta::new_readonly(*update_authority, true)  // is_signer = true

But owner was declared as UncheckedAccount<'info>, which means its AccountInfo has is_signer = false. The Solana runtime detects the mismatch and rejects with:

signer privilege escalated
Program ... failed: Cross-program invocation with unauthorized signer or writable account

The default path (owner == payer) was unaffected because payer: Signer<'info> already had is_signer = true, which propagated to both account slots.

Fix

Change owner from UncheckedAccount<'info> to Signer<'info>.

- /// CHECK: Can be any valid pubkey
- pub owner: UncheckedAccount<'info>,
+ /// Must sign: update_field CPI requires owner as update_authority signer.
+ /// When owner == payer, pass payer pubkey for both accounts.
+ pub owner: Signer<'info>,

This is semantically correct: owner is always the metadata update authority (set in step 2f's initialize_metadata call). Requiring the owner to sign authorises registration of their own agent NFT.

Client behaviour

Case Before After
owner == payer ✅ Pass payer for both; one signer ✅ Same — no change
owner != payer, no metadata ✅ No update_field CPIs; no issue ✅ Owner must now sign (correct)
owner != payer, with metadata ❌ Signer escalation crash ✅ Owner signs; CPIs succeed

Tests

Two regression tests added to programs/sati/tests/registry/register_agent.rs:

  • test_register_agent_owner_not_payer_metadata_key_too_long_correct_error — owner != payer, owner included as signer; transaction reaches input validation (MetadataKeyTooLong) rather than the signer escalation bug. Asserts the error does not contain "signer privilege".
  • test_register_agent_owner_equals_payer_metadata_key_too_long_correct_error — owner == payer; verifies the common path still works after the Signer change.

The full success-path integration test requires a Token-2022 devnet environment with update_field dispatch; the TypeScript SDK tests cover that path.

…calation

Fixes cascade-protocol#4: register_agent failed with 'signer privilege escalated' when
owner != payer and additional_metadata was non-empty.

Root cause: spl_token_metadata_interface::instruction::update_field marks
the update_authority (owner) as a required signer:
  AccountMeta::new_readonly(*update_authority, true)

The previous UncheckedAccount<'info> for owner had is_signer = false,
so the Solana runtime rejected the CPI with:
  Cross-program invocation with unauthorized signer or writable account

Fix: Change owner from UncheckedAccount<'info> to Signer<'info>. The
update_authority is always the owner (set in step 2f initialize_metadata),
so requiring owner to sign is semantically correct — they are authorising
the registration of their own agent NFT.

Client behaviour (unchanged for common case):
- owner == payer: pass payer pubkey for both accounts; no extra signer
- owner != payer: include owner as an additional signer in the transaction

Tests added:
- test_register_agent_owner_not_payer_metadata_key_too_long_correct_error:
  verifies owner != payer + metadata reaches validation (not signer bug)
- test_register_agent_owner_equals_payer_metadata_key_too_long_correct_error:
  verifies owner == payer path still works after the Signer change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

register_agent: signer privilege escalation when owner != payer with additionalMetadata

1 participant