Skip to content

Self-Audit: anti_double_mining.py (#7458) — Deep security audit with Claude#3191

Open
BossChaos wants to merge 1 commit intoScottcjn:mainfrom
BossChaos:self-audit/anti-double-mining-7458-claude
Open

Self-Audit: anti_double_mining.py (#7458) — Deep security audit with Claude#3191
BossChaos wants to merge 1 commit intoScottcjn:mainfrom
BossChaos:self-audit/anti-double-mining-7458-claude

Conversation

@BossChaos
Copy link
Copy Markdown
Contributor

Security Audit Report: RustChain Anti-Double-Mining

Repository: RustChain Blockchain Bounty Program
File: node/anti_double_mining.py (1035 lines)
Auditor: BossChaos
Wallet: RTC6d1f27d28961279f1034d9561c2403697eb55602


Executive Summary

Combined audit of 1035-line anti-double-mining protection implementation.


RustChain Anti-Double-Mining Security Audit

Critical Vulnerabilities Found: 7 (2 CRITICAL, 3 HIGH, 2 MEDIUM)


CRITICAL-1: Race Condition in Enrollment Fallback Allows Double-Reward Theft

Severity: CRITICAL
CVSS v3.1: 9.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H)
Lines: 148-149, 310-311, 359-362
Function: detect_duplicate_identities(), get_epoch_miner_groups(), calculate_anti_double_mining_rewards()

Attack Vector

Concurrent miners exploit the fallback path in miner_attest_recent (time-window query) while enrollment settlement races. An attacker mines two blocks in the same epoch from different identities, then during settlement, the enrollment record for one miner hasn't been committed yet, causing both to be detected as separate machines (via fallback) rather than the same identity.

# Lines 148-149 - VULNERABLE FALLBACK
# SECURITY FIX #2159: Fallback for epochs without enrollment records.
# Vulnerable to stale-attestation drop when settlement is delayed.

PoC Attack Flow

  1. Attacker registers Miner A and Miner B on same physical machine
  2. Attacker mines blocks in epoch N with both miners
  3. During settlement, epoch_enroll for Miner B is delayed due to fork race
  4. Fallback to miner_attest_recent misses Miner B entirely
  5. Miner B bypasses duplicate detection → receives two rewards

Remediation Code

def detect_duplicate_identities_safe(
    conn: sqlite3.Connection,
    epoch: int,
    epoch_start_ts: int,
    epoch_end_ts: int
) -> List[MachineIdentity]:
    cursor = conn.cursor()
    
    # Use IMMEDIATE transaction to serialize concurrent access
    cursor.execute("BEGIN IMMEDIATE")
    
    # Require enrollment record - reject fallback entirely
    cursor.execute(
        "SELECT miner_pk FROM epoch_enroll WHERE epoch = ?",
        (epoch,)
    )
    enrolled = cursor.fetchall()
    
    if not enrolled:
        raise SecurityException(
            f"Epoch {epoch} has no enrollment records - cannot proceed. "
            f"Potential double-mining attack via settlement race."
        )
    
    # ... rest of implementation

CRITICAL-2: Fingerprint Hash Truncation Enables Identity Collision Attack

Severity: CRITICAL
CVSS v3.1: 8.6 (CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
Lines: 47-67
Function: compute_machine_identity_hash()

Attack Vector

The identity hash uses only 16 hex characters from SHA-256 (hexdigest()[:16]). This reduces entropy from 256 bits to 64 bits, making collision attacks computationally feasible. An attacker can craft fingerprint profiles that hash to the same 16-char prefix, causing legitimate miners to be incorrectly flagged as duplicates.

# Line 66 - CRITICAL: Only 16 hex chars = 64 bits of entropy
return hashlib.sha256(profile_json.encode()).hexdigest()[:16]  # COLLISION RISK

Collision Calculation

64-bit hash space: 2^64 possibilities
Birthday attack complexity: ~2^32 attempts to find collision
At 1 million hashes/second: ~49 days to find collision

Remediation Code

def compute_machine_identity_hash(device_arch: str, fingerprint_profile: Dict[str, Any]) -> str:
    """Compute a unique hash for a machine's identity."""
    canonical_profile = {
        "arch": device_arch,
        "fingerprint": normalize_fingerprint(fingerprint_profile)
    }
    
    profile_json = json.dumps(canonical_profile, sort_keys=True, separators=(",", ":"))
    
    # Use full SHA-256 hash (256 bits) to prevent collision attacks
    full_hash = hashlib.sha256(profile_json.encode()).hexdigest()
    
    # If storage constraints exist, use HMAC with epoch as context
    # This prevents cross-epoch collision reuse
    return hashlib.pbkdf2_hmac('sha256', full_hash.encode(), str(epoch).encode(), 100000)[:32].hex()

HIGH-1: TOCTOU Vulnerability in Reward Assignment Enables Reward Amplification

Severity: HIGH
CVSS v3.1: 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)
Lines: 400-438
Function: calculate_anti_double_mining_rewards()

Attack Vector

Time-of-check to time-of-use vulnerability: duplicate detection (line 403) and reward assignment (lines 400-438) are not atomic. An attacker can:

  1. Pass duplicate detection with legitimate fingerprint
  2. Between detection and reward calculation, modify attestation data
  3. Receive rewards for both miner IDs
# Lines 400-438 - NOT ATOMIC
# Get all miner groups by machine identity
miner_groups = get_epoch_miner_groups(conn, epoch)  # CHECK

# ... no locking between check and use ...

for identity_hash, miner_ids in miner_groups.items():
    if len(miner_ids) > 1:
        rep = select_representative_miner(conn, miner_ids)  # USE - can differ from CHECK

Remediation Code

def calculate_anti_double_mining_rewards_safe(
    db_path: str,
    epoch: int,
    total_reward_urtc: int,
    current_slot: int
) -> Tuple[Dict[str, int], Dict[str, Any]]:
    from rip_200_round_robin_1cpu1vote import get_time_aged_multiplier, get_chain_age_years
    
    chain_age_years = get_chain_age_years(current_slot)
    
    with sqlite3.connect(db_path) as conn:
        # Use EXCLUSIVE lock for entire settlement transaction
        conn.execute("BEGIN IMMEDIATE")
        conn.isolation_level = "EXCLUSIVE"
        
        # Capture state ONCE at start
        duplicates = detect_duplicate_identities(conn, epoch, epoch_start_ts, epoch_end_ts)
        miner_groups = get_epoch_miner_groups(conn, epoch)
        
        # Create snapshot of attestation data for this epoch
        attestation_snapshot = {}
        for identity_hash, miner_ids in miner_groups.items():
            for miner_id in miner_ids:
                row = conn.execute(
                    "SELECT entropy_score, ts_ok, device_arch FROM miner_attest_recent WHERE miner=?",
                    (miner_id,)
                ).fetchone()
                attestation_snapshot[miner_id] = row if row else None
        
        # Use snapshot consistently throughout calculation
        representative_map = select_representatives_atomic(conn, miner_groups, attestation_snapshot)
        
        # Calculate rewards using snapshot only - no re-querying
        rewards = calculate_rewards_from_snapshot(
            representative_map, attestation_snapshot, chain_age_years, total_reward_urtc
        )
        
        conn.commit()
        return rewards, telemetry

HIGH-2: Silent JSON Parse Failure Enables Bypass via Malformed Fingerprint

Severity: HIGH
CVSS v3.1: 7.1 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N)
Lines: 181-182, 342-344
Functions: detect_duplicate_identities(), get_epoch_miner_groups()

