substrate-vulnerability-scanner

Scans Substrate pallets for 7 critical vulnerabilities including arithmetic overflow, panic DoS, and bad origin checks. Detects arithmetic overflow, panics, incorrect weights, verify-first violations, unsigned transaction validation issues, bad randomness, and bad origin patterns across FRAME pallets Includes platform detection for Substrate/FRAME projects, scanning workflow with step-by-step guidance, and severity prioritization (critical, high, medium) Provides fuzz testing, benchmarking, and try-runtime recommendations to validate fixes and prevent DoS and state corruption attacks Covers pre-v0.9.25 transactional storage issues and includes a quick-reference audit checklist for dispatchables, access control, and storage safety

INSTALLATION
npx skills add https://github.com/trailofbits/skills --skill substrate-vulnerability-scanner
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Substrate Vulnerability Scanner

1. Purpose

Systematically scan Substrate runtime modules (pallets) for platform-specific security vulnerabilities that can cause node crashes, DoS attacks, or unauthorized access. This skill encodes 7 critical vulnerability patterns unique to Substrate/FRAME-based chains.

2. When to Use This Skill

  • Auditing custom Substrate pallets
  • Reviewing FRAME runtime code
  • Pre-launch security assessment of Substrate chains (Polkadot parachains, standalone chains)
  • Validating dispatchable extrinsic functions
  • Reviewing weight calculation functions
  • Assessing unsigned transaction validation logic

3. Platform Detection

File Extensions & Indicators

  • Rust files: .rs

Language/Framework Markers

// Substrate/FRAME indicators

#[pallet]

pub mod pallet {

    use frame_support::pallet_prelude::*;

    use frame_system::pallet_prelude::*;

    #[pallet::config]

    pub trait Config: frame_system::Config { }

    #[pallet::call]

    impl<T: Config> Pallet<T> {

        #[pallet::weight(10_000)]

        pub fn example_function(origin: OriginFor<T>) -> DispatchResult { }

    }

}

// Common patterns

DispatchResult, DispatchError

ensure!, ensure_signed, ensure_root

StorageValue, StorageMap, StorageDoubleMap

#[pallet::storage]

#[pallet::call]

#[pallet::weight]

#[pallet::validate_unsigned]

Project Structure

  • pallets/*/lib.rs - Pallet implementations
  • runtime/lib.rs - Runtime configuration
  • benchmarking.rs - Weight benchmarks
  • Cargo.toml with frame-* dependencies

Tool Support

  • cargo-fuzz: Fuzz testing for Rust
  • test-fuzz: Property-based testing framework
  • benchmarking framework: Built-in weight calculation
  • try-runtime: Runtime migration testing

4. How This Skill Works

When invoked, I will:

  • Search your codebase for Substrate pallets
  • Analyze each pallet for the 7 vulnerability patterns
  • Report findings with file references and severity
  • Provide fixes for each identified issue
  • Check weight calculations and origin validation

5. Vulnerability Patterns (7 Critical Patterns)

I check for 7 critical vulnerability patterns unique to Substrate/FRAME. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.

Pattern Summary:

-

Arithmetic Overflow ⚠️ CRITICAL

  • Direct +, -, *, / operators wrap in release mode
  • Must use checked_* or saturating_* methods
  • Affects balance/token calculations, reward/fee math

-

Don't Panic ⚠️ CRITICAL - DoS

  • Panics cause node to stop processing blocks
  • No unwrap(), expect(), array indexing without bounds check
  • All user input must be validated with ensure!

-

Weights and Fees ⚠️ CRITICAL - DoS

  • Incorrect weights allow spam attacks
  • Fixed weights for variable-cost operations enable DoS
  • Must use benchmarking framework, bound all input parameters

-

Verify First, Write Last ⚠️ HIGH (Pre-v0.9.25)

  • Storage writes before validation persist on error (pre-v0.9.25)
  • Pattern: validate → write → emit event
  • Upgrade to v0.9.25+ or use manual #[transactional]

-

Unsigned Transaction Validation ⚠️ HIGH

  • Insufficient validation allows spam/replay attacks
  • Prefer signed transactions
  • If unsigned: validate parameters, replay protection, authenticate source

-

Bad Randomness ⚠️ MEDIUM

  • pallet_randomness_collective_flip vulnerable to collusion
  • Must use BABE randomness (pallet_babe::RandomnessFromOneEpochAgo)
  • Use random(subject) not random_seed()

-

Bad Origin ⚠️ CRITICAL

  • ensure_signed allows any user for privileged operations
  • Must use ensure_root or custom origins (ForceOrigin, AdminOrigin)
  • Origin types must be properly configured in runtime

For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.

6. Scanning Workflow

Step 1: Platform Identification

  • Verify Substrate/FRAME framework usage
  • Check Substrate version (v0.9.25+ has transactional storage)
  • Locate pallet implementations (pallets/*/lib.rs)
  • Identify runtime configuration (runtime/lib.rs)

Step 2: Dispatchable Analysis

For each #[pallet::call] function:

  • Arithmetic: Uses checked/saturating operations?
  • Panics: No unwrap/expect/indexing?
  • Weights: Proportional to cost, bounded inputs?
  • Origin: Appropriate validation level?
  • Validation: All checks before storage writes?

Step 3: Panic Sweep

# Search for panic-prone patterns

rg "unwrap\(\)" pallets/

rg "expect\(" pallets/

rg "\[.*\]" pallets/  # Array indexing

rg " as u\d+" pallets/  # Type casts

rg "\.unwrap_or" pallets/

Step 4: Arithmetic Safety Check

# Find direct arithmetic

rg " \+ |\+=| - |-=| \* |\*=| / |/=" pallets/

# Should find checked/saturating alternatives instead

rg "checked_add|checked_sub|checked_mul|checked_div" pallets/

rg "saturating_add|saturating_sub|saturating_mul" pallets/

Step 5: Weight Analysis

  • Run benchmarking: cargo test --features runtime-benchmarks
  • Verify weights match computational cost
  • Check for bounded input parameters
  • Review weight calculation functions

Step 6: Origin &#x26; Privilege Review

# Find privileged operations

rg "ensure_signed" pallets/ | grep -E "pause|emergency|admin|force|sudo"

# Should use ensure_root or custom origins

rg "ensure_root|ForceOrigin|AdminOrigin" pallets/

Step 7: Testing Review

  • Unit tests cover all dispatchables
  • Fuzz tests for panic conditions
  • Benchmarks for weight calculation
  • try-runtime tests for migrations

7. Priority Guidelines

Critical (Immediate Fix Required)

  • Arithmetic overflow (token creation, balance manipulation)
  • Panic DoS (node crash risk)
  • Bad origin (unauthorized privileged operations)

High (Fix Before Launch)

  • Incorrect weights (DoS via spam)
  • Verify-first violations (state corruption, pre-v0.9.25)
  • Unsigned validation issues (spam, replay attacks)

Medium (Address in Audit)

  • Bad randomness (manipulation possible but limited impact)

8. Testing Recommendations

Fuzz Testing

// Use test-fuzz for property-based testing

#[cfg(test)]

mod tests {

    use test_fuzz::test_fuzz;

    #[test_fuzz]

    fn fuzz_transfer(from: AccountId, to: AccountId, amount: u128) {

        // Should never panic

        let _ = Pallet::transfer(from, to, amount);

    }

    #[test_fuzz]

    fn fuzz_no_panics(call: Call) {

        // No dispatchable should panic

        let _ = call.dispatch(origin);

    }

}

Benchmarking

# Run benchmarks to generate weights

cargo build --release --features runtime-benchmarks

./target/release/node benchmark pallet \

    --chain dev \

    --pallet pallet_example \

    --extrinsic "*" \

    --steps 50 \

    --repeat 20

try-runtime

# Test runtime upgrades

cargo build --release --features try-runtime

try-runtime --runtime ./target/release/wbuild/runtime.wasm \

    on-runtime-upgrade live --uri wss://rpc.polkadot.io

9. Additional Resources

  • Building Secure Contracts: building-secure-contracts/not-so-smart-contracts/substrate/

10. Quick Reference Checklist

Before completing Substrate pallet audit:

Arithmetic Safety (CRITICAL):

  • No direct +, -, *, / operators in dispatchables
  • All arithmetic uses checked_* or saturating_*
  • Type conversions use try_into() with error handling

Panic Prevention (CRITICAL):

  • No unwrap() or expect() in dispatchables
  • No direct array/slice indexing without bounds check
  • All user inputs validated with ensure!
  • Division operations check for zero divisor

Weights &#x26; DoS (CRITICAL):

  • Weights proportional to computational cost
  • Input parameters have maximum bounds
  • Benchmarking used to determine weights
  • No free (zero-weight) expensive operations

Access Control (CRITICAL):

  • Privileged operations use ensure_root or custom origins
  • ensure_signed only for user-level operations
  • Origin types properly configured in runtime
  • Sudo pallet removed before production

Storage Safety (HIGH):

  • Using Substrate v0.9.25+ OR manual #[transactional]
  • Validation before storage writes
  • Events emitted after successful operations

Other (MEDIUM):

  • Unsigned transactions use signed alternative if possible
  • If unsigned: proper validation, replay protection, authentication
  • BABE randomness used (not RandomnessCollectiveFlip)
  • Randomness uses random(subject) not random_seed()

Testing:

  • Unit tests for all dispatchables
  • Fuzz tests to find panics
  • Benchmarks generated and verified
  • try-runtime tests for migrations
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card