v1.1.0:8 — admin-gate whole-DB routes + AI custom-URL providers; SSRF guard
Multi-user authorization hardening from a full security evaluation (EVALUATION.md):
- P0: /api/settings/{export,import}-db are now admin-only. Previously any signed-in user could download the whole instance DB (all bcrypt hashes + plaintext AI keys) or replace it wholesale. Per-user CSV export/import stays open.
- AI custom-URL providers (Ollama, OpenAI-compatible) are now admin-only, and every server fetch to a user-supplied URL passes through assertSafeProviderUrl (blocks link-local/cloud-metadata; private LAN allowed by design). Fixed-URL cloud providers stay per-user. Removed the dead legacy /api/ai/config route.
- Dev: fixed broken quick-start (added npm run create-admin; rewrote README; dropped dead CLAUDE_API_KEY) and the export-db 0-byte path resolution (resolveDatabasePath now matches Prisma).
ExVer bumped to 1.1.0:8 (no schema/data migration). Tests 197 pass, build green, tsc clean.
This commit is contained in:
@@ -47,6 +47,8 @@ npx vitest run tests/ai-pricing.test.ts # single file
|
||||
npx vitest run -t "findPrice" # single test by name
|
||||
npx tsc --noEmit # typecheck only
|
||||
npx prisma generate # REQUIRED after editing schema.prisma (else TS can't see new fields)
|
||||
npm run db:seed # seed InstanceSettings singleton (NO users/library — see below)
|
||||
npm run create-admin -- you@example.com pw "Name" # create first admin locally (--force resets existing)
|
||||
```
|
||||
|
||||
Build/sideload the s9pk (from `start9/0.4/`): `make x86` then `make install`. Targets come from `s9pk.mk` (the wrapper `Makefile` just sets `ARCHES := x86`):
|
||||
@@ -60,7 +62,7 @@ Both `install` and `publish` read host/registry config from `~/.startos/config.y
|
||||
|
||||
Canonical publish path for this project: `~/.proof-of-work/publish.sh` (builds, uploads to FileBrowser, registers) — separate from the generic `make publish`. Unpublish: `~/.proof-of-work/unpublish.sh`.
|
||||
|
||||
`npm run db:seed` (= `tsx prisma/seed.ts`) seeds the `InstanceSettings` singleton + curated library; it is **live, not dead** — invoked at Docker image-build time (`start9/0.4/Dockerfile`) to bake the library into the image, and also the local-dev first-run path (`proof-of-work/README.md`). Runtime first-boot/upgrade seeding is handled separately by `docker_entrypoint.sh`.
|
||||
`npm run db:seed` (= `tsx prisma/seed.ts`) seeds **only the `InstanceSettings` singleton** — deliberately NO users and NO curated library (the library attaches at admin-creation and via the boot-time ensure). It is **live, not dead** — invoked at Docker image-build time (`start9/0.4/Dockerfile`) and the local-dev first-run path. `npm run create-admin` (= `tsx scripts/create-admin.ts`) is the local-dev equivalent of the StartOS "Set admin credentials" action: creates the first admin + seeds their library; `--force` to reset/promote an existing account. Runtime first-boot/upgrade seeding is handled separately by `docker_entrypoint.sh`.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -69,6 +71,7 @@ Canonical publish path for this project: `~/.proof-of-work/publish.sh` (builds,
|
||||
- **Schema changes are additive ALTERs in `docker_entrypoint.sh`**, guarded by `PRAGMA table_info` checks. Keep `schema.prisma` in sync as the mirror, but the entrypoint is what migrates live `/data`. Never write a destructive migration.
|
||||
- **Commit subject** = `vX.Y.Z:N — short summary`, imperative, body explains the *why*.
|
||||
- **Git remote is self-hosted** (a private Start9 registry + a FileBrowser artifact host), NOT GitHub. The actual registry/file-host URLs are constants in `~/.proof-of-work/{publish,unpublish}.sh`; FileBrowser creds live in `~/.keysat/filebrowser.env` (outside the repo, gitignored). Default branch is `master`.
|
||||
- **Authorization tiers**: whole-instance routes (`settings/{export,import}-db`) are **admin-only** (`!user.isAdmin → 403`); per-user data routes scope by `user.id`. Custom-URL AI providers (Ollama, OpenAI-compatible — anything with `requiresBaseUrl`) are **admin-only** (SSRF surface); fixed-URL cloud providers (claude/openai/gemini) stay per-user. Gate the server route AND hide the control in the UI.
|
||||
- Tests live in `proof-of-work/tests/`; mock server-action deps with `vi.hoisted()` + `vi.mock`.
|
||||
- **Before editing the AI subsystem (`proof-of-work/lib/ai/**` or the generate/generations routes), read `docs/guides/ai-subsystem.md`** — provider abstraction, SSE/lenient-JSON, pricing/model menus, and the background-runner architecture live there.
|
||||
|
||||
@@ -93,18 +96,18 @@ Canonical publish path for this project: `~/.proof-of-work/publish.sh` (builds,
|
||||
|
||||
## Current state
|
||||
|
||||
Latest version is **1.1.0:7** (built locally, installed on the StartOS server). The registry is currently **empty** — all versions were unpublished; nothing is downloadable until `publish.sh` runs again.
|
||||
Latest version is **1.1.0:8** — **built and sideloaded** to the StartOS server (2026-06-13). Registry is empty and **publishing is parked** (sideload-only via `make install`).
|
||||
|
||||
Working: workout logging, programs (manual + AI), multi-user, curated library, full AI subsystem (5 providers, multi-config, background generation, history detail, cost/duration, Ollama auto-detect, infinite-scroll exercise history).
|
||||
|
||||
In progress: none — repo is at a clean checkpoint.
|
||||
Done this session (2026-06-13 full-eval security batch, on `master`): **P0** whole-instance DB export/import now admin-only (+UI +test); **P1** SSRF guard (`lib/ai/safeUrl.ts`, allows private-LAN by design) + custom-URL AI providers made admin-only + dead legacy `ai/config` route removed; **P1** dev quick-start fixed (`npm run create-admin`, README, `.env.example`); **P1** `export-db` 0-byte dev bug (`resolveDatabasePath` now matches Prisma). Full report in `EVALUATION.md`. Tests **197 pass**, build green, tsc clean. Secrets decision: no at-rest encryption (can't protect users from the operator — structural; threat model stands).
|
||||
|
||||
Decided but not implemented: tiered AI prompt formatting — JSON-Schema enforcement (Ollama `format` / OpenAI `response_format`), pipe-separated library, XML-tagged sections, Ollama-only few-shot. Targets local-model output quality; would ship as 1.1.0:8.
|
||||
In progress: none.
|
||||
|
||||
Git remote: `origin` → self-hosted Gitea at `ssh://git@immense-voyage.local:59916/grant/proof-of-work.git`; `master` is pushed and tracking. (The `~/.proof-of-work/{publish,unpublish}.sh` registry/FileBrowser hosts are separate from this code remote.)
|
||||
Next steps (priority order):
|
||||
1. **Next.js 14→15 major bump** (the remaining P1 — CVEs) as its own tested change. Then the P2/P3 hardening backlog → see `ROADMAP.md` → Security & hardening.
|
||||
2. Tiered AI prompt formatting (`ROADMAP.md` → AI quality) once the security queue is clear.
|
||||
|
||||
Known issues: publish.sh Step 3 (registry register) silently no-op'd on 1.1.0:6 and :7 — uploaded the file but didn't register; investigate before relying on those versions appearing in the registry. (The `Co-Authored-By` trailer scrub on `8f149d3`–`5b0535f` is **done** — history was rewritten and force-pushed; those SHAs are now stale.)
|
||||
Open/parked: `publish.sh` Step-3 registry-register silent no-op on 1.1.0:6/:7 (parked with publishing). Community-registry submission has 4 blockers (see `ROADMAP.md` → Packaging).
|
||||
|
||||
Next steps:
|
||||
1. Re-publish current version once the Step-3 registry-register failure is diagnosed.
|
||||
2. Implement the tiered AI prompt formatting (1.1.0:8).
|
||||
Git remote: `origin` → self-hosted Gitea at `ssh://git@immense-voyage.local:59916/grant/proof-of-work.git`; `master` tracks it. (`~/.proof-of-work/{publish,unpublish}.sh` registry/FileBrowser hosts are separate from this code remote.)
|
||||
|
||||
Reference in New Issue
Block a user