Files
Ten31-Portal/deploy/startos/install/versions/index.ts
T
Jonathan KirkwoodandClaude Opus 4.8 69f12b0519 0.2.26: security hardening from audit (P2/P3 fixes)
Address the security-auditor findings the user selected (items 2, 3, 4):

Default admin credentials (P2): remove the fixed `Ten31` default. First
boot now generates a strong random admin password (secrets.token_urlsafe),
records it 0600 at /data/.admin-password, and surfaces it once via a new
"Show Initial Admin Password" StartOS action (CLI `show-admin-password`).
The stored password is cleared when the admin is reset (CLI reset-password)
or self-changes it (change-password endpoint).

Login hardening (P2): add a per-IP in-memory sliding-window rate limiter
(10 failures / 5 min -> 429 + Retry-After) in ratelimit.py; run a dummy
argon2 verify when the user is unknown so timing can't enumerate usernames;
keep a single generic 401 for unknown-user and wrong-password.

Hardening (P3): server process now runs unprivileged -- Dockerfile adds
uid 10001 appuser; start.sh (still root) chowns the mounted /data then
drops via `setpriv` before exec'ing uvicorn. Spreadsheet imports are
size-capped via storage.read_capped (413 past MAX_UPLOAD_SIZE) in the
schedule, capital preview, and batch paths. batch_import no longer returns
raw exception text (generic per-file messages).

Verified in the packed amd64 container: PID1 uvicorn runs as uid 10001,
/data owned 10001 with 0600 secrets; generated admin password retrievable
via CLI and logs in (200); 11th bad login -> 429; admin reset clears the
stored password. Tests: test_auth_hardening.py (4). Full suite 21 passed;
frontend tsc + StartOS bundle clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:09:27 -05:00

29 lines
1.3 KiB
TypeScript

export { v_0_2_26 as current } from './v_0_2_26'
import { v_0_1_0 } from './v_0_1_0'
import { v_0_2_0 } from './v_0_2_0'
import { v_0_2_1 } from './v_0_2_1'
import { v_0_2_3 } from './v_0_2_3'
import { v_0_2_4 } from './v_0_2_4'
import { v_0_2_5 } from './v_0_2_5'
import { v_0_2_6 } from './v_0_2_6'
import { v_0_2_7 } from './v_0_2_7'
import { v_0_2_8 } from './v_0_2_8'
import { v_0_2_9 } from './v_0_2_9'
import { v_0_2_10 } from './v_0_2_10'
import { v_0_2_11 } from './v_0_2_11'
import { v_0_2_12 } from './v_0_2_12'
import { v_0_2_13 } from './v_0_2_13'
import { v_0_2_14 } from './v_0_2_14'
import { v_0_2_15 } from './v_0_2_15'
import { v_0_2_16 } from './v_0_2_16'
import { v_0_2_17 } from './v_0_2_17'
import { v_0_2_18 } from './v_0_2_18'
import { v_0_2_19 } from './v_0_2_19'
import { v_0_2_20 } from './v_0_2_20'
import { v_0_2_21 } from './v_0_2_21'
import { v_0_2_22 } from './v_0_2_22'
import { v_0_2_23 } from './v_0_2_23'
import { v_0_2_24 } from './v_0_2_24'
import { v_0_2_25 } from './v_0_2_25'
export const other = [v_0_1_0, v_0_2_0, v_0_2_1, v_0_2_3, v_0_2_4, v_0_2_5, v_0_2_6, v_0_2_7, v_0_2_8, v_0_2_9, v_0_2_10, v_0_2_11, v_0_2_12, v_0_2_13, v_0_2_14, v_0_2_15, v_0_2_16, v_0_2_17, v_0_2_18, v_0_2_19, v_0_2_20, v_0_2_21, v_0_2_22, v_0_2_23, v_0_2_24, v_0_2_25]