Files
proof-of-work/start9/0.4/startos/versions/v1.2.0.3.ts
T
Keysat f540a473ef
CI / proof-of-work (Next.js app) (push) Has been cancelled
CI / start9/0.4 (StartOS package code) (push) Has been cancelled
v1.2.0:3 — close login timing oracle, enforce exerciseId ownership on workout writes
Two P3 multi-user hardening fixes from the 2026-06-13 full-eval.

Login timing oracle: both login paths (the UI server action and
POST /api/auth) returned immediately on an unknown email but ran
bcrypt.compare when the email matched a user, so response latency
revealed which emails have accounts. New verifyPasswordOrDummy() in
lib/auth runs bcrypt against a fixed dummy hash when there is no user,
so every attempt spends exactly one bcrypt; the two error branches in
each route collapse into one.

exerciseId ownership: exercises are per-user, but the workout
create / PATCH (set-replace) / add-sets and CSV import-save routes wrote
SetLogs from a client-supplied exerciseId with no ownership check —
letting a user attach another user's exercise to their own workout,
which leaks that exercise's name/notes on fetch and wires up a
cross-user onDelete: Cascade link. All four now reject unowned ids with
400 via the shared lib/exerciseOwnership helper; the pre-existing inline
checks in both programs routes are refactored onto the same helper.

App-code only — no schema, no API contract change, no data migration.
2026-06-15 18:30:08 -05:00

36 lines
1.6 KiB
TypeScript

import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
/**
* v1.2.0:3 — P3 hardening: login timing oracle + exerciseId ownership (2026-06-15).
*
* Two multi-user hardening fixes from the 2026-06-13 full-eval P3 batch:
*
* 1. Login timing oracle. Both login paths (the UI server action and
* POST /api/auth) returned immediately when no user matched the email,
* but ran bcrypt.compare when one did — so response latency revealed
* which emails have accounts. Now an unknown email is compared against
* a fixed dummy hash (lib/auth verifyPasswordOrDummy), so every attempt
* spends one bcrypt regardless.
*
* 2. exerciseId ownership. Exercises are per-user, but the workout
* create/PATCH/add-sets and CSV-import-save routes wrote SetLogs from a
* client-supplied exerciseId without checking ownership — letting a user
* attach another user's exercise to their own workout (leaking its
* name/notes on fetch + a cross-user cascade-delete link). All four now
* reject unowned ids with 400 via the shared lib/exerciseOwnership
* helper (the same check programs-create already did, now centralized).
*
* App-code only — no schema, no API contract change, no data migration.
*/
export const v_1_2_0_3 = VersionInfo.of({
version: '1.2.0:3',
releaseNotes: {
en_US:
'Security hardening: login no longer leaks (via response timing) whether an email has an account, and workouts can only reference exercises from your own library. No data changes.',
},
migrations: {
up: async () => {},
down: IMPOSSIBLE,
},
})