Summary
Severity: High
Location: config.py
A gunicorn.conf.py can declare workers = 1 and call asyncio.run(main()) where main is a module-level async def that await asyncio.to_thread(...) mutates globals()["workers"]. The scanner only flagged direct asyncio.run(asyncio.to_thread(...)), not asyncio.run(main()), so startup allowed memory:// while import-time execution still raised the runtime worker count.
Attacker
Anyone who can supply or modify the Gunicorn config used to start the panel.
Controlled input
workers = 1
import asyncio
async def main():
await asyncio.to_thread(lambda: globals().update({"workers": 4}))
asyncio.run(main())
Reachability
Same as other Gunicorn worker-guard bypasses: config import runs before the master forks; startup validation reads AST only; RATELIMIT_STORAGE_URI=memory:// passes when the scan reports a static single worker.
Impact
Per-worker login rate limits and broken cross-worker session invalidation when Gunicorn actually runs multiple workers.
Remediation
Collect module-level async functions that await mutating asyncio.to_thread targets and treat asyncio.run(<name>) / asyncio.run(<name>()) as dynamic worker mutations.
Summary
Severity: High
Location:
config.pyA
gunicorn.conf.pycan declareworkers = 1and callasyncio.run(main())wheremainis a module-levelasync defthatawait asyncio.to_thread(...)mutatesglobals()["workers"]. The scanner only flagged directasyncio.run(asyncio.to_thread(...)), notasyncio.run(main()), so startup allowedmemory://while import-time execution still raised the runtime worker count.Attacker
Anyone who can supply or modify the Gunicorn config used to start the panel.
Controlled input
Reachability
Same as other Gunicorn worker-guard bypasses: config import runs before the master forks; startup validation reads AST only;
RATELIMIT_STORAGE_URI=memory://passes when the scan reports a static single worker.Impact
Per-worker login rate limits and broken cross-worker session invalidation when Gunicorn actually runs multiple workers.
Remediation
Collect module-level async functions that await mutating
asyncio.to_threadtargets and treatasyncio.run(<name>)/asyncio.run(<name>())as dynamic worker mutations.