Skip to content

feat: security hardening, collector refactor, async orchestrator#57

Merged
GeiserX merged 9 commits into
mainfrom
feat/traffmon-jwt-volume-cleanup
May 19, 2026
Merged

feat: security hardening, collector refactor, async orchestrator#57
GeiserX merged 9 commits into
mainfrom
feat/traffmon-jwt-volume-cleanup

Conversation

@GeiserX
Copy link
Copy Markdown
Owner

@GeiserX GeiserX commented May 19, 2026

Summary

Major release combining security fixes, collector infrastructure refactor, and performance improvements identified through an extensive multi-agent code review (8 specialized reviewers covering security, architecture, performance, collector quality, API design, error handling, concurrency, and test coverage).

Security Hardening

  • Timing-safe API key comparison — replaced == with hmac.compare_digest for admin and fleet key checks in auth.py
  • Content-Security-Policy header — added CSP to security middleware with CDN allowlist
  • Login rate limiting — 5 attempts per 5 minutes per IP to prevent brute force
  • Deploy spec validation — block privileged containers, SYS_ADMIN capability, and dangerous volume mounts (/, /etc, /proc, /sys, /root, Docker socket) at worker level
  • XSS prevention — escape error messages in fleet.html innerHTML
  • Session invalidation — tokens now carry iat timestamp; CASHPILOT_SESSION_EPOCH env var enables mass session revocation
  • Password policy — minimum raised from 8 to 10 characters
  • Error sanitization — collector-alerts endpoint truncates messages to 200 chars (prevent internal info leakage)

Collector Base Refactor

  • Reusable HTTP clientsBaseCollector._get_client() creates persistent httpx.AsyncClient per collector (eliminates 14+ TLS handshakes per cycle)
  • Transient failure retryBaseCollector._retry() with exponential backoff for TimeoutException/NetworkError
  • Instance caching — factory persists collector instances across collection cycles; cached auth tokens survive between runs
  • Lifecycle managementclose_all_collectors() on shutdown prevents unclosed client warnings
  • Full stack traces — all collectors now log with exc_info=True
  • Removed dead codebytes_uploaded field removed from EarningsResult

Collector-Specific Improvements

  • Traffmonetizer — browser JWT auth (replaces broken email/password), proper token validation
  • IPRoyal — stable device_id across logins (prevents security alerts), token caching with 401 re-login
  • Grass — replaced magic sentinel floats (-1.0, -2.0) with _GrassStatus enum
  • EarnApp — configurable API version via class attribute
  • Bytelixir — path-based URL validation (not just string "login" check), _get_client override for session cookies
  • PacketStream — moved import json to module level

Performance & Reliability

  • Parallel collectionasyncio.gather() runs all 14 collectors concurrently (was sequential: 42-70s → ~5-10s)
  • Collection lock — prevents overlapping manual/scheduled runs from wasting API quota
  • Async orchestrator — all Docker SDK calls wrapped in asyncio.to_thread() on worker (was blocking event loop 10-20s per heartbeat with 10 containers)
  • SQLite busy_timeoutPRAGMA busy_timeout=5000 prevents SQLITE_BUSY on write contention
  • Background task logging — upgraded from DEBUG to WARNING (previously invisible in production)
  • Worker error visibility — replaced contextlib.suppress(Exception) with try/except + logging

Infrastructure

  • Volume cleanup on removedelete_volumes flag on container removal endpoint
  • Docker SDK test isolationpytest.importorskip("docker") for CI where SDK is unavailable

Test plan

  • All 803 tests pass locally (1 skipped — docker SDK tests in CI)
  • Ruff lint + format clean
  • CI: test, ruff, CodeQL, GitGuardian, codecov all green
  • Manual: deploy on live instance, verify collection cycle completes
  • Manual: verify bytelixir session cookie still works after refactor
  • Manual: confirm traffmonetizer JWT token works

Summary by CodeRabbit

  • New Features

    • Added per-IP login rate limiting for improved security.
    • Added optional volume deletion when removing containers.
    • Introduced collector instance caching for improved performance.
  • Bug Fixes

    • Fixed HTML escaping in fleet error messages for safe rendering.
    • Improved error message handling with sanitization and truncation.
  • Security

    • Implemented constant-time bearer token validation.
    • Increased minimum password length from 8 to 10 characters.
    • Added Content-Security-Policy header support.
    • Session tokens now include issuance timestamps with mass invalidation capability.
    • Traffmonetizer collector now uses token-only authentication (programmatic login blocked by reCAPTCHA).
  • Performance

    • Concurrent earnings collection across multiple platforms.
    • Prevented overlapping collection runs.
    • Added automatic retry logic with exponential backoff for failed requests.