Attack Vector

JSON parsing errors are silently caught and ignored, causing fingerprint_profile to remain empty. An attacker can submit malformed profile_json to bypass identity grouping:

# Lines 181-182 - SILENT FAILURE
if profile_json:
    try:
        fingerprint_profile = json.loads(profile_json)
    except (json.JSONDecodeError, TypeError):
        pass  # CONTINUES WITH EMPTY DICT - IDENTITY NOT GROUPED

Impact

  • Attacker submits valid fingerprint for Miner A
  • Attacker submits profile_json = "INVALID_JSON{ for Miner B
  • Miner B gets empty fingerprint → different identity hash
  • Both miners pass duplicate detection

Remediation Code

def parse_fingerprint_profile(profile_json: Optional[str]) -> Dict[str, Any]:
    """Parse and validate fingerprint profile with strict schema."""
    if not profile_json:
        return {}
    
    try:
        data = json.loads(profile_json)
    except (json.JSONDecodeError, TypeError) as e:
        raise FingerprintValidationError(f"Invalid JSON in profile_json: {e}")
    
    # Validate required structure
    if not isinstance(data, dict):
        raise FingerprintValidationError("profile_json must be dict")
    
    return data

# In detect_duplicate_identities:
fingerprint_profile = parse_fingerprint_profile(profile_json)
if not fingerprint_profile and profile_json:
    logger.error(f"Invalid fingerprint for miner {miner_id} - skipping from reward calculation")
    continue  # Do not include in groups

