Phases 4-8: admin backend, storage driver, local LLM, Docker, polish

↗ view on GitHub · Claude · 2026-05-15 · 9166a01d

Closes audit findings C1, H4 (sandboxing follow-up parked), H6, M2.

Phase 4 - admin backend.
  - routes/admin.ts mounted at /admin/*, gated by requireAuth +
    requireAdmin.
  - GET / PATCH / DELETE /admin/users (last-admin protection;
    self-mutation refused).
  - GET / PATCH /admin/org-settings with typed validation per
    field; audit_events records every change.
  - GET /admin/audit paginated; optional action and user_id
    filters.
  - Frontend admin pages land later; the API is complete.

Phase 5 - storage driver + at-rest encryption + download tokens.
  - lib/storageLocal.ts: local driver. Each file on disk is
    [12-byte IV][16-byte GCM tag][ciphertext]. Master key is
    SHA-256(STORAGE_ENCRYPTION_KEY). Path traversal: resolved
    absolute path must stay under STORAGE_LOCAL_PATH.
  - lib/storage.ts becomes a facade keyed on STORAGE_DRIVER:
    local (default for Docker) or s3 (existing R2 / MinIO).
    Signed URLs return null in local mode; callers fall back to
    /download/:token.
  - End-to-end verified: 25-byte plaintext -> 53-byte on-disk
    file; plaintext does not appear; round-trip preserved.
  - lib/downloadTokens.ts payload adds u (user_id) + exp;
    /download/:token refuses tokens issued to a different user
    or past expiry. TTL defaults to 24h, env-tunable. Closes C1.

Phase 6 - local LLM + EXTERNAL_AI_DISABLED.
  - lib/llm/local.ts: OpenAI-compatible adapter for Ollama /
    vLLM / LM Studio. Streaming + completion. Models prefixed
    "local/" route here; suffix sent verbatim as the upstream
    model id. Tools not yet wired (follow-up).
  - lib/llm/index.ts adds assertProviderAllowed: when
    EXTERNAL_AI_DISABLED=true, refuses Claude/Gemini/OpenAI
    dispatch. Local always passes.
  - Provider enum extended to include "local".
  - tabular.ts missingModelApiKey skips the check for local
    models (gated by LOCAL_LLM_BASE_URL on the backend).

Phase 7 - Docker / Caddy / deployment.
  - backend/Dockerfile multi-stage (deps -> tsc build -> slim
    runtime). LibreOffice baked in. Non-root user. Entrypoint
    runs `npm run migrate` (toggle via MIGRATE_ON_BOOT) then
    starts the server.
  - frontend/Dockerfile multi-stage. next.config.ts:
    output: "standalone".
  - docker-compose.yml at repo root. Services:
      postgres, backend, frontend, ollama, caddy, backup.
    Every persistent volume mounts under ${DATA_ROOT}. Backend
    healthcheck uses /ready. Backup sidecar runs nightly
    pg_dump with BACKUP_KEEP_DAYS retention.
  - caddy/Caddyfile terminates TLS via Let's Encrypt for
    ${PIP_DOMAIN}; forwards backend paths to backend:3001 and
    everything else to frontend:3000.
  - .env.compose.example documents every required and optional
    var.

Phase 8 - rate limits + structure_tree sanitisation.
  - rate-limit keyGenerator keys on res.locals.userId for chat,
    chat-create, and upload paths; pre-auth requests still fall
    back to IP. Closes H6.
  - sanitiseStructureTitle strips control chars, escapes angle
    brackets, caps length. Applied to PDF outline titles and
    DOCX mammoth-extracted lines before storage. Closes M2.

Type-check clean across the backend. All 16 migrations apply
cleanly. Storage driver smoke-tested.

Remaining outstanding (post-MVP polish):
  - Frontend workspace switcher + workspace settings page.
  - Frontend admin pages (Users / AI Policy / Audit).
  - Frontend Account page polish for the new per-user fields.
  - Tool support on the local LLM adapter so edit / generate /
    read flows work with Ollama-class models.
Repository cpatpa/PIP
Author Claude <noreply@anthropic.com>
Authored
Parents 510d7e86
Stats 24 files changed , +1349 , -108
Part of Phases 4-8 - admin backend, local storage driver, local LLM, Docker, admin/account frontend

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-9166a01d.md from inside the repo you want the change in.

⬇ Download capture-commit-9166a01d.md