From 16e8dd26afa6019ca9244a474b4033616faef1c7 Mon Sep 17 00:00:00 2001 From: jonasXchen Date: Thu, 3 Sep 2026 10:01:57 +0200 Subject: [PATCH 1/6] feat: hardening action instructions with comments --- crank-counter/anchor/programs/crank-counter/src/lib.rs | 5 +++++ .../anchor/programs/delegation-actions/src/lib.rs | 5 +++++ magic-actions/anchor/programs/magic-actions/src/lib.rs | 7 +++++++ .../programs/roll-dice-delegated/src/processor.rs | 9 +++++++++ roll-dice/pinocchio/programs/roll-dice/src/processor.rs | 9 +++++++++ 5 files changed, 35 insertions(+) diff --git a/crank-counter/anchor/programs/crank-counter/src/lib.rs b/crank-counter/anchor/programs/crank-counter/src/lib.rs index 2e0c5a6f..94171718 100644 --- a/crank-counter/anchor/programs/crank-counter/src/lib.rs +++ b/crank-counter/anchor/programs/crank-counter/src/lib.rs @@ -30,6 +30,11 @@ pub mod anchor_counter { Ok(()) } + // SECURITY: besides the scheduled-crank path, this is an ordinary instruction any caller + // can invoke — a wallet transaction or a CPI from another program. Fine for a counter. A + // privileged handler must authenticate the caller for whatever it accepts: verify the + // crank signer PDA the scheduler supplies for the crank path, and/or a `Signer` bound to + // stored authority. /// Increment the counter. pub fn increment(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; diff --git a/delegation-actions/anchor/programs/delegation-actions/src/lib.rs b/delegation-actions/anchor/programs/delegation-actions/src/lib.rs index 20636ff8..d2ea243a 100644 --- a/delegation-actions/anchor/programs/delegation-actions/src/lib.rs +++ b/delegation-actions/anchor/programs/delegation-actions/src/lib.rs @@ -22,6 +22,11 @@ pub mod delegation_actions { Ok(()) } + // SECURITY: besides the post-delegation action path, this is an ordinary instruction any + // caller can invoke — a wallet transaction or a CPI from another program. Fine for a + // counter. A privileged handler must authenticate the caller for whatever it accepts: an + // escrow/authority signer for the action path, and/or a `Signer` bound to stored + // authority. See the magic-actions escrow pattern. pub fn increment(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count += 1; diff --git a/magic-actions/anchor/programs/magic-actions/src/lib.rs b/magic-actions/anchor/programs/magic-actions/src/lib.rs index 2944a823..3a79fb79 100644 --- a/magic-actions/anchor/programs/magic-actions/src/lib.rs +++ b/magic-actions/anchor/programs/magic-actions/src/lib.rs @@ -127,6 +127,13 @@ pub struct Increment<'info> { pub counter: Account<'info, Counter>, } +// SECURITY: besides the post-commit action path, this is an ordinary instruction any caller +// can invoke — a wallet transaction or a CPI from another program (`#[action]` only adds the +// action path). Fine here (just a score bump). A privileged action must authenticate the +// caller for whatever it accepts: require the injected `escrow` signer for the post-commit +// path (only the delegation program can sign it), and/or a `Signer` bound to stored authority. +// #[account(signer, address = ephemeral_balance_pda_from_payer(&escrow_auth.key(), 255))] +// pub escrow: UncheckedAccount<'info>, #[action] #[derive(Accounts)] pub struct UpdateLeaderboard<'info> { diff --git a/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs b/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs index cb6b584f..dd4eb2da 100644 --- a/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs +++ b/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs @@ -124,6 +124,15 @@ pub fn process_callback_roll_dice( return Err(ProgramError::InvalidSeeds); } + // Besides the VRF program's randomness callback, this is an ordinary instruction any + // caller can invoke — a wallet transaction or a CPI from another program. Matching the + // identity address is not enough (`scoped_vrf_identity` is a public PDA any caller can + // pass); requiring it as a signer is what separates the real callback (only the VRF + // program can sign it) from a caller forging randomness. + if !program_identity.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + let rnd_u8 = random_u8_with_range(&randomness, 1, 6); pinocchio_log::log!("Consuming random number: {}", rnd_u8); pinocchio_log::log!("client_seed={}", client_seed); diff --git a/roll-dice/pinocchio/programs/roll-dice/src/processor.rs b/roll-dice/pinocchio/programs/roll-dice/src/processor.rs index 1e1d1834..16ecfd93 100644 --- a/roll-dice/pinocchio/programs/roll-dice/src/processor.rs +++ b/roll-dice/pinocchio/programs/roll-dice/src/processor.rs @@ -118,6 +118,15 @@ pub fn process_callback_roll_dice( return Err(ProgramError::InvalidSeeds); } + // Besides the VRF program's randomness callback, this is an ordinary instruction any + // caller can invoke — a wallet transaction or a CPI from another program. Matching the + // identity address is not enough (`scoped_vrf_identity` is a public PDA any caller can + // pass); requiring it as a signer is what separates the real callback (only the VRF + // program can sign it) from a caller forging randomness. + if !program_identity.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + let rnd_u8 = random_u8_with_range(&randomness, 1, 6); pinocchio_log::log!("Consuming random number: {}", rnd_u8); pinocchio_log::log!("client_seed={}", client_seed); From 768dc73f0d7d677d3946167427627327b4c134f2 Mon Sep 17 00:00:00 2001 From: Jonas Chen <38880107+jonasXchen@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:39:11 +0800 Subject: [PATCH 2/6] Update roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs Co-authored-by: Dhruv D Jain --- .../programs/roll-dice-delegated/src/processor.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs b/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs index dd4eb2da..98438ca6 100644 --- a/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs +++ b/roll-dice/pinocchio/programs/roll-dice-delegated/src/processor.rs @@ -124,11 +124,8 @@ pub fn process_callback_roll_dice( return Err(ProgramError::InvalidSeeds); } - // Besides the VRF program's randomness callback, this is an ordinary instruction any - // caller can invoke — a wallet transaction or a CPI from another program. Matching the - // identity address is not enough (`scoped_vrf_identity` is a public PDA any caller can - // pass); requiring it as a signer is what separates the real callback (only the VRF - // program can sign it) from a caller forging randomness. + // The scoped identity address is public. Its signer privilege proves that the + // VRF program invoked this callback. if !program_identity.is_signer() { return Err(ProgramError::MissingRequiredSignature); } From 2fc0bc7486f520a4f3983b3c044694f25576357a Mon Sep 17 00:00:00 2001 From: Jonas Chen <38880107+jonasXchen@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:39:36 +0800 Subject: [PATCH 3/6] Update crank-counter/anchor/programs/crank-counter/src/lib.rs Co-authored-by: Dhruv D Jain --- crank-counter/anchor/programs/crank-counter/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crank-counter/anchor/programs/crank-counter/src/lib.rs b/crank-counter/anchor/programs/crank-counter/src/lib.rs index 94171718..c5b789bc 100644 --- a/crank-counter/anchor/programs/crank-counter/src/lib.rs +++ b/crank-counter/anchor/programs/crank-counter/src/lib.rs @@ -30,11 +30,9 @@ pub mod anchor_counter { Ok(()) } - // SECURITY: besides the scheduled-crank path, this is an ordinary instruction any caller - // can invoke — a wallet transaction or a CPI from another program. Fine for a counter. A - // privileged handler must authenticate the caller for whatever it accepts: verify the - // crank signer PDA the scheduler supplies for the crank path, and/or a `Signer` bound to - // stored authority. + // This example intentionally keeps `increment` permissionless. A privileged scheduled + // instruction must authenticate the preceding Hydra `Trigger` through the instructions + // sysvar. Scheduled instructions run top-level and do not inherit a Hydra PDA signature. /// Increment the counter. pub fn increment(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; From 985ee962d29d4b76582582398de0e934054e3472 Mon Sep 17 00:00:00 2001 From: Jonas Chen <38880107+jonasXchen@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:39:49 +0800 Subject: [PATCH 4/6] Update delegation-actions/anchor/programs/delegation-actions/src/lib.rs Co-authored-by: Dhruv D Jain --- .../anchor/programs/delegation-actions/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/delegation-actions/anchor/programs/delegation-actions/src/lib.rs b/delegation-actions/anchor/programs/delegation-actions/src/lib.rs index d2ea243a..6c8b48b7 100644 --- a/delegation-actions/anchor/programs/delegation-actions/src/lib.rs +++ b/delegation-actions/anchor/programs/delegation-actions/src/lib.rs @@ -22,11 +22,9 @@ pub mod delegation_actions { Ok(()) } - // SECURITY: besides the post-delegation action path, this is an ordinary instruction any - // caller can invoke — a wallet transaction or a CPI from another program. Fine for a - // counter. A privileged handler must authenticate the caller for whatever it accepts: an - // escrow/authority signer for the action path, and/or a `Signer` bound to stored - // authority. See the magic-actions escrow pattern. + // This example intentionally keeps `increment` permissionless. A privileged + // post-delegation action should require an action signer that was validated and recorded + // when the account was delegated, or check an authority stored in program state. pub fn increment(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count += 1; From b91011a3a8b8af5b1c5fd821351f2a9bc1c83f19 Mon Sep 17 00:00:00 2001 From: Jonas Chen <38880107+jonasXchen@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:40:11 +0800 Subject: [PATCH 5/6] Update roll-dice/pinocchio/programs/roll-dice/src/processor.rs Co-authored-by: Dhruv D Jain --- roll-dice/pinocchio/programs/roll-dice/src/processor.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/roll-dice/pinocchio/programs/roll-dice/src/processor.rs b/roll-dice/pinocchio/programs/roll-dice/src/processor.rs index 16ecfd93..f88c4a5f 100644 --- a/roll-dice/pinocchio/programs/roll-dice/src/processor.rs +++ b/roll-dice/pinocchio/programs/roll-dice/src/processor.rs @@ -118,11 +118,8 @@ pub fn process_callback_roll_dice( return Err(ProgramError::InvalidSeeds); } - // Besides the VRF program's randomness callback, this is an ordinary instruction any - // caller can invoke — a wallet transaction or a CPI from another program. Matching the - // identity address is not enough (`scoped_vrf_identity` is a public PDA any caller can - // pass); requiring it as a signer is what separates the real callback (only the VRF - // program can sign it) from a caller forging randomness. + // The scoped identity address is public. Its signer privilege proves that the + // VRF program invoked this callback. if !program_identity.is_signer() { return Err(ProgramError::MissingRequiredSignature); } From 82d98bb7de886020c798c4acbe162f5fd221afe1 Mon Sep 17 00:00:00 2001 From: Jonas Chen <38880107+jonasXchen@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:40:34 +0800 Subject: [PATCH 6/6] Update magic-actions/anchor/programs/magic-actions/src/lib.rs Co-authored-by: Dhruv D Jain --- .../anchor/programs/magic-actions/src/lib.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/magic-actions/anchor/programs/magic-actions/src/lib.rs b/magic-actions/anchor/programs/magic-actions/src/lib.rs index 3a79fb79..f13dfe05 100644 --- a/magic-actions/anchor/programs/magic-actions/src/lib.rs +++ b/magic-actions/anchor/programs/magic-actions/src/lib.rs @@ -127,13 +127,10 @@ pub struct Increment<'info> { pub counter: Account<'info, Counter>, } -// SECURITY: besides the post-commit action path, this is an ordinary instruction any caller -// can invoke — a wallet transaction or a CPI from another program (`#[action]` only adds the -// action path). Fine here (just a score bump). A privileged action must authenticate the -// caller for whatever it accepts: require the injected `escrow` signer for the post-commit -// path (only the delegation program can sign it), and/or a `Signer` bound to stored authority. -// #[account(signer, address = ephemeral_balance_pda_from_payer(&escrow_auth.key(), 255))] -// pub escrow: UncheckedAccount<'info>, + // `#[action]` adds the action accounts but does not authenticate the caller. + // This example intentionally leaves `update_leaderboard` public. A privileged handler + // must constrain `escrow_auth` to an expected authority and require the derived `escrow` + // PDA as a signer, or require an authority stored in program state. #[action] #[derive(Accounts)] pub struct UpdateLeaderboard<'info> {