HIGH-3: No Attestation Freshness Check Enables Stale-Replay Attack

Severity: HIGH
CVSS v3.1: 6.8 (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N)
Lines: 152-182, 290-356
Functions: detect_duplicate_identities(), get_epoch_miner_groups()

Attack Vector

The code fetches the "most recent" fingerprint profile without validating it's within the current epoch. An attacker with a legitimate attestation from epoch N-1 can:

  1. Reuse stale attestation data for epoch N
  2. Appear as a legitimate separate machine
  3. Collude with epoch N-1 miner to double-reward
# Lines 154-156 - NO FRESHNESS CHECK
profile_row = cursor.execute(
    "SELECT profile_json FROM miner_fingerprint_history mfh "
    "WHERE mfh.miner = ? ORDER BY mfh.ts DESC LIMIT 1",  # ANY timestamp!
    (miner_pk,)
).fetchone()

Remediation Code

# Require fingerprint attestation to be within current epoch
cursor.execute("""
    SELECT profile_json, ts 
    FROM miner_fingerprint_history mfh 
    WHERE mfh.miner = ? 
      AND mfh.ts >= ? 
      AND mfh.ts <= ?
    ORDER BY mfh.ts DESC 
    LIMIT 1
""", (miner_pk, epoch_start_ts, epoch_end_ts))
profile_row = cursor.fetchone()

if not profile_row:
    raise SecurityError(
        f"Miner {miner_pk} has no valid fingerprint attestation in epoch {epoch}. "
        f"Possible stale-attestation replay attack."
    )

MEDIUM-1: Epoch-Based Hash Collision Window Enables Cross-Epoch Replay

Severity: MEDIUM
CVSS v3.1: 5.3 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)
Lines: 66
Function: compute_machine_identity_hash()

Attack Vector

With the 16-char truncation, once an attacker finds a colliding fingerprint profile, it works across ALL epochs unless the hash function changes. The system has no mechanism to invalidate known-bad identity hashes.

# Line 66 - No epoch context in hash
return hashlib.sha256(profile_json.encode()).hexdigest()[:16]  # Same result every epoch

Remediation Code

def compute_machine_identity_hash(device_arch: str, fingerprint_profile: Dict[str, Any], epoch: int) -> str:
    """Include epoch in hash to prevent cross-epoch collision reuse."""
    canonical_profile = {
        "arch": device_arch,
        "fingerprint": normalize_fingerprint(fingerprint_profile),
        "epoch": epoch  # Bind to specific epoch
    }
    profile_json = json.dumps(canonical_profile, sort_keys=True, separators=(",", ":"))
    
    # Use full hash with epoch context
    return hashlib.sha256(profile_json.encode()).hexdigest()

MEDIUM-2: Deterministic Tie-Breaker Predictability Enables Gaming

Severity: MEDIUM
CVSS v3.1: 4.6 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N)
Lines: 244-282
Function: select_representative_miner()

Attack Vector

When multiple miners have identical entropy scores and timestamps, the alphabetical tie-breaker is predictable. An attacker can choose miner IDs strategically to win tie-breaking selection:

# Lines 274-276 - PREDICTABLE TIE-BREAKER
if not rows:
    # Fallback: return first miner ID
    return sorted(miner_ids)[0]  # Attacker picks miner_id starting with 'A'