Traffmonetizer now enforces reCAPTCHA on login, permanently blocking
programmatic email/password auth. Switch collector to browser-extracted
JWT (same pattern as bytelixir/grass). Simpler implementation: token-only
constructor, clear error messages guiding users to Local Storage.

Add delete_volumes flag to remove_service that cleans up named Docker
volumes when removing a container. Flows end-to-end: UI → main API →
worker API → orchestrator. Bind mounts and anonymous volumes are skipped.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@GeiserX has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 40 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 423419ea-dc72-4453-8ade-defc16475ce4

📥 Commits

Reviewing files that changed from the base of the PR and between af6e093 and 3db7077.

📒 Files selected for processing (1)
  • app/collectors/iproyal.py
📝 Walkthrough

Walkthrough

PR refactors collectors to share an async HTTP client and retry helper, migrates Traffmonetizer to token-only JWT auth, adds cached collector lifecycle and close_all_collectors, extends container removal with optional Docker volume deletion (with per-volume error reporting), hardens auth/session handling, adds per-IP login rate limiting and concurrent collection, validates deploy specs, and updates DB PRAGMA, templates, tests, and security headers.

Changes

Collectors refactor & Traffmonetizer JWT

Layer / File(s) Summary
BaseCollector and EarningsResult
app/collectors/base.py
Adds async client lifecycle, _get_client, close, and generic _retry; removes EarningsResult.bytes_uploaded.
Collectors init / simple wrappers
app/collectors/*
Many collectors call super().__init__() and use _get_client/_retry with improved exception logging.
Complex collector flows
app/collectors/bytelixir.py, iproyal.py, honeygain.py, earnapp.py, repocket.py, storj.py, grass.py, mystnodes.py, packetstream.py, proxyrack.py, salad.py, bitping.py
Refactors to nested retry-wrapped fetch coroutines, cached clients, stricter API responses, token refresh paths, and per-collector helpers (_get_client, _fetch_balance, enum statuses).
Traffmonetizer token-only
app/collectors/traffmonetizer.py, services/bandwidth/traffmonetizer.yml, tests/*
Collector switches to token-only JWT flow; constructor simplified; collect() errors explicitly for missing/expired token; service YAML and tests updated to reflect browser JWT usage.
Collector caching lifecycle
app/collectors/__init__.py
Adds in-memory collector instance cache keyed by slug, evicts stale instances when kwargs change or slugs vanish, and exports close_all_collectors() to close all cached/stale instances.
Tests: collector and orchestrator coverage
tests/test_collectors*.py, tests/test_coverage_gaps.py
Updates tests for _get_client changes, adds Traffmonetizer token tests, and expands orchestrator volume-deletion test coverage.

Server infra, orchestrator, auth, DB, and API

Layer / File(s) Summary
Auth and session epoch
app/auth.py
Adds hmac.compare_digest for constant-time bearer-token checks; session tokens include iat and are rejected if older than _SESSION_EPOCH env value.
Orchestrator container removal with volumes
app/orchestrator.py
remove_service gains delete_volumes flag, collects named Docker volumes from Mounts, removes the container, attempts per-volume removal, and returns structured deleted/failed lists.
Main and worker API integration
app/main.py, app/worker_api.py, tests/test_main_deploy_routes.py
Main adds per-IP login rate limiting, collection lock and concurrent collector execution, CSP header, stronger password length check, and _proxy_worker_command params; worker_api validates deploy specs, moves orchestrator calls to asyncio.to_thread, and accepts/forwards delete_volumes flag on container removal endpoints.
DB timeout, password change tracking, and template escaping
app/database.py, app/templates/fleet.html
Sets SQLite PRAGMA busy_timeout=5000; migration adds users.password_changed_at; adds update_user_password(); fleet.html escapes error messages before innerHTML.
Security headers and alert sanitization
app/main.py
Adds Content-Security-Policy header; /api/collector-alerts sanitizes and truncates collector error messages; job/status exception logging upgraded to warnings.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main focus areas of this PR: security hardening, collector infrastructure refactoring, and async orchestrator improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/traffmon-jwt-volume-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

GeiserX added 2 commits May 19, 2026 14:31
- Use hmac.compare_digest for admin/fleet API key comparison (timing attack)
- Add Content-Security-Policy header to security middleware
- Add login rate limiting (5 attempts per 5 minutes per IP)
- Block privileged containers and dangerous volume mounts on worker
- Escape error messages in fleet.html to prevent XSS
- Add PRAGMA busy_timeout=5000 to prevent SQLITE_BUSY on contention
- Parallelize collector execution with asyncio.gather
- Add collection lock to prevent overlapping runs
- Replace silent contextlib.suppress with try/except+logging on worker
- Upgrade background task error logging from debug to warning
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/main.py`:
- Around line 53-59: The current _check_login_rate function records every
attempt before authentication, causing successful logins to count toward the
limit; change the flow so only failed attempts update the _login_attempts bucket
and ensure the bucket is cleared on successful authentication. Concretely,
remove the pre-auth append in _check_login_rate (or add an argument to make it a
read-only check) and instead call a new/updated function to record failures
(e.g., increment_failed_login or _record_failed_login) when authentication
fails; additionally, on successful auth clear _login_attempts[client_ip] (or
reset its list) in the success path. Apply the same change to the other
occurrence referenced (lines ~394-410) so only failed logins are recorded and
successful logins reset the bucket.

In `@app/worker_api.py`:
- Around line 278-287: In _validate_deploy_spec, the volume check only rejects
exact matches so nested/relative mounts slip through; normalize each volume
source (use os.path.realpath or pathlib.Path(source).resolve(strict=False)) and
normalize all entries in _BLOCKED_VOLUME_SOURCES the same way, then reject if
the resolved source is equal to a blocked path or is a subpath (i.e., startswith
blocked_path + os.sep) or is root "/"; update the loop over spec.volumes
(variable source) to perform this normalized comparison and raise the same
HTTPException when matched.

In `@tests/test_main_deploy_routes.py`:
- Around line 272-285: The assertion is too weak; in
test_remove_with_delete_volumes replace the fuzzy check of
mock_client.delete.call_args with a direct assertion that the forwarded query
params are exactly as expected: inspect
mock_client.delete.call_args.kwargs["params"] and assert it equals
{"delete_volumes": "true"} so the test verifies the precise params forwarded by
the code under test (refer to test_remove_with_delete_volumes and
mock_client.delete.call_args).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fe4b2ff5-7de4-4c21-a8bf-31fc12df17ac

📥 Commits

Reviewing files that changed from the base of the PR and between fc363c0 and 9fad938.

📒 Files selected for processing (11)
  • app/auth.py
  • app/collectors/traffmonetizer.py
  • app/database.py
  • app/main.py
  • app/orchestrator.py
  • app/templates/fleet.html
  • app/worker_api.py
  • services/bandwidth/traffmonetizer.yml
  • tests/test_collectors_deep.py
  • tests/test_coverage_gaps.py
  • tests/test_main_deploy_routes.py

Comment thread app/main.py Outdated
Comment thread app/worker_api.py
Comment thread tests/test_main_deploy_routes.py Outdated
GeiserX added 2 commits May 19, 2026 14:52
CI environment doesn't install docker SDK (runtime-only dependency).
Use pytest.importorskip to gracefully skip these tests.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

❌ Patch coverage is 88.64734% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.04%. Comparing base (fc363c0) to head (3db7077).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
app/main.py 79.41% 14 Missing ⚠️
app/collectors/__init__.py 65.51% 10 Missing ⚠️
app/collectors/bytelixir.py 75.75% 8 Missing ⚠️
app/database.py 50.00% 6 Missing ⚠️
app/collectors/iproyal.py 85.00% 3 Missing ⚠️
app/collectors/base.py 92.59% 2 Missing ⚠️
app/collectors/packetstream.py 93.33% 2 Missing ⚠️
app/auth.py 90.00% 1 Missing ⚠️
app/collectors/storj.py 94.73% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #57      +/-   ##
==========================================
- Coverage   96.58%   92.04%   -4.55%     
==========================================
  Files          24       24              
  Lines        2375     2526     +151     
==========================================
+ Hits         2294     2325      +31     
- Misses         81      201     +120     
Files with missing lines Coverage Δ
app/collectors/bitping.py 93.61% <100.00%> (-6.39%) ⬇️
app/collectors/earnapp.py 97.82% <100.00%> (+0.39%) ⬆️
app/collectors/earnfm.py 94.64% <100.00%> (-1.59%) ⬇️
app/collectors/grass.py 92.77% <100.00%> (-2.11%) ⬇️
app/collectors/honeygain.py 97.77% <100.00%> (-2.23%) ⬇️
app/collectors/mystnodes.py 96.25% <100.00%> (-1.22%) ⬇️
app/collectors/proxyrack.py 100.00% <100.00%> (ø)
app/collectors/repocket.py 94.82% <100.00%> (-1.54%) ⬇️
app/collectors/salad.py 100.00% <100.00%> (ø)
app/collectors/traffmonetizer.py 100.00% <100.00%> (ø)
... and 9 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…nc orchestrator

- BaseCollector: add _get_client() for HTTP connection reuse, _retry()
  for transient failure recovery, close() for lifecycle management
- Factory: cache collector instances across cycles (tokens persist),
  evict stale instances on config change, close_all on shutdown
- All 14 collectors: use shared client, add exc_info=True to error logs,
  add super().__init__(), use self._retry() for balance fetches
- Grass: replace magic sentinel floats with _GrassStatus enum
- IPRoyal: stable device_id across logins, token caching with 401 retry
- EarnApp: configurable API version via class attribute
- Bytelixir: path-based URL validation instead of string "login" check
- Orchestrator: wrap all Docker SDK calls in asyncio.to_thread()
- Auth: add session iat (issued-at) + SESSION_EPOCH for mass invalidation
- Database: add password_changed_at column migration + update function
- Pipeline: close stale collectors, sanitize alert errors (200 char max)
- Password policy: minimum raised from 8 to 10 characters
- Remove dead bytes_uploaded field from EarningsResult
@GeiserX GeiserX changed the title feat: traffmonetizer browser JWT auth + volume cleanup on remove feat: security hardening, collector refactor, async orchestrator May 19, 2026
GeiserX added 2 commits May 19, 2026 15:20
EarnApp dashboard API now requires version 1.627.783. Traffmonetizer
needs Origin/Referer headers matching the browser app to avoid 403s.
- Rate limiter only counts failed logins (not successful ones)
- Volume validation blocks subpaths of sensitive mounts
- Strengthen delete_volumes test assertion to check exact params
@GeiserX
Copy link
Copy Markdown
Owner Author

GeiserX commented May 19, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@GeiserX
Copy link
Copy Markdown
Owner Author

GeiserX commented May 19, 2026

All 3 CodeRabbit findings from the previous review have been addressed in commit af6e093:

  1. Rate limiter — now only counts failed logins, clears on success (_record_failed_login + _login_attempts.pop())
  2. Volume subpath blocking — normalizes paths and rejects anything under blocked prefixes (not just exact matches)
  3. Test assertion — now asserts exact params == {"delete_volumes": "true"} instead of string matching

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/collectors/__init__.py (1)

79-147: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale collectors not closed after make_collectors returns.

make_collectors is synchronous but populates _stale with evicted collectors. These only get closed when close_all_collectors() is called (typically at shutdown). If config changes frequently, stale collectors accumulate with open httpx clients.

Consider returning or scheduling async cleanup, or making the caller responsible for awaiting _close_stale() after each make_collectors call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/collectors/__init__.py` around lines 79 - 147, make_collectors currently
accumulates evicted collectors in _stale but never closes them until shutdown;
after building collectors, if _stale is non-empty schedule asynchronous cleanup
instead of deferring to shutdown: import asyncio and call
asyncio.create_task(_close_stale()) (or similar non-blocking scheduling) at the
end of make_collectors when _stale has items, ensuring _close_stale()
consumes/clears _stale and that _cached_collectors/_cached_kwargs remain
consistent; alternatively, if you prefer caller-driven cleanup, change
make_collectors to return a token or the stale list so callers can await
_close_stale() themselves (refer to make_collectors, _stale, _close_stale(),
close_all_collectors(), _cached_collectors, _cached_kwargs).
🧹 Nitpick comments (2)
app/collectors/grass.py (1)

106-108: 💤 Low value

_estimate_from_active_devices still returns -1.0 sentinel while _get_settled_points uses enum.

For consistency, consider returning _GrassStatus.RATE_LIMITED from _estimate_from_active_devices on 429, matching the enum pattern in _get_settled_points.

Also applies to: 184-189

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/collectors/grass.py` around lines 106 - 108, The function
_estimate_from_active_devices returns a float sentinel -1.0 on HTTP 429 but the
rest of the module (e.g., _get_settled_points) uses the _GrassStatus enum;
change the 429 return path(s) in _estimate_from_active_devices (and the other
occurrence around the second 429 handling) to return _GrassStatus.RATE_LIMITED
instead of -1.0, and update any callers or type annotations as needed to expect
an _GrassStatus value from this function.
app/collectors/base.py (1)

36-42: 💤 Low value

Client kwargs not stored; subsequent calls with different kwargs silently reuse stale config.

If a collector calls _get_client(cookies=X) then later _get_client(timeout=60), the second call reuses the existing client (which has the old cookies but ignores the new timeout). This works only because collectors currently use consistent kwargs.

If intentional, document that the first call's kwargs win. Otherwise, store _client_kwargs and recreate on mismatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/collectors/base.py` around lines 36 - 42, The _get_client method
currently reuses self._client even when callers pass different kwargs, causing
stale configuration; update the class to record the initial client kwargs (e.g.,
self._client_kwargs) and in _get_client compare incoming kwargs to
self._client_kwargs and recreate self._client with the new combined defaults
when they differ (also update self._client_kwargs), or if you prefer the
first-call-wins behavior, add a clear docstring on _get_client and a comment
stating that the first call's kwargs are authoritative; reference the
_get_client method and the self._client attribute when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/collectors/iproyal.py`:
- Around line 86-96: When handling a 401 you refresh the token then call
_fetch_balance directly, which bypasses the retry/backoff wrapper used for the
initial fetch; change the re-login path to invoke the same retry-enabled fetch
logic instead of calling _fetch_balance directly (either call the existing
higher-level method that applies retries/backoff or wrap _fetch_balance in the
same retry/backoff helper) so transient network errors after token refresh get
the same retry behavior; update the 401 branch where self._token is set and resp
is re-fetched to use that retry-enabled call.

---

Outside diff comments:
In `@app/collectors/__init__.py`:
- Around line 79-147: make_collectors currently accumulates evicted collectors
in _stale but never closes them until shutdown; after building collectors, if
_stale is non-empty schedule asynchronous cleanup instead of deferring to
shutdown: import asyncio and call asyncio.create_task(_close_stale()) (or
similar non-blocking scheduling) at the end of make_collectors when _stale has
items, ensuring _close_stale() consumes/clears _stale and that
_cached_collectors/_cached_kwargs remain consistent; alternatively, if you
prefer caller-driven cleanup, change make_collectors to return a token or the
stale list so callers can await _close_stale() themselves (refer to
make_collectors, _stale, _close_stale(), close_all_collectors(),
_cached_collectors, _cached_kwargs).

---

Nitpick comments:
In `@app/collectors/base.py`:
- Around line 36-42: The _get_client method currently reuses self._client even
when callers pass different kwargs, causing stale configuration; update the
class to record the initial client kwargs (e.g., self._client_kwargs) and in
_get_client compare incoming kwargs to self._client_kwargs and recreate
self._client with the new combined defaults when they differ (also update
self._client_kwargs), or if you prefer the first-call-wins behavior, add a clear
docstring on _get_client and a comment stating that the first call's kwargs are
authoritative; reference the _get_client method and the self._client attribute
when making this change.

In `@app/collectors/grass.py`:
- Around line 106-108: The function _estimate_from_active_devices returns a
float sentinel -1.0 on HTTP 429 but the rest of the module (e.g.,
_get_settled_points) uses the _GrassStatus enum; change the 429 return path(s)
in _estimate_from_active_devices (and the other occurrence around the second 429
handling) to return _GrassStatus.RATE_LIMITED instead of -1.0, and update any
callers or type annotations as needed to expect an _GrassStatus value from this
function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a366f8dd-e5a2-403e-bd6c-04d3fbabd5bb

📥 Commits

Reviewing files that changed from the base of the PR and between 9fad938 and af6e093.

📒 Files selected for processing (25)
  • app/auth.py
  • app/collectors/__init__.py
  • app/collectors/base.py
  • app/collectors/bitping.py
  • app/collectors/bytelixir.py
  • app/collectors/earnapp.py
  • app/collectors/earnfm.py
  • app/collectors/grass.py
  • app/collectors/honeygain.py
  • app/collectors/iproyal.py
  • app/collectors/mystnodes.py
  • app/collectors/packetstream.py
  • app/collectors/proxyrack.py
  • app/collectors/repocket.py
  • app/collectors/salad.py
  • app/collectors/storj.py
  • app/collectors/traffmonetizer.py
  • app/database.py
  • app/main.py
  • app/worker_api.py
  • tests/test_collectors.py
  • tests/test_collectors_deep.py
  • tests/test_collectors_extended.py
  • tests/test_coverage_gaps.py
  • tests/test_main_deploy_routes.py
💤 Files with no reviewable changes (1)
  • tests/test_collectors.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_main_deploy_routes.py
  • tests/test_coverage_gaps.py
  • app/collectors/traffmonetizer.py

Comment thread app/collectors/iproyal.py
Ensures transient network errors after token refresh get the same
retry/backoff protection as the initial fetch.
@GeiserX GeiserX merged commit f33a60c into main May 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant