Skip to content

perf+security+test: deferred audit follow-ups (health growth, first-run token, orchestrator coverage)#99

Closed
GeiserX wants to merge 6 commits into
mainfrom
feat/web-deferred-followups
Closed

perf+security+test: deferred audit follow-ups (health growth, first-run token, orchestrator coverage)#99
GeiserX wants to merge 6 commits into
mainfrom
feat/web-deferred-followups

Conversation

@GeiserX

@GeiserX GeiserX commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Implements three of the items deferred in #98, each independently verified (ruff check + ruff format --check + full pytest). Stacked on #98 (fix/web-audit-security-correctness) — review/merge that first; GitHub will retarget this to main once it lands.

Two larger deferred items — per-worker fleet keys and the main.py god-module split — are still coming as their own follow-up PRs.

1. Bound health_events growth ([PERF])

check_ok/check_down uptime samples (one per service every 5 min) dominate the table, but get_health_scores only reads a bounded window (/api/health/scores caps at 90 days) while retention kept everything 400 days.

  • Trim samples to HEALTH_CHECK_RETENTION_DAYS=95 (just past the 90-day max query, so no allowed query can out-range its own samples — uptime% is unchanged) while keeping rare lifecycle events (start/stop/restart/crash) the full 400 days.
  • Add a weekly VACUUM job (commit-first, wal_checkpoint(TRUNCATE)) — SQLite never shrinks the file on DELETE alone.

2. First-run setup token + trusted-proxy client IP ([SEC])

The "private network only" guard on first-run /register trusts request.client.host, which behind a reverse proxy is the proxy (loopback/private) — so it always passes and the first public visitor could seize the owner account.

  • While no users exist, startup generates a one-time setup token, persists it (encrypted config), and logs it; /register now requires it (query param / X-Setup-Token header / form field) and it is retired the instant the owner is created.
  • Resolve the real client IP behind a trusted proxy (opt-in CASHPILOT_TRUSTED_PROXY, right-most XFF) — used by the first-run network check and the login rate limiter, which previously collapsed every proxied client into one bucket keyed by the proxy IP.

3. orchestrator/worker_api coverage ([TEST])

The two lowest-covered, most Docker-privileged modules: orchestrator.py 38%→73%, worker_api.py 44%→74% (61 tests, Docker SDK fully mocked). Covers stats parsing edge cases, container-lookup fallbacks, docker-unavailable paths, _verify_api_key negative paths, _validate_deploy_spec rejections, and every command endpoint's success + error branches. Plus a _login_attempts/setup-token autouse reset fixture removing latent order-dependent flake.

Verified: ruff (check + format) clean; full pytest 1075 passed.

GeiserX added 6 commits July 10, 2026 15:41
Ported from improvements made in CashPilot-Desktop (the Go port), applied where
the web app didn't already have them:

- dashboard: surface crashes_7d + an "unstable" flag (>=3 crashes in the 7-day
  health window) on each deployed service row, and render a distinct "unstable"
  badge (worst tone + explicit label + restarts/crashes in the tooltip) so a
  crash-looping earner is legible at a glance, not just via a lower score number.
  Reuses the existing get_health_scores data (already computes crashes/restarts).
- metrics: log a startup WARNING when Prometheus /metrics is enabled — it is
  served unauthenticated and exposes earnings/health, so keep it behind a proxy
  and off untrusted networks.

Tests: two new deployed-row tests (unstable at/above vs below the threshold).
ruff clean; full suite 997 passed.
…op stalls, authz, error surfacing

From a full-codebase 8-lens audit. Security + correctness fixes:

Security:
- worker_api: replace the bypassable deploy-spec volume DENYLIST with a
  realpath-normalized check that blocks /var/run + /run parents (closing the
  /var/run:/x -> docker.sock -> host-root escape) and adds a network_mode
  allowlist (permits host for mysterium, blocks container:<id>). Authenticate the
  previously-unauthenticated worker mini-UI status page.
- main: deploy via /api/workers/{id}/command is now owner-gated (was writer — an
  owner-gate bypass of /api/deploy). Single-source the secret-config-key set from
  database.SECRET_CONFIG_KEYS (the shorter local list left remember_web/xsrf_token
  unmasked in /api/collectors/meta).