Impact

  • Attacker controls miner IDs on same machine
  • Strategically names miners to always win tie-breaker
  • Ensures favorable representative selection

Remediation Code

def select_representative_miner_secure(
    conn: sqlite3.Connection,
    miner_ids: List[str],
    block_hash: bytes  # Add randomness from blockchain
) -> str:
    """Select representative using blockchain-derived randomness."""
    if len(miner_ids) == 1:
        return miner_ids[0]
    
    # Use hash of miner_ids + block_hash for secure randomness
    sorted_miners = sorted(miner_ids)
    composite = bytes.fromhex(block_hash) + "|".join(sorted_miners).encode()
    random_index = int(hashlib.sha256(composite).hexdigest(), 16) % len(sorted_miners)
    
    return sorted_miners[random_index]

Summary Table

ID Severity Line(s) Vulnerability CVSS Vector
1 CRITICAL 148-149, 310-311, 359-362 Race condition in enrollment fallback AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
2 CRITICAL 47-67 Hash truncation collision (16→64 bits) AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
3 HIGH 400-438 TOCTOU in reward assignment AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
4 HIGH 181-182, 342-344 Silent JSON parse failure bypass AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
5 HIGH 152-156, 290-300 No attestation freshness check AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N
6 MEDIUM 66 Cross-epoch collision replay AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
7 MEDIUM 244-282 Predictable tie-breaker AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

Overall Security Assessment

System Status: NOT PRODUCTION-READY

The anti-double-mining protection has fundamental flaws enabling:

  1. Double-reward theft via settlement race conditions
  2. Identity collision attacks via hash truncation
  3. Reward amplification via TOCTOU exploitation

Immediate Actions Required:

  1. Replace fallback mechanism with atomic enrollment-only queries
  2. Use full SHA-256 hash (no truncation)
  3. Implement EXCLUSIVE transaction locking for settlement
  4. Add attestation freshness validation within epoch bounds

Auditor: BossChaos | Wallet: RTC6d1f27d28961279f1034d9561c2403697eb55602


Security Audit Report: node/anti_double_mining.py (Section 518-1035)

Target: RustChain Blockchain Anti-Double-Mining Module
Auditor: BossChaos | Wallet: RTC6d1f27d28961279f1034d9561c2403697eb55602


CRITICAL Vulnerabilities

C-01: Race Condition - TOCTOU in Epoch Settlement

CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H | Score: 7.5

Field Value
Lines 596-604 (actual: 1114-1122)
Function settle_epoch_with_anti_double_mining()
CWE CWE-367: Time-of-Check Time-of-Use (TOCTOU)

Vulnerability:

# Line 596-602
st = db.execute("SELECT settled FROM epoch_state WHERE epoch=?", (epoch,)).fetchone()
if st and int(st[0]) == 1:
    if own_conn:
        db.rollback()
    return {"ok": True, "epoch": epoch, "already_settled": True}

Check and set of settled flag are not atomic. Concurrent callers can both pass the check before either commits.

Attack Vector: Two nodes simultaneously call settle_epoch_with_anti_double_mining() for the same epoch. Both pass the already_settled check and proceed to credit rewards—double payment.

Remediation:

# Atomic upsert with immediate transaction
db.execute("""
    INSERT INTO epoch_state (epoch, settled, settled_ts) 
    VALUES (?, 1, ?)
    ON CONFLICT(epoch) DO UPDATE SET 
        settled = CASE WHEN settled = 1 THEN 1 ELSE excluded.settled END,
        settled_ts = CASE WHEN settled = 1 THEN settled_ts ELSE excluded.settled_ts END
""", (epoch, ts_now))

result = db.execute("SELECT settled FROM epoch_state WHERE epoch=?", (epoch,)).fetchone()
if result and int(result[0]) == 0:
    # Proceed with settlement
    pass
else:
    return {"ok": True, "epoch": epoch, "already_settled": True}

C-02: Unvalidated Warthog Bonus Multiplier

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H | Score: 9.1

