Skip to content

Document fee-payer SOL/SPL drain attack pattern as cross-SDK invariant #112

@EfeDurmaz16

Description

@EfeDurmaz16

Filed from PR #102 review thread (Ludo inline #102 (comment)).

Scope

The Lua SDK ships a regression test at `lua/tests/solana_verify_spec.lua:786` titled "SECURITY: rejects extra SystemProgram transfer sourced from fee-payer on SPL route". That test guards a real attack pattern that every SDK must enforce. Write the invariant up as cross-SDK security documentation so any future language port has a reference.

Attack shape

  1. Server advertises a server-side fee-payer (`methodDetails.feePayer = true`).
  2. Client builds a valid SPL token-transfer payment to the recipient.
  3. Client appends an extra `SystemProgram::Transfer` instruction sourced from the fee-payer to an attacker-controlled wallet.
  4. Without a source-pubkey allowlist guard the server co-signs the transaction (the SPL transfer half is valid for the receipt) and the fee-payer SOL drain settles in the same tx.

The guard required: server verifier MUST allowlist `SystemProgram::Transfer` source pubkeys. Specifically: when fee_payer mode is active, any SystemProgram transfer in the verified tx must NOT be sourced from the fee-payer's pubkey unless it is the explicit payment instruction (which for the SPL route never is).

Where the guard lives today

  • Rust spine: `rust/src/server/charge.rs` (canonical implementation; trace the SystemProgram transfer guard).
  • Lua: `lua/mpp/server/solana_verify.lua` (mirrored guard + the cited regression test in `solana_verify_spec.lua:786`).
  • Python: attack regression set under `python/tests/`.
  • TypeScript: `typescript/packages/mpp/src/server/Charge.ts` instruction allowlist + source-pubkey check.
  • PHP: `php/src/Server/SolanaChargeTransactionVerifier.php` instruction allowlist.
  • Ruby: `ruby/lib/mpp/methods/solana/verifier.rb`.
  • Go: `go/server/` verifier.

Proposal

  1. Create `docs/security/fee-payer-drain.md` (or wherever the repo's security docs land) that:
    • Names the attack.
    • Shows the malicious instruction sequence.
    • Lists the server-side guard each SDK must implement.
    • Points to the regression test in each language so a new SDK port has a reference.
  2. Add a cross-SDK interop scenario under `tests/interop/` (analogous to charge-cross-server-portability) that asserts every server REJECTS the attack tx with a canonical error code.
  3. Add the doc to the contributor onboarding so any new method-handler port has a checklist item.

Label as `m2-followup` / cross-SDK invariant work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions