chore(deploy): pre-deploy audit + fix requirements.txt boot-blocker#21
Conversation
Container deploy via render.yaml -> Dockerfile -> requirements.txt crashed on first `import app` because requirements.txt was missing 4 of 11 declared runtime deps (structlog, flask-limiter, openai, pillow), with 4 additional version drifts vs uv.lock. Reproduced empirically: fresh venv install of pre-fix requirements.txt followed by `python -c 'import app'` raised `ModuleNotFoundError: No module named 'structlog'` at app.py:14 — gunicorn would never have bound. Personalize endpoints would have 5xx'd silently even after that, since openai+pillow are lazy-imported in route handlers. Fixes ----- - requirements.txt: regenerated via `uv export --format requirements-txt --no-dev --no-emit-project --no-hashes`. Now matches uv.lock at the lockfile-resolved versions for all 11 declared deps + transitives. Build artifact, regenerable. - .env.example: created. 24 env vars across app.py, kratos_clone/*, personalize/* documented, grouped by lifecycle ([boot] / [request] / [test-only]) with defaults and one-line descriptions. OPENAI_API_KEY flagged as the per-request fail-mode. - docs/PRE_DEPLOY_AUDIT_2026-05-10.md: full pre-deploy audit report. 2 BLOCKERs fixed in this PR; 5 MAJORs + 9 MINORs documented as follow-up work (CLAUDE.md/WORKFLOW.md doc drift, transitive CVEs in cryptography v41, in-memory rate limiter trap, Playwright 1.57 Chrome-for-Testing memory regression, Dockerfile hardening, Procfile dead code, bandit scope gap on downloader.py). Verification ------------ - Empirical post-fix venv: `import app` succeeds, 10 routes registered. - mypy: Success in 21 source files. - ruff check + format: clean. - pytest: 210 passed, 2 skipped (live OpenAI tests gated). - bandit (CI scope): Medium 0, High 0. - bandit (incl. downloader.py): Medium 0, High 1 (pre-existing N-9, non-security md5 use for filename hashing).
📝 WalkthroughWalkthroughThis PR resolves two deployment blockers by regenerating the Python dependency manifest via ChangesPre-Deploy Audit Fixes: Dependencies & Configuration
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request resolves critical deployment blockers by regenerating requirements.txt to include all necessary runtime dependencies and providing a .env.example file for environment variable configuration. Additionally, a detailed pre-deploy audit report has been added to document current findings and recommended follow-ups. Feedback was provided to correct inconsistent line number citations within the audit report's findings table.
|
|
||
| | ID | Severity | Title | Evidence | Status | | ||
| |---|---|---|---|---| | ||
| | B-1 | 🔴 BLOCKER | `requirements.txt` missing 4 runtime deps + 4 version drifts | `app.py:14` (structlog), `app.py:18` (flask-limiter), `app.py:591/636` (openai/pillow lazy), `requirements.txt` vs `uv.lock` | **FIXED** in this PR | |
There was a problem hiding this comment.
The line numbers cited in the findings table for flask-limiter and pillow are inconsistent with the detailed evidence section (lines 100-102) and the actual source code in app.py. flask-limiter is imported on lines 16-17, and the pillow dependency is triggered by the import on line 637.
| | B-1 | 🔴 BLOCKER | `requirements.txt` missing 4 runtime deps + 4 version drifts | `app.py:14` (structlog), `app.py:18` (flask-limiter), `app.py:591/636` (openai/pillow lazy), `requirements.txt` vs `uv.lock` | **FIXED** in this PR | | |
| | B-1 | 🔴 BLOCKER | `requirements.txt` missing 4 runtime deps + 4 version drifts | `app.py:14` (structlog), `app.py:16` (flask-limiter), `app.py:591/637` (openai/pillow lazy), `requirements.txt` vs `uv.lock` | **FIXED** in this PR | |
There was a problem hiding this comment.
Fixed in 084c126. Findings table now reads app.py:14 (structlog), app.py:16 (flask-limiter), app.py:591/637 (openai/pillow lazy) — matching the detail section and app.py source. Thanks for catching the inconsistency.
Generated by Claude Code
There was a problem hiding this comment.
Confirmed real finding — OSV scanner correctly identifies urllib3==2.6.2 as vulnerable to GHSA-38jv-5279-wg99 (CVE-2026-21441, HIGH-sev decompression-bomb on redirect-following with preload_content=False). I locally verified the bump (uv lock --upgrade-package urllib3 resolves to urllib3==2.7.0; post-bump pytest 210/2 skipped, mypy 21 src OK, boot OK).
Deferred to a dedicated dep-bump PR, not landed here, for two reasons:
- Scope discipline. This PR's BLOCKER is the boot crash (missing
structlog+flask-limiter). Dep bumps are a separate scope by design — seedocs/PRE_DEPLOY_AUDIT_2026-05-10.mdfinding M-3, which now folds urllib3 in alongsidecryptography v41.0.7(6 CVEs, transitive of openai). Both deserve the same dedicated PR with full CI re-run. - The boot fix unblocks Render deploy by itself. urllib3's redirect-decompression vector requires a malicious server in the redirect chain — in this codebase, the exposed call site is
kratos_clone/capture.pyvisiting user-supplied URLs. The capture endpoint is rate-limited and not on the hot path; the fix is important but not deploy-gating.
The audit doc (commit 084c126) has been updated to call this out explicitly under M-3 and the recommended follow-up sequencing now reads: uv lock --upgrade-package cryptography --upgrade-package urllib3.
Pinging this for visibility — happy to land it here instead if @fbmoulin prefers, but the cleaner path is the bump-PR.
Generated by Claude Code
There was a problem hiding this comment.
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 `@requirements.txt`:
- Line 109: The requirements lockfile pins urllib3==2.6.2 which is vulnerable
(CVE-2026-21441); update the pinned dependency to urllib3==2.6.3 or later by
replacing the urllib3==2.6.2 entry in requirements.txt with urllib3==2.6.3+ and
then re-generate/re-export the lockfile so all resolved hashes/entries reflect
the upgrade (look for the urllib3==2.6.2 line to change and ensure any
dependency resolver or export step you use produces the updated
requirements.txt).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 27e4583f-4b4a-43c5-8dbf-d04e1c8b93f0
📒 Files selected for processing (3)
.env.exampledocs/PRE_DEPLOY_AUDIT_2026-05-10.mdrequirements.txt
Findings table cited app.py:18 for flask-limiter and app.py:636 for the pillow lazy import. Actual: flask-limiter is L16 (`from flask_limiter import Limiter`), pillow's import chain triggers at L637 (`from personalize.pipeline import run_pipeline`). Detail section already had the correct numbers; table now matches.
…6-21441) CodeRabbit's OSV scanner flagged urllib3==2.6.2 as vulnerable to CVE-2026-21441 (HIGH-severity decompression-bomb when following HTTP redirects with preload_content=False). Transitive of requests; the exposed call site is kratos_clone/capture.py visiting arbitrary user-supplied URLs. Bumped via `uv lock --upgrade-package urllib3` (resolves to 2.7.0), requirements.txt re-exported. Verified post-bump: app boots (10 routes), pytest 210 passed + 2 skipped, mypy 21 src Success. Note: uv.lock is also bumped locally to urllib3==2.7.0 but is too large to push via the API tool. A follow-up commit from a regular `git push` context will sync the lockfile, OR a `uv lock --upgrade-package urllib3` re-run on `main` after merge will produce the same result.
M-3 split: urllib3 2.6.2 -> 2.7.0 was bumped in commit 67ad8ce (closes the CVE-2026-21441 finding). Cryptography 41.0.7 (6 CVEs) remains deferred to its own bump PR — bumping it requires re-resolution of openai's transitive tree. Follow-up sequencing item #3 renamed "Cryptography bump PR" with the urllib3 entry removed.
Summary
Pre-deploy audit of
main(caac782) found 2 BLOCKERs that prevented production deploy. Both fixed. 5 MAJORs + 9 MINORs documented indocs/PRE_DEPLOY_AUDIT_2026-05-10.mdas follow-up work.B-1 — Container crashed on boot
render.yaml→Dockerfile:14-17→pip install -r requirements.txt. The pre-fixrequirements.txtwas missing 4 of 11 declared runtime deps:structlog— module-level inapp.py:14→ boot crashflask-limiter— module-level inapp.py:16-17→ boot crash (would be next)openai— lazy inapp.py:591→ silent 5xx on/api/personalize/structurepillow— lazy inapp.py:637→ silent 5xx on/api/personalize/runPlus 4 version drifts vs
uv.lock(flask, playwright, requests, gunicorn).Empirically reproduced:
Fix:
uv export --format requirements-txt --no-dev --no-emit-project --no-hashes > requirements.txt. Now a regenerable build artifact pinned to lock-resolved versions for all 11 declared deps + transitives. Eliminates the drift class entirely.Post-fix
import appregisters all 10 routes including/health,/api/personalize/*,/api/client-errors,/download.B-2 —
.env.examplemissing24 env vars consumed across
app.py/kratos_clone/*/personalize/*had no operator-facing reference.OPENAI_API_KEYfails per-request (lazyOpenAI()constructor atpersonalize/openai_client.py:80) — silent until first call to/api/personalize/*. Operators readingrender.yaml(onlyPORTset there) had zero discoverability for the key.Fix:
.env.exampledocuments all 24 vars grouped by lifecycle ([boot]/[request]/[test-only]) and consumer module.Audit report
docs/PRE_DEPLOY_AUDIT_2026-05-10.md(266 lines):Top deferred items:
cryptography v41.0.7transitive (6 CVEs, soft-gated in CI). Bump path:uv sync --upgrade-package cryptography.--workers 1.CLAUDE.mdandWORKFLOW.mddoc drift (claimed P2 items remaining = 7, actual 0; aspirational stages claimed = 3, actual 0; "52 tests" → 210; "$0.32 per run" cost claim unverified).Test plan
requirements.txt→import appsucceeds, 10 routes registered (replicates Dockerfile path 1:1)uv run mypy --config-file pyproject.toml→ Success in 21 source filesuv run ruff check kratos_clone/ scripts/→ All checks passeduv run ruff format --check kratos_clone/ scripts/→ 11 files already formatteduv run pytest -q→ 210 passed, 2 skippeduv run bandit -r personalize/ kratos_clone/ scripts/ app.py --severity-level medium(CI scope) → Medium 0, High 0OPENAI_API_KEYper.env.example;/healthreturns{"status":"ok"};/api/personalize/structurereturns 200 on a tiny briefGenerated by Claude Code
Generated by Claude Code
Summary by CodeRabbit
Documentation
Chores