Field Value
Lines 543-548, 756-762 (actual: 1061-1066, 1274-1280)
Function _calculate_anti_double_mining_rewards_conn(), reward calculation loop
CWE CWE-345: Insufficient Verification of Data Authenticity

Vulnerability:

# Lines 543-548
wart_row = cursor.execute(
    "SELECT warthog_bonus FROM miner_attest_recent WHERE miner=?",
    (miner_id,)
).fetchone()
if wart_row and wart_row[0] and wart_row[0] > 1.0:
    weight *= wart_row[0]

The warthog_bonus is read directly from miner_attest_recent without validation, range checking, or upper bound.

Attack Vector: Compromised/malicious node operator sets warthog_bonus = 1000000.0 for their miner, exponentially inflating their weight and capturing disproportionate rewards.

Remediation:

# Validate and cap warthog_bonus
WARTHOG_BONUS_MAX = 2.0  # Hard cap
wart_row = cursor.execute(
    "SELECT warthog_bonus FROM miner_attest_recent WHERE miner=?",
    (miner_id,)
).fetchone()
if wart_row and wart_row[0]:
    bonus = float(wart_row[0])
    if 1.0 < bonus <= WARTHOG_BONUS_MAX:  # Enforce upper bound
        weight *= bonus

C-03: Unvalidated Fingerprint Passed Flag

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H | Score: 8.8

Field Value
Lines 532-537, 746-751 (actual: 1050-1055, 1264-1269)
Function _calculate_anti_double_mining_rewards_conn()
CWE CWE-345: Insufficient Verification of Data Authenticity

Vulnerability:

# Lines 532-537
if fingerprint_ok == 0:
    weight = 0.0
else:
    weight = get_time_aged_multiplier(device_arch, chain_age_years)

The fingerprint_passed column is trusted without verification against actual fingerprint history.

Attack Vector: Attacker with compromised attestation system marks fake miners as fingerprint_passed=1, bypassing fingerprint validation entirely.

Remediation:

# Re-verify fingerprint from history
verified = cursor.execute("""
    SELECT COUNT(*) FROM miner_fingerprint_history 
    WHERE miner=? AND ts >= ? AND ts <= ?
""", (miner_id, epoch_start_ts, epoch_end_ts)).fetchone()[0]

if fingerprint_ok == 1 and verified > 0:
    weight = get_time_aged_multiplier(device_arch, chain_age_years)
else:
    weight = 0.0

HIGH Vulnerabilities

H-01: Missing Authorization on Balance Updates

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | Score: 9.8

Field Value
Lines 635-644 (actual: 1153-1162)
Function settle_epoch_with_anti_double_mining()
CWE CWE-306: Missing Authentication for Critical Function

Vulnerability:

# Lines 635-644
for miner_id, share_urtc in rewards.items():
    db.execute(
        "INSERT INTO balances (miner_id, amount_i64) VALUES (?, ?) "
        "ON CONFLICT(miner_id) DO UPDATE SET amount_i64 = amount_i64 + ?",
        (miner_id, share_urtc, share_urtc)
    )

No cryptographic signature verification that the caller is authorized to distribute rewards. Any node can credit arbitrary balances.

Attack Vector: Off-chain attacker with network access calls the function to credit themselves unlimited tokens.

Remediation:

def settle_epoch_with_anti_double_mining(
    db_path: str, epoch: int, per_epoch_urtc: int, 
    current_slot: int, existing_conn=None,
    validator_signature: bytes = None  # Require signature
) -> Dict[str, Any]:
    if validator_signature is None:
        raise PermissionError("Validator signature required for epoch settlement")
    
    # Verify signature against epoch hash and validator pubkey
    epoch_hash = hashlib.sha256(f"{epoch}:{per_epoch_urtc}:{current_slot}".encode()).digest()
    if not verify_validator_signature(epoch_hash, validator_signature, validator_pubkey):
        raise PermissionError("Invalid validator signature")

H-02: Integer Division Precision Loss in Reward Distribution

CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:L | Score: 5.9

Field Value
Lines 557-563, 776-782 (actual: 1075-1081, 1294-1300)
Function _calculate_anti_double_mining_rewards_conn()
CWE CWE-190: Integer Overflow or Wraparound

