Skip to content

Modernize MichMoney: security fixes, uv/ruff/Vite, tests, CI#1

Open
rramboer wants to merge 1 commit into
mainfrom
modernize
Open

Modernize MichMoney: security fixes, uv/ruff/Vite, tests, CI#1
rramboer wants to merge 1 commit into
mainfrom
modernize

Conversation

@rramboer

@rramboer rramboer commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Note: This PR targets this fork (rramboer/MichMoney), not the upstream SpartaHack repo.

⚠️ Action required after merge

Real secrets were committed to this repo's history: the Flask SECRET_KEY, three live admin API tokens, and real user accounts (emails + bcrypt hashes) in sql/data.sql and the tracked var/michmoney.sqlite3. This PR removes them from the tree, but they remain in git history — rotate them and treat the historical credentials as compromised.

Summary

Full modernization of this SpartaHack fintech app (structurally a sibling of WolvWealth) to current professional standards, preserving app behavior.

Security

  • Admin API was fully unauthenticated: all 7 /api/admin/* and /api/db/* routes called check_admin_priv() but discarded the result. New fail-closed require_admin returns 403; db_dump no longer returns password hashes or raw token values
  • SECRET_KEY now read from MICHMONEY_SECRET_KEY (dev-only fallback + rotation warning); seed data replaced with obviously-fake dev accounts
  • CSRF tokens on all form POSTs; logout/delete POST-only; delete requires login
  • A non-ASCII Authorization header or CSRF token used to raise TypeError inside hmac.compare_digest → HTTP 500; now rejected cleanly as 403/400 (with regression tests)
  • Restored the MAX_CONTENT_LENGTH request-body cap; bin/run prod runs gunicorn on localhost instead of sudo flask --debug

Correctness

  • add_admin/remove_admin 500'd on every request (phantom username params)
  • Hardcoded UTC−5 replaced with DST-correct zoneinfo Eastern time (unit-tested EST vs EDT)
  • Atomic token-use decrement; DB commits only on success; parse_universe no longer mutates the list it iterates
  • Data paths resolved via pathlib; committed deterministic synthetic market data so /api/optimize works offline; dead/buggy yfinance code removed
  • Choropleth.jsx no longer re-downloads its data every 5 seconds; broken redirect(redirect(...)) auth flows replaced with a shared login_required decorator

Tooling

  • Python: hatchling + PEP 621 pyproject.toml, audited deps, uv.lock; ruff replaces pylint/black; frozen requirements.txt deleted
  • Frontend: webpack+babel → Vite (same bundle path); pruned 17 unused deps (react-scripts, typosquat-suspect react-do, unused chart libs), added the genuinely-missing d3-scale, and used npm overrides to dedupe vulnerable transitives — npm audit: 0 vulnerabilities. Charting libs actually in use (d3, visx, react-simple-maps, chart.js) were verified by import audit and kept. ESLint 9 flat config with react-hooks
  • Untracked the committed SQLite DB and build artifacts; removed a 24 MB orphaned hero image and dead data files; comprehensive .gitignore
  • CI: Python job (uv, ruff check + format, pytest) and frontend job (npm ci, eslint, Vite build); Dependabot; MIT LICENSE; .editorconfig

Tests & docs

  • 76 pytest cases, 85% coverage (was zero): auth flows, CSRF (including the non-ASCII case), admin 403 matrix, /api/account (including non-ASCII key), optimizer end-to-end, timezone units
  • README rewritten with quickstart, scripts table, configuration + rotation warning, API docs, and project structure; the Devpost hackathon story is preserved

Verification

  • Fresh uv install, ruff check/format --check, pytest (76/76), npm ci + lint + Vite build, npm audit (0) — all green
  • App smoke-tested: DB rebuilt from seeds; GET / and /login/ → 200; auth-gated /forex/, /account/, /earnings-analyzer/ → 302; anonymous POST /api/db/dump/ → 403; bundle asset served; no 500s in the log

Security:
- Admin API endpoints called check_admin_priv() but discarded the
  result, leaving all 7 /api/admin and /api/db routes unauthenticated;
  new fail-closed require_admin decorator returns 403, and db_dump no
  longer returns password hashes or raw token values
- SECRET_KEY moved to MICHMONEY_SECRET_KEY env var (dev-only fallback
  with rotation warning); real user rows and admin tokens in sql/data.sql
  replaced with fake dev seeds. The old key, three admin tokens, and
  real user credentials remain in git history and must be rotated
- CSRF tokens on all form POSTs; logout/delete POST-only; delete
  requires login; session.pop fixes anonymous 500s
- Non-ASCII Authorization header and CSRF token now rejected cleanly
  (403/400) instead of raising TypeError -> 500 in hmac.compare_digest
- Restored MAX_CONTENT_LENGTH request-body cap
- bin/run prod runs gunicorn on 127.0.0.1 instead of sudo flask --debug

Correctness:
- add_admin/remove_admin 500'd on every request (phantom params)
- Hardcoded UTC-5 replaced with DST-correct zoneinfo America/Detroit
- Atomic token-use decrement; commit only on success, else rollback
- parse_universe rebuilds its list instead of mutating while iterating
- Data paths resolved via pathlib; deterministic synthetic sample data
  committed so /api/optimize works offline; dead yfinance code removed
- Choropleth.jsx no longer re-downloads every 5s; broken auth redirects
  replaced with a shared login_required decorator

Tooling:
- pyproject on hatchling with audited deps + uv.lock; ruff replaces
  pylint/black; requirements.txt deleted
- Frontend: webpack+babel -> Vite; pruned 17 unused deps (react-scripts,
  react-do, unused chart libs), added missing d3-scale, npm overrides
  dedupe vulnerable transitives (npm audit: 0); ESLint 9 flat config
- Untracked var/michmoney.sqlite3 and build artifacts; removed 24 MB
  orphaned hero image and dead data files; rewritten .gitignore
- GitHub Actions CI (uv+ruff+pytest, node eslint+vite), dependabot,
  MIT LICENSE, .editorconfig

Tests and docs:
- 76 pytest cases at 85% coverage: auth flows, CSRF (incl. non-ASCII),
  admin 403 matrix, /api/account (incl. non-ASCII key), optimizer
  end-to-end, timezone units
- README rewritten with quickstart, scripts table, config + rotation
  warning, API docs, and structure; Devpost story preserved
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