- auth/routers: invalidate outstanding sessions on user role-change and deletion
  (a demoted/deleted user kept access until the token expired) via the existing
  per-user epoch mechanism.
- app.js: sanitizeHint rejects non-http(s)/mailto hrefs (blocks javascript:).

Correctness (event-loop stalls under async):
- main: _validate_worker_url's DNS resolution now runs via asyncio.to_thread at
  its sole call site (a slow-DNS worker no longer stalls the whole UI event loop);
  the validator stays sync so its existing tests are unaffected.
- worker_api: 4 unthreaded docker_available() calls now use asyncio.to_thread
  (they blocked exactly when Docker was degraded).
- main: fire-and-forget collection tasks keep a reference + log exceptions
  (_spawn); record_collection_start moved inside the try; the hourly collector
  gather is bounded by a semaphore (anti-storm as the catalog grows).

Consistency / error-surfacing:
- main: both container-action route families now record BOTH the health event and
  the metrics lifecycle (they had diverged).
- exchange_rates: wire the previously-dead rates_stale() into get_all() so the UI
  can flag stale FX.
- app.js: stop fabricating $0.00 / eternal spinners / a false-healthy bell on
  fetch errors — surface the error, keep last values; remove dead code.

Tests: negative-role test for deploy-via-command (owner-only); updated the route
side-effect + deploy-gate tests to the new behavior; added a _login_attempts
autouse reset fixture (removes a latent order-dependent flake).

Verified: ruff clean; full pytest 998 passed.
health_events is dominated by check_ok/check_down uptime samples (one per
service every 5 minutes), yet get_health_scores only reads a bounded window
(/api/health/scores caps it at 90 days) while retention kept everything 400
days. Trim samples to HEALTH_CHECK_RETENTION_DAYS=95 (just past the max query
window, so no allowed query can out-range its own samples) while keeping the
rare lifecycle events (start/stop/restart/crash) the full 400 days. Add a weekly
VACUUM job (commit-first, WAL-checkpoint TRUNCATE) since SQLite never shrinks the
file on DELETE alone — without it the DB keeps its high-water-mark size forever.

Uptime% is unchanged for every allowed query. Tests cover the split-retention
boundary + a clean VACUUM run.
Add 61 tests (Docker SDK fully mocked) for the two lowest-covered, most
privileged modules: orchestrator.py 38%->73%, worker_api.py 44%->74%. Covers
_collect_stats (zero-delta CPU, missing keys, APIError), _find_container name/
label/ValueError fallback, get_status[_light] docker-unavailable + corrupted-
container-skip paths, _verify_api_key negative paths, _validate_deploy_spec
rejections (privileged, cap_add, network_mode, blocked volume roots incl.
subpaths; host/bridge + named volumes allowed), and every command endpoint's
success + ValueError->404 / RuntimeError->503 branches.

Also update the lifespan-scheduler test to expect the new db_vacuum interval
job (added in the previous commit).
The "private network only" guard on first-run /register trusts
request.client.host, which behind a reverse proxy is the proxy (a loopback/
private address) — so the check always passes and the first public visitor could
seize the owner account. Add a proxy-independent gate: while no users exist,
startup generates a one-time setup token, persists it (encrypted config), and
logs it; /register then requires it (query param, X-Setup-Token header, or form
field) and it is retired the moment the owner account is created.

Also resolve the real client IP behind a trusted proxy (opt-in
CASHPILOT_TRUSTED_PROXY, right-most X-Forwarded-For), used by both the first-run
network check and the login rate limiter — which previously collapsed every
proxied client into one bucket keyed by the proxy IP.

Tests: token module + client_ip (trusted/untrusted) + the first-run guard
(missing/wrong/query/header/form token, public-IP-still-blocked, network-only
fallback once retired) + register integration (403 without token, 303 + token
cleared with it); conftest resets the token global between tests.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa46cd5c-f58b-4504-a327-eb211f497995

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-deferred-followups

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.

@GeiserX GeiserX deleted the branch main July 10, 2026 16:39
Base automatically changed from fix/web-audit-security-correctness to main July 10, 2026 16:39
@GeiserX GeiserX closed this Jul 10, 2026
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