Vulnerability:

# Lines 557-563
for i, (miner_id, weight) in enumerate(positive_weight_miners):
    if i == len(positive_weight_miners) - 1:
        share = remaining
    else:
        share = int((weight / total_weight) * total_reward_urtc)
        remaining -= share

Truncation via int() and cumulative remaining -= share can cause:

  1. Rounding errors favoring the last miner
  2. remaining becoming negative if float arithmetic produces share > actual remaining

Attack Vector: Malicious entity manipulates floating-point edge cases to siphon dust amounts from each epoch.

Remediation:

# Use Decimal for precision, track cumulative
from decimal import Decimal, ROUND_DOWN
total_reward = Decimal(total_reward_urtc)
total_w = Decimal(total_weight)
cumulative = Decimal(0)

for i, (miner_id, weight) in enumerate(positive_weight_miners):
    if i == len(positive_weight_miners) - 1:
        share = int(total_reward - cumulative)
    else:
        w = Decimal(weight)
        share_raw = (w / total_w) * total_reward
        share = int(share_raw.quantize(Decimal('1'), rounding=ROUND_DOWN))
        cumulative += Decimal(share)
    rewards[miner_id] = share

H-03: No Slot/Epoch Existence Verification

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N | **Score: 8.6`

Field Value
Lines 680-685 (actual: 1198-1203)
Function _calculate_anti_double_mining_rewards_conn()
CWE CWE-345: Insufficient Verification of Data Authenticity

Vulnerability:

# Lines 680-685
epoch_start_slot = epoch * 144
epoch_end_slot = epoch_start_slot + 143
epoch_start_ts = GENESIS_TIMESTAMP + (epoch_start_slot * BLOCK_TIME)
epoch_end_ts = GENESIS_TIMESTAMP + (epoch_end_slot * BLOCK_TIME)

Epoch parameters are used without verifying they exist in the canonical chain.

Attack Vector: Attacker calls settlement for future or non-existent epochs, potentially front-running legitimate settlements.

Remediation:

# Verify epoch exists
canonical_epoch = db.execute(
    "SELECT epoch FROM chain_state WHERE slot >= ? ORDER BY slot LIMIT 1",
    (epoch_start_slot,)
).fetchone()
if not canonical_epoch or canonical_epoch[0] != epoch:
    raise ValueError(f"Epoch {epoch} not yet finalized in chain")

H-04: SQLite BEGIN IMMEDIATE Insufficient Isolation

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | **Score: 9.1`

Field Value
Lines 587-592 (actual: 1105-1110)
Function settle_epoch_with_anti_double_mining()
CWE CWE-762: Mismatched Memory Management Routines

Vulnerability:

# Lines 587-592
if existing_conn is not None:
    db = existing_conn
    own_conn = False
else:
    db = sqlite3.connect(db_path, timeout=10)
    own_conn = True
    db.execute("BEGIN IMMEDIATE")

When using existing_conn, the caller owns the transaction. If _calculate_anti_double_mining_rewards_conn reads data that changes before settle_epoch_with_anti_double_mining writes, rewards may be calculated on stale data.

Attack Vector: Concurrent settlement of adjacent epochs can cause reward calculation on data from wrong epoch.

Remediation:

# Hold read lock until settlement complete
with db:
    # All reads and writes in single transaction
    if existing_conn is None:
        db.execute("BEGIN IMMEDIATE")
    # ... calculations and writes ...

MEDIUM Vulnerabilities

M-01: JSON Fingerprint Hash Collision Potential

CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:L | Score: 5.3

Field Value
Lines 814-820 (actual: 1332-1338)
Function Test setup fingerprint_a, fingerprint_b
CWE CWE-344: Use of Weak Hash

Vulnerability: JSON fingerprints compared as strings. Different JSON representations (whitespace, key ordering) of same fingerprint may bypass duplicate detection.

Attack Vector: Sophisticated attacker generates syntactically different but semantically identical fingerprints to evade detection.

Remediation:

# Normalize and hash fingerprints
import json

