Skip to content

[BUG] Missing Score Validation in log_task API Allows Validators to Submit Arbitrary Scores #178

@EnthusiasticTech

Description

@EnthusiasticTech

Project

term-challenge

Description

The /api/v1/validator/log_task endpoint accepts a score: f64 field from validators without any validation. A malicious validator can submit arbitrary score values (including negative, extreme, or NaN values) to manipulate agent data.

Severity: HIGH

Location:

  • src/api/handlers.rs (lines 1704, 1832)
  • src/api/routes/validator.rs (line 366)
  • Endpoint: POST /api/v1/validator/log_task

Error Message

Debug Logs

System Information

Bounty Version: 0.1.0
OS: Ubuntu 24.04 LTS
CPU: AMD EPYC-Genoa Processor (8 cores)
RAM: 15 GB

Screenshots

No response

Steps to Reproduce

  1. Set up a validator with valid credentials (hotkey, signature)
  2. Send a POST request to /api/v1/validator/log_task with manipulated score values:

Attack Vector 1 - Score/Passed Mismatch:

curl -X POST /api/v1/validator/log_task \
  -H "Content-Type: application/json" \
  -d '{
    "validator_hotkey": "<valid_hotkey>",
    "signature": "<valid_signature>",
    "timestamp": 1737400000,
    "agent_hash": "abc123...",
    "task_id": "task-001",
    "task_name": "task-001",
    "passed": false,
    "score": 1.0,
    "execution_time_ms": 1000,
    "steps": 5,
    "cost_usd": 0.01,
    "started_at": 1737399900
  }'

Attack Vector 2 - Extreme Values:

{ "passed": true, "score": 999999.0 }

Attack Vector 3 - Negative Scores:

{ "passed": false, "score": -100.0 }

Attack Vector 4 - Special Float Values:

{ "passed": true, "score": "NaN" }

Expected Behavior

The server should validate the score field:

  1. Reject NaN and Infinity values
  2. Enforce score range between 0.0 and 1.0
  3. Ensure score matches the passed boolean (1.0 for passed, 0.0 for failed)

The legitimate validator code (src/worker/validator.rs:2090) correctly computes score:

"score": if passed { 1.0 } else { 0.0 },

The server should enforce the same rule.

Actual Behavior

The server accepts any score value without validation and stores it directly in the database:

// src/api/handlers.rs:1824-1847
let task_log = TaskLog {
    // ...
    passed: req.passed,
    score: req.score,  // Directly stored without validation!
    // ...
};

The LogTaskRequest struct accepts any f64 value:

// src/api/handlers.rs:1696-1720
pub struct LogTaskRequest {
    // ...
    pub passed: bool,
    pub score: f64,  // NO VALIDATION - accepts any value
    // ...
}

Additional Context

Mitigating Factor: The final consensus score is calculated from passed_tasks/total_tasks ratio (using the passed boolean), not from summing the score field. However, the total_score field IS stored in TaskLogSummary and used in database queries:

-- src/storage/pg.rs:4157
COALESCE(SUM(score::FLOAT8), 0.0)::FLOAT8

Suggested Fix:

// Add to log_task handler before creating TaskLog

// Validate score bounds
if req.score.is_nan() || req.score.is_infinite() {
    return Err((StatusCode::BAD_REQUEST, Json(LogTaskResponse {
        success: false,
        error: Some("Invalid score value (NaN or Infinite)".to_string()),
        ..Default::default()
    })));
}

if req.score < 0.0 || req.score > 1.0 {
    return Err((StatusCode::BAD_REQUEST, Json(LogTaskResponse {
        success: false,
        error: Some("Score must be between 0.0 and 1.0".to_string()),
        ..Default::default()
    })));
}

// Better: Compute score server-side from passed field
let score = if req.passed { 1.0 } else { 0.0 };

Additional Recommendations:

  1. Server should compute the score from the passed field rather than trusting the client
  2. Add database constraints: CHECK (score >= 0.0 AND score <= 1.0)
  3. Log anomalous scores for monitoring

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions