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
Summary
Four auth and access-control gaps in the bot API.
flask-Limiteris 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-Limiterdeclared but never instantiatedflask-Limiter==3.12is inrequirements.txtbut zeroLimiter(...)or@limiter.limit()calls exist anywhere. All three bot API endpoints are reachable without rate limiting. A compromised agent key can hammer/bot_api/getjobto starve the priority queue at arbitrary speed.Fix: instantiate
Limiterin__init__.pyand apply per-endpoint limits (e.g. 60/min ongetjob, 30/min onsndjob).Finding 2 — Timing oracle on agent key index lookup (
apis.py:115–124)validate_agent_keyqueries the DB on the first 16 chars of the key, then callscheck_password_hash(). WhenNoResultFoundis 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:
Finding 3 — Race condition on shared priority scheduler state (
apis.py:270–290)_select_weighted_priority()reads and writesdb.app.config["priority_weighted_round_robin"]without a lock. Under any multi-threaded WSGI deployment, two concurrent/bot_api/getjobcalls race on this dict, producing lost updates and corrupted fairness counters.Fix:
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_idvalues from the info-level log line emitted on mismatch.Fix: check ownership before the idempotency short-circuit:
Files
webapp/app/apis.py,webapp/app/__init__.py