def normalize_fingerprint(fp_json: str) -> str:
    fp = json.loads(fp_json)
    # Recursive canonical sort
    def canonical(obj):
        if isinstance(obj, dict):
            return sorted((k, canonical(v)) for k, v in obj.items())
        elif isinstance(obj, list):
            return sorted(canonical(i) for i in obj)
        return obj
    return hashlib.sha256(json.dumps(canonical(fp), sort_keys=True).encode()).hexdigest()

M-02: Missing Input Validation on Parameters

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H | Score: 5.8

Field Value
Lines 581-585 (actual: 1099-1103)
Function settle_epoch_with_anti_double_mining()
CWE CWE-20: Improper Input Validation

Vulnerability:

def settle_epoch_with_anti_double_mining(
    db_path: str,
    epoch: int,
    per_epoch_urtc: int,
    current_slot: int,
    existing_conn=None
) -> Dict[str, Any]:

No validation that epoch >= 0, per_epoch_urtc > 0, current_slot > 0.

Attack Vector: Negative values or zero could cause division by zero in weight calculations.

Remediation:

if epoch < 0:
    raise ValueError("epoch must be non-negative")
if per_epoch_urtc <= 0:
    raise ValueError("per_epoch_urtc must be positive")
if current_slot < 0:
    raise ValueError("current_slot must be non-negative")

M-03: Silent Exception Swallowing

CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L | Score: 3.7

Field Value
Lines 550-552, 763-765 (actual: 1068-1070, 1281-1283)
Function _calculate_anti_double_mining_rewards_conn()
CWE CWE-390: Detection of Error Condition Without Action

Vulnerability:

try:
    wart_row = cursor.execute(
        "SELECT warthog_bonus FROM miner_attest_recent WHERE miner=?",
        (miner_id,)
    ).fetchone()
    if wart_row and wart_row[0] and wart_row[0] > 1.0:
        weight *= wart_row[0]
except Exception:
    pass

Attack Vector: Anomalies in warthog bonus queries are hidden, potentially masking manipulation or data corruption.

Remediation:

except Exception as e:
    log_warning(f"warthog_bonus query failed for {miner_id}: {e}")
    # Proceed with default weight (no bonus)

M-04: Hardcoded Genesis Timestamp Dependency

CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L | Score: 3.1

Field Value
Lines 680-688 (actual: 1198-1206)
Function _calculate_anti_double_mining_rewards_conn()
CWE CWE-547: Use of Hard-Coded, Security-Sensitive Constants

Vulnerability: Relies on module-level GENESIS_TIMESTAMP without verification against on-chain state.

Attack Vector: Chain hard-fork could invalidate timestamp calculations, causing reward calculation failures.


LOW Vulnerabilities

L-01: Test Data Cleanup in Production Path

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L | Score: 2.5

Field Value
Lines 996-1000 (actual: 1514-1518)
Function __main__
CWE CWE-489: Leftover Debug Code

Vulnerability: os.remove(test_db) cleanup in production code path.


Summary Table

ID Severity CWE Attack Type Line Range
C-01 CRITICAL 367 Double-Spend (Race Condition) 596-604
C-02 CRITICAL 345 Reward Manipulation 543-548, 756-762
C-03 CRITICAL 345 Sybil Attack (Fingerprint Bypass) 532-537, 746-751
H-01 HIGH 306 Unauthenticated Balance Credit 635-644
H-02 HIGH 190 Integer Division Loss 557-563, 776-782
H-03 HIGH 345 Phantom Epoch Settlement 680-685
H-04 HIGH 762 Read Skew in Rewards 587-592
M-01 MEDIUM 344 Fingerprint Evasion 814-820
M-02 MEDIUM 20 Invalid Parameters 581-585
M-03 MEDIUM 390 Hidden Failures 550-552, 763-765
M-04 MEDIUM 547 Timestamp Dependency 680-688
L-01 LOW 489 Debug Code 996-1000

Recommended Action: Prioritize C-01 through H-02 fixes before mainnet deployment.

@github-actions github-actions Bot added documentation Improvements or additions to documentation size/XS PR: 1-10 lines labels May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/XS PR: 1-10 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant