# A04: Cryptographic Failures Last updated: March 1, 2026 ## Findings addressed 1. Production origin could be configured as non-HTTPS. 2. JWT configuration did not explicitly constrain issuer/audience/algorithm. 3. Missing explicit env contract for JWT issuer/audience values. ## Fixes implemented 1. Production HTTPS origin enforcement: - Added production guard requiring `APP_ORIGIN` to start with `https://`. 2. JWT hardening in Fastify: - Explicit signing algorithm set to `HS256`. - JWT signing includes configured `iss` and `aud`. - JWT verification enforces: - allowed algorithm (`HS256`) - allowed issuer - allowed audience 3. Env schema expanded: - Added `JWT_ISSUER` and `JWT_AUDIENCE` with secure defaults: - `skymoney-api` - `skymoney-web` ## Files changed 1. `api/src/env.ts` 2. `api/src/server.ts` 3. `.env.example` 4. `api/tests/cryptographic-failures.test.ts` 5. `api/tests/cryptographic-failures.runtime.test.ts` 6. `api/vitest.security.config.ts` ## Verification Commands: ```bash cd api npx vitest run -c vitest.security.config.ts tests/cryptographic-failures.test.ts tests/cryptographic-failures.runtime.test.ts ``` Verified output: - Test Files: `2 passed (2)` - Tests: `7 passed (7)` Dedicated A04 checks in `cryptographic-failures.test.ts`: 1. Production env rejects non-HTTPS `APP_ORIGIN`. 2. JWT issuer/audience defaults resolve as expected. 3. Fastify JWT plugin is registered with explicit algorithm + issuer + audience constraints. Runtime adversarial checks in `cryptographic-failures.runtime.test.ts`: 1. Token with invalid issuer is rejected (`401`). 2. Token with invalid audience is rejected (`401`). 3. Unsigned token (`alg=none`) is rejected (`401`). 4. Token with valid signature + expected issuer/audience is accepted on protected auth refresh route. ## Required production env values 1. `APP_ORIGIN=https://...` 2. `JWT_SECRET` strong 32+ chars 3. `COOKIE_SECRET` strong 32+ chars 4. `JWT_ISSUER=skymoney-api` (or your approved value) 5. `JWT_AUDIENCE=skymoney-web` (or your approved value)