← Overnight Audit · sample report
microblog · Overnight Audit
This is a public sample report. Forgehaven Labs ran the Overnight Audit process against
miguelgrinberg/microblog, the MIT-licensed companion repo to the Flask Mega-Tutorial, to show what a paying customer receives. This is teaching code and it is excellent at that job. We audit it here as if a developer had forked it toward production, which is exactly how thousands of people use it. Every finding is reproducible from Section 7.
1. The verdict (read this first)
Overall grade: C+ (as production code) / A (as a tutorial) · Clean, idiomatic Flask with proper password hashing and CSRF protection. The gap between "great tutorial" and "safe to deploy" is two things: a weak default secret that, if it survives to production, makes password-reset tokens and session cookies forgeable, and a dependency set frozen in late 2023 where 17 of 54 pinned packages now carry published CVEs, including two HIGH request-smuggling issues in the WSGI server you deploy behind.
If you fix only one thing: guarantee
SECRET_KEYis set from a strong random env var in every deployed environment and make the app refuse to boot without it (Section 3, P0-1). That single change closes an account-takeover path.
2. Scorecard
| Area | Grade | One-line summary |
|---|---|---|
| Security | C | Password hashing + CSRF are correct; the weak SECRET_KEY fallback and an unignored .env are the risks |
| Correctness & error handling | A- | Idiomatic SQLAlchemy 2.0, sensible token expiry, clean auth flow |
| Dependencies & supply chain | D | 17 of 54 pinned deps have CVEs (2 HIGH in gunicorn, RCE-class in Werkzeug debugger) |
| Build, test & CI | B | tests.py present with real unit tests; CI wiring is minimal |
| Code health & maintainability | A | Small, readable, blueprint-structured, well-commented |
| Docs & onboarding | A | README + the tutorial itself are best-in-class |
Verification statement: the CVE list is authoritative OSV.dev data queried live against the exact pinned versions in requirements.txt; the secret-key and .env findings are confirmed against the source and git check-ignore. Section 7 lists every command.
3. Findings
Severity: P0 fix before you ship · P1 fix this month · P2 fix when nearby · P3 note.
P0-1 · Weak fallback SECRET_KEY makes reset tokens and sessions forgeable if it reaches production
- What we found:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'. That same key signs (a) Flask session cookies and (b) the HS256 JWT used for password-reset links (get_reset_password_token/verify_reset_password_token). If the app is ever deployed withoutSECRET_KEYin the environment, the key is public knowledge (it is in the source and in the tutorial), so an attacker can mint a valid reset token for any user id and take over the account, or forge an authenticated session cookie. - Evidence:
config.py:9(fallback);app/models.py:181-190(reset token signed withSECRET_KEY, verified with the same). Chain is: known key → forge{'reset_password': <victim_id>}JWT →GET /reset_password/<token>→ set a new password. - Impact if ignored: Full account takeover of any user, plus session forgery, for any deployment that forgot the env var. This is the single highest-impact issue in the repo.
- Fix: In production config, require the key and fail fast:
SECRET_KEY = os.environ['SECRET_KEY'](or raiseRuntimeErrorif unset in non-dev). Generate withpython -c "import secrets; print(secrets.token_hex(32))". Keep the friendly fallback only for the local tutorial config, clearly separated. - Effort: 30 min.
P1-1 · .env is not git-ignored; the README tells you to put secrets in it
- What we found:
config.pycallsload_dotenv()and readsSECRET_KEY,MAIL_PASSWORD,MS_TRANSLATOR_KEY,DATABASE_URLfrom.env. But.envis absent from.gitignore(which listsvenv,app.db,microblog.log*but not.env). - Evidence:
git check-ignore .env→ no match (not ignored);.gitignorecontents confirm the omission;config.py:1-12reads secrets from.env. - Impact if ignored: A developer who follows the setup and runs
git add .commits real credentials (mail password, translator API key, production secret key) to history. This is the most common way secrets leak from Flask projects. - Fix: Add
.env(and.env.*) to.gitignorenow. Ship a committed.env.examplewith empty values instead. - Effort: 5 min. (If a real secret was ever committed: rotate first, then scrub history.)
P1-2 · Dependencies frozen at late-2023 versions; 17 of 54 now carry published CVEs
- What we found:
requirements.txtpins exact versions that were current around November 2023. Queried live against OSV.dev, 17 of the 54 pinned packages now have advisories. The ones that matter for a deployed app:gunicorn==21.2.0: two HIGH HTTP request-smuggling advisories (GHSA-w3h3-4rj7-4ph4 endpoint-restriction bypass, GHSA-hc5x-x2vx-497g request/response smuggling). This is your production WSGI server. Fixed in 22.0.0+.Werkzeug==3.0.1: six advisories incl. HIGH debugger remote code execution (GHSA-2g68-c3qc-8985),safe_joinbypass on Windows, and form-parsing resource exhaustion.Jinja2==3.1.2: five advisories incl. sandbox breakout via theattrfilter (GHSA-cpwx-vrp4-4pq7).requests==2.31.0:Sessionkeepsverify=Falseafter the first request (GHSA-9wx4-h78v-vm56);urllib3==2.1.0: 7 advisories;PyJWT==2.8.0: 6+ advisories (relevant given the reset-token flow above); pluscertifi,idna,setuptools,Flask,Flask-HTTPAuth,aiosmtpd,Mako,dnspython,Pygments,httpie,python-dotenv.
- Evidence: OSV.dev
querybatchoverrequirements.txton 2026-07-01: 17/54 packages with advisories (full GHSA list captured in Appendix A-1). Severities confirmed via OSV vuln records. - Impact if ignored: The gunicorn smuggling issues are directly exploitable against a deployed instance; the Werkzeug debugger RCE matters if debug mode is ever enabled in a reachable environment. Compounding risk with P0-1 (PyJWT + weak key).
- Fix: Regenerate
requirements.txtagainst current releases (gunicorn>=23,Werkzeug>=3.0.6,Jinja2>=3.1.6,requests>=2.32.4,urllib3>=2.5,PyJWT>=2.10), run the test suite, and add a scheduledpip-audit/Dependabot check so this does not refreeze. - Effort: Half day incl. regression testing.
P2-1 · No session-cookie hardening configured
- What we found: No
SESSION_COOKIE_SECURE,SESSION_COOKIE_HTTPONLY, orSESSION_COOKIE_SAMESITEis set; the app relies entirely on Flask defaults. Over plain HTTP the session cookie ships without theSecureflag. - Evidence: grep across the app for
SESSION_COOKIE*/REMEMBER_COOKIE*returns nothing;config.pysets none. - Impact if ignored: Session cookie can travel over HTTP and is more exposed to interception/CSRF-adjacent issues in a production deployment behind a misconfigured proxy.
- Fix: In production config set
SESSION_COOKIE_SECURE=True,SESSION_COOKIE_HTTPONLY=True,SESSION_COOKIE_SAMESITE='Lax', andREMEMBER_COOKIE_SECURE=True. - Effort: 15 min.
P2-2 · ADMINS still points at the placeholder address
- What we found:
ADMINS = ['[email protected]']. The error-mail handler sends production exception emails to this list. - Evidence:
config.py:20. - Impact if ignored: Production error notifications go to
example.com(nowhere), so a deployed fork silently loses its crash alerts. - Fix: Read
ADMINSfrom an env var; document it in.env.example. - Effort: 10 min.
P3-1 · Things we checked that are NOT problems (so you can stop worrying about them)
md5(self.email)inapp/models.py:141is the Gravatar avatar hash, which Gravatar's API requires. It is not a password or security hash. Not a finding.os.system(...)inapp/cli.pyruns fixedpybabelcommand strings with no user input. Not injectable. Not a finding.- Password storage uses
werkzeug.security.generate_password_hash/check_password_hashcorrectly, and CSRF is on via Flask-WTF. Both correct.
5. The fix plan (next 30 days)
Session 1, 1 h (stop the bleeding): Require SECRET_KEY in production and fail fast (P0-1); add .env to .gitignore and ship .env.example (P1-1).
Session 2, half day (this month): Regenerate requirements.txt against current releases, re-run tests.py, add scheduled dependency scanning (P1-2).
Later / opportunistic: Session-cookie hardening (P2-1); real ADMINS from env (P2-2).
Explicitly fine to ignore: The Gravatar md5 and the os.system babel calls (P3-1).
6. Quick wins (under 30 minutes each)
- Add
.envand.env.*to.gitignore: one line, closes the most common secret-leak path. python -c "import secrets; print(secrets.token_hex(32))"and set it asSECRET_KEYin every deployed env: de-risks P0-1 today even before the code guard lands.- Bump
gunicornto>=23: clears both HIGH request-smuggling CVEs on the server you actually deploy behind.
7. What we checked (methodology & reproducibility)
Fleet + human: an automated agent fleet ran the passes below on a fresh clone of a975ef6; a human engineer reviewed and reproduced every finding.
| Pass | Key commands (run on a fresh clone) |
|---|---|
| Secrets & config review | read config.py; traced SECRET_KEY usage to app/models.py:181-190 |
| Gitignore check | git check-ignore .env (→ not ignored); read .gitignore |
| Dependency scan | OSV.dev POST /v1/querybatch over all 54 pinned versions in requirements.txt |
| Advisory severities | OSV.dev GET /v1/vulns/<GHSA> for the top issues |
| Auth/crypto review | read app/models.py, app/auth/routes.py, app/api/auth.py |
| False-positive triage | verified md5 = Gravatar, os.system = fixed babel strings |
Limits (honest): we did not execute tests.py in-sandbox. This host runs an externally-managed Python with no quick virtualenv path, and the suite pulls heavy optional services (Elasticsearch, Redis/RQ). The dependency findings come from OSV.dev against the exact pins, not from a runtime exploit; the secret-key and .env findings are confirmed from source and git. On a paid engagement we stand up a throwaway venv and run your suite. No penetration test or load test was performed.
8. Appendix: raw evidence
A-1: OSV.dev advisories by pinned package (2026-07-01):
aiosmtpd==1.4.4.post2 GHSA-pr2m-px7j-xg65, GHSA-wgjv-9j3q-jhg8, PYSEC-2024-221
certifi==2023.11.17 GHSA-248v-346w-9cwc, PYSEC-2024-230
dnspython==2.4.2 GHSA-3rq5-2g8h-59hc
Flask==3.0.0 GHSA-68rp-wp8r-4726
Flask-HTTPAuth==4.8.0 GHSA-p44q-vqpr-4xmg
gunicorn==21.2.0 GHSA-hc5x-x2vx-497g (HIGH), GHSA-w3h3-4rj7-4ph4 (HIGH)
httpie==3.2.2 GHSA-8r96-8889-qg2x, PYSEC-2023-242
idna==3.4 GHSA-65pc-fj4g-8rjx, GHSA-jjg7-2v4v-x38h, +2
Jinja2==3.1.2 GHSA-cpwx-vrp4-4pq7, +4
Mako==1.3.0 GHSA-2h4p-vjrc-8xpq, +2
Pygments==2.17.1 GHSA-5239-wwwm-4pmq
PyJWT==2.8.0 GHSA-752w-5fwx-jx9f, +10
python-dotenv==1.0.0 GHSA-mf9w-mj56-hr94
requests==2.31.0 GHSA-9wx4-h78v-vm56, +2
setuptools==68.2.2 GHSA-5rjg-fvgr-3xxf, GHSA-cx63-2mw6-8hw5, PYSEC-2025-49
urllib3==2.1.0 GHSA-2xpw-w6gg-jr37, +6
Werkzeug==3.0.1 GHSA-2g68-c3qc-8985 (HIGH debugger RCE), +5
Forgehaven Labs LLC · Overnight Audit · fixed price, 24-hour turnaround.
Want this run against your repo? forgehavenlabs.com/audit