Skip to content

fix(security): fail closed on default/weak SECRET_KEY and stop shipping .env - #2513

Closed
failsafesecurity wants to merge 2 commits into
fastapi:masterfrom
failsafesecurity:fix/fail-closed-secret-key
Closed

failsafesecurity wants to merge 2 commits into
fastapi:masterfrom
failsafesecurity:fix/fail-closed-secret-key

Conversation

@failsafesecurity

Copy link
Copy Markdown

Summary

Fixes the shipped-default-secret vulnerability that allows forgery of any JWT (including superuser password-reset tokens).

Finding: Shipped default SECRET_KEY with fail-open validation permits forgery of any JWT, including password-reset tokens for the superuser
Severity: CRITICAL — CVSS 4.0: 9.3 AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
CWE: CWE-321 · OWASP: A04:2025 Cryptographic Failures
Scan ID: cmv17pczu00womo01ei5nap4f (project cmv17pcvt00wmmo010afycocp)

Root cause

backend/app/core/config.py::_check_default_secret only raised for SECRET_KEY == "changethis" when FASTAPI_ENV != "development"; in development it merely warned. The repository shipped .env with FASTAPI_ENV=development and SECRET_KEY=changethis, and compose.override.yml forces FASTAPI_ENV: "development". The same HS256 key signs both access tokens (backend/app/core/security.py) and password-reset tokens (backend/app/utils.py), and backend/app/core/db.py creates the built-in superuser from shipped defaults — so anyone who copies the public template can mint a token for any user id/email and take over the superuser.

PoC summary

  1. docker compose up → backend loads SECRET_KEY=changethis with FASTAPI_ENV=development.
  2. Forge a reset token for the superuser:
    python3 -c 'import jwt,time; print(jwt.encode({"exp":time.time()+3600,"nbf":time.time(),"sub":"admin@example.com"},"changethis",algorithm="HS256"))'
  3. POST /api/v1/reset-password/ with the forged token and a new password → 200 Password updated successfully.
  4. Log in as admin@example.com, call a superuser-only endpoint (e.g. GET /api/v1/users/) → full admin access.

Fix explanation

  • Fail closed everywhere. _check_default_secret now raises unconditionally; the development warning path is removed, so the app refuses to start on a placeholder secret in every environment.
  • Enforce key strength. Startup rejects a SECRET_KEY shorter than 32 characters (MINIMUM_SECRET_KEY_LENGTH).
  • Stop shipping secrets. .env is removed from version control and added to .gitignore and .dockerignore; .env.example documents every variable with empty placeholders.
  • Safe local/CI bootstrap. scripts/generate-env.sh generates strong random SECRET_KEY, FIRST_SUPERUSER_PASSWORD, and POSTGRES_PASSWORD values; it is wired into the backend, docker-compose, and playwright CI workflows so CI still boots.
  • Regression test. backend/tests/test_config_security.py asserts placeholders and short keys are rejected in every environment.
  • Docs (development.md) updated to describe the untracked .env and the generator.

Notes

This is a defensive hardening change and does not change application behavior when a properly generated secret is configured. The companion test PR exercises these checks in CI.

failsafesecurity and others added 2 commits October 9, 2026 17:12
…ng .env

The template shipped FASTAPI_ENV=development together with SECRET_KEY=changethis,
and the placeholder check only warned in development. Because the same HS256 key
signs and verifies access tokens and password-reset tokens, a shipped default key
let anyone forge a JWT for any user, including the built-in superuser
(CWE-321, CVSS 9.3).

- Make _check_default_secret raise unconditionally (all environments); remove the
  development warning path so the app refuses to start on a placeholder secret.
- Enforce a minimum SECRET_KEY length (32 chars).
- Stop tracking .env and add it to .gitignore/.dockerignore; add .env.example.
- Add scripts/generate-env.sh to generate strong random secrets for local dev/CI.
- Wire the generator into the backend, docker-compose, and playwright workflows.
- Update development.md and add a fail-closed config regression test.
@github-actions

Copy link
Copy Markdown
Contributor

This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI.

@github-actions github-actions Bot closed this Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants