Skip to content

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

Merged
GeiserX merged 5 commits into
mainfrom
feat/web-deferred-followups
Jul 10, 2026
Merged

perf+security+test: deferred audit follow-ups (health growth, first-run token, orchestrator coverage)#100
GeiserX merged 5 commits into
mainfrom
feat/web-deferred-followups

Conversation

@GeiserX

@GeiserX GeiserX commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Implements three items deferred in #98, each independently verified (ruff check + ruff format --check + full pytest 1075 passed). (Re-opened as a fresh PR — the original #99 auto-closed when its stacked base branch was deleted on merge; identical content, now rebased directly on main.)

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% 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])

Two lowest-covered, most Docker-privileged modules: orchestrator.py 38%→73%, worker_api.py 44%→74% (61 tests, Docker SDK fully mocked). Plus a _login_attempts/setup-token autouse reset fixture removing latent order-dependent flake.

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

Summary by CodeRabbit

  • Security

    • Added first-run setup-token protection for creating the initial account.
    • Restricted first-run access to private or loopback networks.
    • Improved client IP detection when operating behind a trusted proxy.
    • Setup tokens are cleared after successful initial registration.
  • Maintenance

    • Added a weekly database maintenance task.
  • Reliability

    • Expanded automated coverage for authentication, deployment, container management, and setup-token behavior.

GeiserX added 3 commits July 10, 2026 18:42
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

Review Change Stack

Warning

Review limit reached

@GeiserX, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b9bf2868-ae22-4b4e-a710-0bc8d6e9593e

📥 Commits

Reviewing files that changed from the base of the PR and between 1812260 and f78c323.

📒 Files selected for processing (8)
  • app/database.py
  • app/deps.py
  • app/main.py
  • app/routers/auth.py
  • app/templates/auth.html
  • tests/test_database.py
  • tests/test_main_routes.py
  • tests/test_setup_token.py
📝 Walkthrough

Walkthrough

Changes

First-run access and lifecycle

Layer / File(s) Summary
Setup-token lifecycle
app/setup_token.py
Adds URL-safe token generation, activation, clearing, retrieval, and constant-time verification.
Client-IP and access guard
app/deps.py, app/main.py
Derives client IPs with optional trusted-proxy support and enforces private-network plus setup-token access.
Startup and maintenance scheduling
app/main.py
Initializes first-run tokens during startup and schedules weekly database vacuuming.
Registration integration
app/routers/auth.py, app/templates/auth.html
Passes setup tokens through first-user registration and clears persisted and active token state after owner creation.
Registration and lifecycle validation
tests/conftest.py, tests/test_main_routes.py, tests/test_setup_token.py
Covers token gating, client-IP derivation, cleanup, and scheduler wiring.

API and orchestrator coverage

Layer / File(s) Summary
Orchestrator branch coverage
tests/test_orchestrator_coverage.py
Covers Docker statistics, container lookup, status reporting, deduplication, and failure paths.
Worker API coverage
tests/test_worker_api_coverage.py
Covers authentication, deployment validation, container commands, logs, removal, and error mappings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • GeiserX/CashPilot#46: Updates first-run registration access with private or loopback network restrictions.
  • GeiserX/CashPilot#85: Provides the earlier private-network registration guard extended here with client-IP and setup-token handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% 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 reflects the three main follow-up areas: retention/perf, first-run token security, and orchestrator test coverage.
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.
✨ 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.

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.61%. Comparing base (3e10aa4) to head (f78c323).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #100      +/-   ##
==========================================
+ Coverage   92.46%   92.61%   +0.14%     
==========================================
  Files          30       31       +1     
  Lines        3118     3180      +62     
==========================================
+ Hits         2883     2945      +62     
  Misses        235      235              
Files with missing lines Coverage Δ
app/database.py 87.35% <100.00%> (+0.32%) ⬆️
app/deps.py 100.00% <100.00%> (ø)
app/main.py 92.89% <100.00%> (+0.09%) ⬆️
app/routers/auth.py 100.00% <100.00%> (ø)
app/setup_token.py 100.00% <100.00%> (ø)
🚀 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/deps.py`:
- Around line 97-112: The first-run setup flow accepts the sensitive token
through a query parameter, exposing it in access logs and browser history.
Update `_require_first_run_access` and its startup/help messaging to remove or
de-emphasize `request.query_params.get("setup_token")`, directing operators to
the `X-Setup-Token` header or `setup_token` form field instead; ensure no
generated URL or log advertises the token in a query string.
🪄 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: 48045f47-2d6f-4e34-a724-0277adcad7a9

📥 Commits

Reviewing files that changed from the base of the PR and between 3e10aa4 and 1812260.

📒 Files selected for processing (10)
  • app/deps.py
  • app/main.py
  • app/routers/auth.py
  • app/setup_token.py
  • app/templates/auth.html
  • tests/conftest.py
  • tests/test_main_routes.py
  • tests/test_orchestrator_coverage.py
  • tests/test_setup_token.py
  • tests/test_worker_api_coverage.py

Comment thread app/deps.py
GeiserX added 2 commits July 10, 2026 18:56
…r _run_vacuum + first-run token

A messy multi-rebase during PR stacking silently dropped the database.py half of
the health_events change (HEALTH_CHECK_RETENTION_DAYS, the check-sample purge, and
vacuum_database) while keeping main.py's _run_vacuum + scheduler job — leaving
main calling a database function that no longer existed. Restore the database.py
logic and its tests, and add coverage for main._run_vacuum (success + error) and
the lifespan first-run setup-token generation (the codecov/patch gaps).

Full pytest 1081 passed; ruff check + format clean.
…odeRabbit)

A ?setup_token= URL leaks the secret into reverse-proxy access logs and browser
history. Drop query-param support in _require_first_run_access (form field +
X-Setup-Token header only); the GET /register page now renders behind just the
network check so the operator can reach it, the token is a visible form field
they type from the logs, and the startup log no longer embeds the token in a URL.
@GeiserX

GeiserX commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

The query-param setup-token finding is addressed in f78c323: _require_first_run_access no longer reads request.query_params, the token is a form field / X-Setup-Token header only, the GET /register page renders behind just the network check, and the startup log no longer embeds the token in a URL.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
cd /tmp
gh pr view 100 --repo GeiserX/CashPilot --json commits --jq '.commits[] | select(.oid | startswith("f78c323")) | .oid' || true
echo "---"
gh api repos/GeiserX/CashPilot/commits/f78c323 --jq '.files[].filename' || true

Length of output: 533


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== deps.py ==="
cat app/deps.py
echo "=== auth.py ==="
cat app/routers/auth.py
echo "=== main.py (relevant parts) ==="
grep -n "setup_token\|_setup_token\|client_ip" app/main.py

Length of output: 13220


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '300,345p' app/main.py

Length of output: 2326


@GeiserX, confirmed. Commit f78c323 addresses the finding correctly:

  • _require_first_run_access only checks setup_token_value (form field) or the X-Setup-Token header, no request.query_params read.
  • GET /register gates only via main._require_private_network(request) — token isn't required/exposed on that path.
  • Startup log at Line 333 in app/main.py prints the token as plain text, not embedded in a URL.
  • tests/test_setup_token.py covers the token/header/network enforcement paths.

Nice fix. I'll trigger a full review of the changes.

ヾ(⌐■_■)ノ♪

✅ Action performed

Review finished.

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 GeiserX merged commit 8595a89 into main Jul 10, 2026
7 checks passed
@GeiserX GeiserX deleted the feat/web-deferred-followups branch July 10, 2026 17:14
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