# 2026-04-13 — Switch JWT signing to RS256 (4096-bit RSA) **Timestamp:** 2026-04-13T05:00:00 ## Summary Replaced symmetric HS256 JWT signing with asymmetric RS256 using a 4096-bit RSA key pair. The private key signs tokens; the public key verifies them. Added `iat` (issued-at) claim to every token and a key-generation helper script. ## Motivation HS256 uses the same secret for signing and verification — if the key leaks, an attacker can forge arbitrary tokens. RS256 with a 4096-bit key eliminates this: the private key never leaves the backend process, and the public key can be distributed safely. ## Files Added / Modified - `scripts/generate_jwt_keys.py` — generates a 4096-bit RSA key pair; outputs single-line PEM values ready to paste into `backend/.env` - `backend/app/core/config.py` — replaced `SECRET_KEY` / `ALGORITHM=HS256` with `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY`, `ALGORITHM=RS256`; added `expand_newlines` validator to handle `\n`-escaped PEM in `.env` - `backend/app/core/security.py` — `create_access_token` now signs with `JWT_PRIVATE_KEY` and includes `iat` claim; `decode_access_token` verifies with `JWT_PUBLIC_KEY` and pins `algorithms=["RS256"]` - `.env.example` — removed `SECRET_KEY`, added `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` placeholders with generation instructions - `.claude/agents/security-auditor.md` — updated JWT checklist: added checks for wrong algorithm (non-RS256), symmetric key usage, and key-from-env requirement; updated policy note - `TODO.md` — added RS256 item to Auth/session security section (checked off)