# Phase 7.3: Security — ES256 Algorithm Upgrade - Discussion Log > **Audit trail only.** Do not use as input to planning, research, or execution agents. > Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. **Date:** 2026-06-05 **Phase:** 07.3-security-es256-algorithm-upgrade-inserted **Areas discussed:** Key format in env vars, Startup refresh-token rotation, Key generation workflow, JWT TTL / Remember Me --- ## Key Format in Env Vars ### Q1: How should P-256 PEM keys be stored in env vars? | Option | Description | Selected | |--------|-------------|----------| | Base64-encoded | Single line, shell-safe, decoded in config.py | ✓ | | Escaped `\n` in .env | python-dotenv expands `\n`; loader-dependent | | | Multiline .env block | Breaks in docker-compose environment: block | | **User's choice:** Base64-encoded **Notes:** Standard 12-factor approach; no loader ambiguity. --- ### Q2: Both keys required, or derive public from private at startup? | Option | Description | Selected | |--------|-------------|----------| | Both required env vars | Clean separation; workers can hold public key only | ✓ | | Private key only — derive public | Fewer env vars but more magic in config.py | | **User's choice:** Both required env vars --- ### Q3: What happens to SECRET_KEY after ES256? | Option | Description | Selected | |--------|-------------|----------| | Remove from JWT code | SECRET_KEY no longer used for signing; stays in env for future HMAC | ✓ | | Repurpose as HMAC key for Phase 7.4 | Forward-compatible but couples phases | | **User's choice:** Remove SECRET_KEY from JWT code --- ## Startup Refresh-Token Rotation ### Q1: How to implement startup rotation? | Option | Description | Selected | |--------|-------------|----------| | DELETE all refresh_tokens | Simple, one-shot; no schema change | | | Bulk revoke (set revoked=True) | Cleaner audit trail; same user impact | ✓ | | Let natural expiry handle it | Doesn't satisfy ROADMAP requirement | | **User's choice:** Bulk revoke (set revoked=True) --- ### Q2: How to detect "first boot after ES256 migration"? | Option | Description | Selected | |--------|-------------|----------| | Check system_settings table | Stores `jwt_algorithm`; idempotent on restart | ✓ | | New env var JWT_TOKEN_GENERATION=N | Simple but requires operator bump on deploy | | | One-time Alembic migration | Runs at migrate time, not startup | | **User's choice:** Check system_settings table (`jwt_algorithm` key) --- ## Key Generation Workflow ### Q1: How should developers generate the key pair? | Option | Description | Selected | |--------|-------------|----------| | Python one-liner in README | Uses existing `cryptography` dep; no extra tooling | ✓ | | openssl CLI command | Familiar to ops but multi-step, requires openssl | | | Auto-generate on first boot | Convenient for dev; dangerous in prod (ephemeral keys) | | **User's choice:** Python one-liner documented in README --- ### Q2: docker-compose key placeholder vs. .env reference? | Option | Description | Selected | |--------|-------------|----------| | .env file reference only | Follows existing ${SECRET_KEY} pattern | ✓ | | Placeholder comment in docker-compose | Helpful but could mislead | | **User's choice:** .env file reference only --- ## JWT TTL / Remember Me ### Q1 (freeform): What token should default to 12 hours? User note: "JWT Tokens should only be valid for 12 hours and the user can opt-in to create a token which is valid for 30 days." Asked for clarification on whether this applied to the access token or refresh token. User asked: "What do you think is the most secure way to handle tokens?" **Claude's recommendation:** Keep access token at 15 min (CLAUDE.md requirement). Change refresh token default to 16–24 hours with explicit "remember me" opt-in for 30 days. **User's decision:** 16 hours — covers a full workday; guarantees session is revoked overnight. --- ### Q2: Fold "remember me" into Phase 7.3 or separate phase? | Option | Description | Selected | |--------|-------------|----------| | Fold into Phase 7.3 | Already touching token creation code; incremental work | ✓ | | Separate phase 7.5 | Keeps 7.3 purely as ES256 upgrade | | **User's choice:** Fold into Phase 7.3 --- ## Claude's Discretion - PyJWT API form: `jwt.encode(payload, pem_str, algorithm="ES256")` — PEM strings passed directly, no key object conversion needed - Bulk revoke via raw SQL `UPDATE` rather than ORM row-by-row — efficiency decision - "Stay signed in for 30 days" as checkbox label (clearer than "Remember me") - system_settings key name: `jwt_algorithm`, value `"ES256"` ## Deferred Ideas - **Phase 7.4:** Token fingerprinting / token binding (`fgp` claim = HMAC of User-Agent + Accept-Language) - **Key rotation ceremony:** Dual-key overlap process for rotating P-256 keys in production — deferred until production hardening milestone