Production readiness: fix password hashing, add CI, align docs#15
Merged
Conversation
Critical fixes
- Replace passlib with the maintained bcrypt library directly. passlib 1.7.4
is unmaintained and crashes against bcrypt>=4.1 ("module 'bcrypt' has no
attribute '__about__'" / 72-byte ValueError), so a fresh install broke every
signup/login. Truncate inputs to bcrypt's 72-byte limit; pin bcrypt>=4.1,<6.
- Add a GitHub Actions CI workflow (backend lint+unit tests, frontend
lint+build, agent tests). There was no CI, which is why broken tests reached
main unnoticed.
Docs
- Rewrite the Security Notes in README.md and CLAUDE.md: they described
SHA-256 hashing, token-{user_id} auth, and hardcoded CORS as TODOs, but all
three were already replaced with bcrypt, signed JWTs, and env-driven CORS.
Document the genuinely-remaining hardening items instead.
Production deployment
- Add frontend/Dockerfile.prod (multi-stage Vite build served by nginx) plus
nginx.conf (SPA fallback + API reverse-proxy + /healthz) and a .dockerignore;
the compose frontend service was shipping the Vite dev server.
- Add a backend healthcheck to docker-compose.yml.
Hygiene
- Fix stale test_embed_texts_deterministic_fallback (asserted dim 3 vs the
service's 512) and make it hermetic.
- Remove dead pages/admin/Users.jsx (router resolves users.tsx).
- Fix pre-existing lint violations (B904/B905/F841/I001) and whitelist the
framework Depends/E402 patterns so the repo passes its own linters in CI.
https://claude.ai/code/session_01K5vvMbgiRvkvfRrcKeFxe8
The .gitignore ignored the entire .github directory (two entries), so the new .github/workflows/ci.yml was silently never staged or pushed — which is why no CI run appeared. Remove the .github ignore rules and harden the workflow for this repo's setup: no committed package-lock.json (use npm install, drop lockfile cache) and explicit pip cache-dependency-path. https://claude.ai/code/session_01K5vvMbgiRvkvfRrcKeFxe8
test_send_once_posts_correct_payload substring-matched the serialized JSON with no separator whitespace (e.g. "register_token":"tok"), but httpx 0.27.2 serializes with spaces, so it failed under the agent's pinned deps. Parse the JSON and assert on values instead. https://claude.ai/code/session_01K5vvMbgiRvkvfRrcKeFxe8
Frontend job failed on 7 pre-existing ESLint errors (now that lint actually
runs in CI):
- The flat config set `globals: { browser: true }`, which declares a global
literally named "browser" instead of the browser globals — so console,
setInterval, clearInterval, structuredClone and Image were all no-undef.
Import the `globals` package and spread globals.browser/node.
- App.jsx had UI text starting with "//" that ESLint read as a stray JSX
comment (react/jsx-no-comment-textnodes) — wrap it as a string expression.
- Annotator.jsx had an eslint-disable for react-hooks/exhaustive-deps, but
that plugin isn't loaded, so the directive itself errored — remove it.
Backend job timed out/failed installing the full ~1.5GB ML stack (torch,
ultralytics, onnx, onnxruntime, open-clip, opencv) just to run unit tests
that never import it (those libs are loaded lazily inside services). Add
backend/requirements-test.txt with the lightweight runtime deps and point the
unit-test job at it: install drops from minutes to ~15s and is deterministic.
The auth/bcrypt-class coverage is preserved since those deps remain.
Verified locally: 96 backend unit tests pass, ruff+black clean, frontend lint
+ build clean, agent tests pass.
https://claude.ai/code/session_01K5vvMbgiRvkvfRrcKeFxe8
…lects tests The backend unit-test job failed at collection: test_agents_install.py does `from tests.conftest import client`, which needs the backend root on sys.path. conftest.py only adds src/, so it worked under `python -m pytest` (CWD on path) but not under the bare `pytest` console script CI uses — ModuleNotFoundError: No module named 'tests'. Add `pythonpath = [".", "src"]` to the pytest config so collection works under any invocation. Also remove the dead frontend/.eslintrc.cjs: ESLint 9 uses the flat config (eslint.config.js) and warned that .eslintrc.cjs is not a valid flat-config name on every lint run. Verified locally with the CI-exact bare console scripts: 96 backend unit tests pass, 8 agent tests pass, frontend lint + build clean. https://claude.ai/code/session_01K5vvMbgiRvkvfRrcKeFxe8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A production-readiness pass over VisionForge. The codebase is further along than its own docs claim — but there were two real deploy blockers plus several hygiene gaps. Everything below is implemented and verified.
Critical fixes
requirements.txtpinnedpasslib[bcrypt]>=1.7(unmaintained since 2020) with no bcrypt ceiling, so a clean install pullsbcrypt 5.0, which passlib can't drive — every signup/login threwValueError(module 'bcrypt' has no attribute '__about__'/ 72-byte error). Replaced passlib with the maintainedbcryptlibrary directly, truncate inputs to bcrypt's 72-byte limit, and pinnedbcrypt>=4.1,<6..github/workflows/ci.yml(backend lint + unit tests, frontend lint + build, agent tests). It installs the realrequirements.txt(CPU torch) — exactly what would have caught the bcrypt break. The absence of CI is why broken tests had reachedmainunnoticed.Docs
README.mdandCLAUDE.md. They described SHA-256 hashing,token-{user_id}auth, and hardcoded CORS as TODOs — all three were already replaced with bcrypt, signed JWTs, and env-driven CORS. Now documents the genuinely-remaining hardening (rotate secrets, Redis-backed rate limiter for multi-replica, TLS, prod frontend build).Production deployment
frontend/Dockerfile.prod(multi-stage Vite build served by nginx),frontend/nginx.conf(SPA history fallback + API reverse-proxy +/healthz), andfrontend/.dockerignore. The composefrontendservice was shipping the Vite dev server.docker-compose.yml.Hygiene
test_embed_texts_deterministic_fallback(asserted dim 3 vs the service's actual 512) and made it hermetic.frontend/src/pages/admin/Users.jsx(the router resolvesusers.tsx).Depends(B008) / alembic+bootstrapE402patterns so the repo passes its own linters under CI.lint_all.shhad been swallowing these with|| true.Verification
ruff checkandblack --check: cleanNotes / out of scope
https://claude.ai/code/session_01K5vvMbgiRvkvfRrcKeFxe8
Generated by Claude Code