fix(config): refuse to boot with a dev-default JWT_SECRET outside dev/test (PMS-497) #370

Merged
nrupard merged 4 commits from fix/PMS-497-jwt-secret-production-guard into main 2026-06-29 21:34:41 +02:00
Member

JWT_SECRET fell back to the hardcoded "development-secret-change-in-production" sentinel with no production guard, so a staging/production deploy that forgot to set it would sign session cookies with a publicly known key and let anyone forge sessions.

AppConfig::from_env now calls a new validate_jwt_secret guard that, for any ENVIRONMENT not in {development,dev,test}, returns Err (the server refuses to boot via the existing .expect at startup, consistent with the fail-loud treatment of ENCRYPTION_KEY/SMTP/CORS/migrations) when JWT_SECRET is unset, equals the built-in dev sentinel, or is shorter than 32 bytes. is_production() is wired into the guard as the strictest-environment label so the previously dead method now gates the secret. Dev/test keep the convenience default unchanged.

Adds unit tests covering dev/test pass-through, production rejection of unset/sentinel/too-short secrets, production acceptance of a strong secret, and staging fail-safe. Documents the requirement in .env.example.

#PMS-497

JWT_SECRET fell back to the hardcoded "development-secret-change-in-production" sentinel with no production guard, so a staging/production deploy that forgot to set it would sign session cookies with a publicly known key and let anyone forge sessions. AppConfig::from_env now calls a new validate_jwt_secret guard that, for any ENVIRONMENT not in {development,dev,test}, returns Err (the server refuses to boot via the existing .expect at startup, consistent with the fail-loud treatment of ENCRYPTION_KEY/SMTP/CORS/migrations) when JWT_SECRET is unset, equals the built-in dev sentinel, or is shorter than 32 bytes. is_production() is wired into the guard as the strictest-environment label so the previously dead method now gates the secret. Dev/test keep the convenience default unchanged. Adds unit tests covering dev/test pass-through, production rejection of unset/sentinel/too-short secrets, production acceptance of a strong secret, and staging fail-safe. Documents the requirement in .env.example. #PMS-497
fix(config): refuse to boot with a dev-default JWT_SECRET outside dev/test (PMS-497)
Some checks failed
E2E / Playwright against staging (pull_request) Failing after 31s
Integration / integration tests (pull_request) Failing after 48s
Check / fmt + clippy + build + tests (pull_request) Successful in 1m7s
e6089c94a1
JWT_SECRET fell back to the hardcoded "development-secret-change-in-production" sentinel with no production guard, so a staging/production deploy that forgot to set it would sign session cookies with a publicly known key and let anyone forge sessions.

AppConfig::from_env now calls a new validate_jwt_secret guard that, for any ENVIRONMENT not in {development,dev,test}, returns Err (the server refuses to boot via the existing .expect at startup, consistent with the fail-loud treatment of ENCRYPTION_KEY/SMTP/CORS/migrations) when JWT_SECRET is unset, equals the built-in dev sentinel, or is shorter than 32 bytes. is_production() is wired into the guard as the strictest-environment label so the previously dead method now gates the secret. Dev/test keep the convenience default unchanged.

Adds unit tests covering dev/test pass-through, production rejection of unset/sentinel/too-short secrets, production acceptance of a strong secret, and staging fail-safe. Documents the requirement in .env.example.

#PMS-497
Merge remote-tracking branch 'origin/main' into fix/PMS-497-jwt-secret-production-guard
Some checks failed
Check / fmt + clippy + build + tests (pull_request) Failing after 20s
Integration / integration tests (pull_request) Failing after 1m2s
E2E / Playwright against staging (pull_request) Failing after 2m54s
f7091aef79
# Conflicts:
#	.env.example
#	src/main.rs
fix(config): repair merge tail + drop superseded validate_jwt_secret (PMS-497)
Some checks failed
Check / fmt + clippy + build + tests (pull_request) Successful in 1m41s
Integration / integration tests (pull_request) Successful in 11m53s
E2E / Playwright against staging (pull_request) Failing after 2m51s
508fceb55f
The conflict-resolution commit captured a stale staged copy: the from_env
struct close was left as the old `let config = Self {...};` tail with a dead
`config.validate_jwt_secret(...)` call and the orphaned method, clashing with
main's `Ok(Self {...})`. Close with `})`, drop the dead method (PMS-499's
resolve_secret supersedes it); the PMS-497 contribution is the retained
check_jwt_secret_len guard.

#PMS-497
Merge branch 'main' into fix/PMS-497-jwt-secret-production-guard
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 4m48s
E2E / Playwright against staging (pull_request) Successful in 1m22s
Create release / Gate (release-branch merges only) (pull_request) Successful in 3s
Create release / Create release from merged PR (pull_request) Has been skipped
Integration / integration tests (pull_request) Successful in 15m32s
7aa5e68c92
nrupard deleted branch fix/PMS-497-jwt-secret-production-guard 2026-06-29 21:34:41 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!370
No description provided.