skills/multiversx/mx-ai-skills/multiversx-variant-analysis

multiversx-variant-analysis

SKILL.md

Variant Analysis

Multiply the value of a single vulnerability finding by systematically locating similar issues elsewhere in the codebase. Once you find one bug, this skill helps you find all its "cousins."

When to Use

  • After discovering an initial vulnerability
  • During comprehensive security audits
  • When creating detection patterns for CI/CD
  • Before claiming a bug class is fully remediated
  • When assessing the extent of a vulnerability pattern

1. The Variant Analysis Process

From Finding to Pattern

1. Find Initial Bug → Specific vulnerability instance
2. Abstract Pattern → What makes this a bug?
3. Create Search    → Grep/Semgrep queries
4. Find Variants    → All similar occurrences
5. Verify Each      → Confirm true positives
6. Report All       → Document the bug class

Example Transformation

Initial Bug:

// Found: Missing payment validation in deposit()
#[payable("*")]
fn deposit(&self) {
    let payment = self.call_value().single_esdt();
    self.balances().update(|b| *b += payment.amount);
    // Bug: No token ID validation!
}

Abstract Pattern:

  • #[payable("*")] endpoint
  • Uses call_value() to get payment
  • Does NOT validate token_identifier

Search Query:

# Find all payable endpoints
grep -n "#\[payable" src/*.rs

# Then check each for token validation
grep -A 20 "#\[payable" src/*.rs | grep -v "token_identifier"

2. Common MultiversX Variant Patterns

Pattern: Missing Payment Validation

Initial Finding: One endpoint accepts payment but doesn't validate the token.

Variant Search:

# Find all payable endpoints
grep -rn "#\[payable" src/

# Check for missing token validation
# Look for call_value() without subsequent token_identifier check
grep -A 30 "#\[payable" src/*.rs > payable_endpoints.txt
# Manually review each for token_identifier validation

Semgrep Rule:

rules:
  - id: mvx-payable-no-token-check
    patterns:
      - pattern: |
          #[payable("*")]
          $ANNOTATIONS
          fn $FUNC(&self, $...PARAMS) {
              $...BODY
          }
      - pattern-not: |
          #[payable("*")]
          $ANNOTATIONS
          fn $FUNC(&self, $...PARAMS) {
              <... token_identifier ...>
          }

Pattern: Unbounded Iteration

Initial Finding: One function iterates over a VecMapper without bounds.

Variant Search:

# Find all .iter() calls on storage mappers
grep -rn "\.iter()" src/

# Find all for loops over storage
grep -rn "for.*in.*self\." src/

Checklist for Each:

  • Is iteration bounded?
  • Can a user grow the collection?
  • Is there pagination?

Pattern: Callback State Assumptions

Initial Finding: One callback doesn't handle the error case.

Variant Search:

# Find all callbacks
grep -rn "#\[callback\]" src/

# Check for proper result handling
grep -A 20 "#\[callback\]" src/*.rs | grep -c "ManagedAsyncCallResult"

All Callbacks Need:

#[callback]
fn any_callback(&self, #[call_result] result: ManagedAsyncCallResult<T>) {
    match result {
        ManagedAsyncCallResult::Ok(_) => { /* success */ },
        ManagedAsyncCallResult::Err(_) => { /* handle failure! */ }
    }
}

Pattern: Missing Access Control

Initial Finding: One admin function lacks #[only_owner].

Variant Search:

# Find functions that modify admin-like storage
grep -rn "admin\|owner\|config\|fee" src/ | grep "\.set("

# Cross-reference with access control
grep -B 10 "admin.*\.set\|config.*\.set" src/*.rs | grep -v "only_owner"

Pattern: Arithmetic Without Checks

Initial Finding: One calculation uses raw + instead of checked_add.

Variant Search:

# Find all arithmetic operations
grep -rn " + \| - \| \* " src/*.rs

# Exclude test files and comments
grep -rn " + \| - \| \* " src/*.rs | grep -v "test\|//"

3. Systematic Variant Hunting

Step 1: Characterize the Bug

Answer these questions:

  1. What is the vulnerable code pattern?
  2. What makes it exploitable?
  3. What would a fix look like?

Step 2: Create Detection Queries

Grep-based:

# Pattern: [specific code pattern]
grep -rn "[pattern]" src/

# Negative pattern (should be present but isn't)
grep -L "[expected_pattern]" src/*.rs

Semgrep-based:

# See multiversx-semgrep-creator skill for details
rules:
  - id: variant-pattern
    patterns:
      - pattern: <vulnerable pattern>
      - pattern-not: <fixed pattern>

Step 3: Triage Results

For each potential variant:

Result Classification Action
Clearly vulnerable True Positive Report
Needs context Investigate Manual review
Has mitigation False Positive Document why
Different pattern Not a variant Skip

Step 4: Document Findings

## Variant Analysis: [Bug Class Name]

### Initial Finding
- Location: [file:line]
- Description: [what's wrong]

### Pattern Description
[Abstract description of what makes this a bug]

### Search Method
```bash
[grep/semgrep commands used]

Variants Found

Location Status Notes
file1.rs:23 Confirmed Same pattern
file2.rs:45 Confirmed Slight variation
file3.rs:67 FP Has validation elsewhere

Remediation

[How to fix all instances]


## 4. Automation for Future Prevention

### Convert to CI/CD Check

After finding variants, create automated detection:

```yaml
# .github/workflows/security.yml
- name: Check for vulnerability patterns
  run: |
    # Run semgrep with custom rules
    semgrep --config rules/mvx-security.yaml src/

    # Grep-based checks
    if grep -rn "unsafe_pattern" src/; then
      echo "Found potential vulnerability"
      exit 1
    fi

Create Semgrep Rule

See multiversx-semgrep-creator skill:

rules:
  - id: mvx-[bug-class]-[id]
    languages: [rust]
    message: "[Description of bug class]"
    severity: ERROR
    patterns:
      - pattern: <vulnerable pattern>

5. Variant Analysis Checklist

After finding any bug:

  • Abstract the pattern (what makes it a bug?)
  • Create search queries (grep, semgrep)
  • Search entire codebase
  • Triage each result (TP/FP/needs investigation)
  • Verify true positives are exploitable
  • Document all variants
  • Create prevention rule for CI/CD
  • Recommend fix for all instances

6. Common Variant Categories

Input Validation Variants

  • Missing in one endpoint → Check ALL endpoints
  • Missing for one parameter → Check ALL parameters

Access Control Variants

  • Missing on one admin function → Check ALL admin functions
  • Inconsistent role checks → Audit entire role system

State Management Variants

  • Reentrancy in one function → Check ALL external calls
  • Missing callback handling → Check ALL callbacks

Arithmetic Variants

  • Overflow in one calculation → Check ALL math operations
  • Precision loss in one formula → Check ALL division operations

7. Reporting Multiple Variants

Consolidated Report

When multiple variants exist, consolidate:

# Bug Class: [Name]

## Summary
Found [N] instances of [bug description] across the codebase.

## Root Cause
[Why this pattern is vulnerable]

## Instances

### Instance 1 (file1.rs:23)
[Details]

### Instance 2 (file2.rs:45)
[Details]

...

## Recommended Fix
[Generic fix pattern]

```rust
// Before (vulnerable)
[vulnerable code]

// After (fixed)
[fixed code]

Prevention

[How to prevent this class of bugs in the future]


### Severity Aggregation

| Individual Severity | Count | Aggregate Severity |
|---------------------|-------|-------------------|
| Critical | 3+ | Critical |
| High | 5+ | Critical |
| Medium | 10+ | High |
| Low | Any | Low |

## 8. Example: Complete Variant Analysis

**Initial Bug: Missing amount validation in stake()**

```rust
// Found in stake.rs:45
#[payable("EGLD")]
fn stake(&self) {
    let payment = self.call_value().egld_value();
    // Bug: No check for amount > 0
    self.staked().update(|s| *s += payment.clone_value());
}

Pattern: Missing amount > 0 check on payable endpoint

Search:

grep -rn "#\[payable" src/ | cut -d: -f1 | sort -u | while read file; do
    echo "=== $file ==="
    grep -A 30 "#\[payable" "$file" | head -40
done > payable_review.txt

Variants Found:

  1. stake.rs:45 - stake() - CONFIRMED
  2. stake.rs:78 - add_stake() - CONFIRMED
  3. rewards.rs:23 - deposit_rewards() - CONFIRMED
  4. fees.rs:12 - pay_fee() - FALSE POSITIVE (has check on line 15)

Fix Applied to All:

#[payable("EGLD")]
fn stake(&self) {
    let payment = self.call_value().egld_value();
    require!(payment.clone_value() > 0, "Amount must be positive");
    self.staked().update(|s| *s += payment.clone_value());
}

CI Rule Created: rules/mvx-amount-validation.yaml

Weekly Installs
5
GitHub Stars
10
First Seen
Jan 30, 2026
Installed on
antigravity4
openclaw2
claude-code2
codex2
gemini-cli2
cursor2