Skip to content

HIGH: Bot API auth gaps — timing oracle on key lookup, priority scheduler race, idempotency ownership, rate limiting unused #143

@t0kubetsu

Description

@t0kubetsu

Summary

Four auth and access-control gaps in the bot API. flask-Limiter is declared as a dependency but never wired up; the key validation is vulnerable to timing enumeration; the priority scheduler has a race condition under threads; and the idempotent resubmit path does not verify bot ownership.

Finding 1 — flask-Limiter declared but never instantiated

flask-Limiter==3.12 is in requirements.txt but zero Limiter(...) or @limiter.limit() calls exist anywhere. All three bot API endpoints are reachable without rate limiting. A compromised agent key can hammer /bot_api/getjob to starve the priority queue at arbitrary speed.

Fix: instantiate Limiter in __init__.py and apply per-endpoint limits (e.g. 60/min on getjob, 30/min on sndjob).

Finding 2 — Timing oracle on agent key index lookup (apis.py:115–124)

validate_agent_key queries the DB on the first 16 chars of the key, then calls check_password_hash(). When NoResultFound is raised, the function returns immediately without running the hash comparison. Valid key prefixes are therefore enumerable via response timing — requests with a matching prefix take measurably longer (scrypt/pbkdf2 is deliberately slow).

Fix: always run a dummy hash comparison when no key is found:

except NoResultFound:
    check_password_hash("pbkdf2:sha256:260000$placeholder$" + "a" * 64, value)
    raise ValidationError("Invalid AGENT_KEY")

Finding 3 — Race condition on shared priority scheduler state (apis.py:270–290)

_select_weighted_priority() reads and writes db.app.config["priority_weighted_round_robin"] without a lock. Under any multi-threaded WSGI deployment, two concurrent /bot_api/getjob calls race on this dict, producing lost updates and corrupted fairness counters.

Fix:

import threading
_priority_state_lock = threading.Lock()

def _select_weighted_priority(available_priorities):
    with _priority_state_lock:
        ...

Finding 4 — Idempotency check does not verify bot ownership (apis.py:607–617)

The idempotent resubmit path returns 200 for any authenticated agent submitting an already-completed job UID, regardless of which bot originally ran it. Any bot can probe finished job UIDs and harvest bot_id values from the info-level log line emitted on mismatch.

Fix: check ownership before the idempotency short-circuit:

if job_bot.finished and not job_bot.active:
    if job_bot.bot_id != submitting_bot.id:
        return self.response(403, message="forbidden")
    return self.response(200, message="ready")

Files

webapp/app/apis.py, webapp/app/__init__.py

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