Files
platform/codex.txt
Yusuf Suleman 6023ebf9d0 feat: tasks app, security hardening, mobile fixes, iOS app shell
- Custom SQLite task manager replacing TickTick wrapper
- 73 tasks migrated from TickTick across 15 projects
- RRULE recurrence engine with lazy materialization
- Dashboard tasks widget (desktop sidebar + mobile card)
- Tasks page with project tabs, add/edit/complete/delete
- Security: locked ports to localhost, removed old containers
- Gitea Actions runner configured and all 3 CI jobs passing
- Fixed mobile overflow on dashboard cards
- iOS Capacitor app shell (Second Brain)
- Frontend/backend guide docs for adding new services
- TickTick Google Calendar sync re-authorized

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:35:57 -05:00

230 lines
10 KiB
Plaintext

Platform Codex — Full Build & Remediation Log
================================================
Date: 2026-03-29
PLATFORM OVERVIEW
=================
A premium SaaS-style personal dashboard integrating self-hosted services:
- Budget (Actual Budget)
- Inventory (NocoDB)
- Fitness (SparkyFitness)
- Trips
- Reader (Miniflux)
- Media (Shelfmark/Spotizerr/Booklore)
Stack: SvelteKit (Svelte 5 runes) + Python gateway + Express.js services
Orchestration: Docker Compose, 6 containers
Reverse proxy: Pangolin at dash.quadjourney.com
Gitea repo: gate.quadjourney.com/yusiboyz/platform
WHAT WAS BUILT
==============
1. Design System
- Unified CSS token system: spacing (--sp-*), radius (--radius-*),
shadows (--shadow-*), colors, typography (--text-*)
- Migrated 235 raw values across 31 files
- Documented in frontend-v2/DESIGN_SYSTEM.md
2. Gateway Modular Refactor
- Split 1878-line server.py into 15 modular files:
server.py (thin router), config.py, database.py, auth.py, proxy.py,
dashboard.py, plus integrations/ directory (booklore, kindle, image_proxy,
qbittorrent, etc.)
- ThreadingHTTPServer for concurrent requests
- 30-second per-user dashboard cache
3. Fitness API Integration
- Replaced all mock data with real SparkyFitness backend API calls
- AI food input with multi-item splitting (/api/fitness/foods/split)
- Canonical food storage with dedup (naive singularize + pre-creation check)
- Entry editing (quantity) and deletion
- Confirmation modal for resolved items with quantity +/-
- Local timezone date fix (was showing UTC = next day after 7pm CDT)
- Per-user goals editing in Settings
- Shared food database across users, independent fitness goals
4. Media App (Built from Scratch)
- Books tab: search via Shelfmark/Anna's Archive, download + auto-import
to Booklore, per-book library dropdown
- Music tab: Spotify search via Spotizerr, track/album/artist/playlist
search types, Spotify embed player, download progress polling
- Library tab: Booklore library browser (237 books), cover resolution via
ISBN -> Open Library with localStorage cache, book detail modal,
format badges (EPUB/PDF), library filter pills
- Send to Kindle: SMTP2GO API integration, per-book Kindle label selector
("Madiha"/"Hafsa"), sends from bookdrop or booklore-books volumes
- "After download, also send to" Kindle option on book search
5. Multi-User Support
- Created Madiha's account
- Per-user nav visibility (cosmetic only, documented as such)
- hiddenByUser map in +layout.server.ts
- Shared food database, independent fitness goals
6. Frontend Architecture
- SvelteKit with Svelte 5 runes ($state, $props, $derived, $effect, $bindable)
- SvelteKit hooks.server.ts for auth on Immich/Karakeep proxies
- Settings page: real API data, fitness goals editing, disconnect confirmation
dialog, theme toggle, sign out
SECURITY REMEDIATION (Gitea Issues #1-#10)
==========================================
All 10 issues completed. Re-audited and verified in code on 2026-03-29.
#2 Auth Boundary
- /api/auth/register disabled (403)
- Gateway admin seeded from ADMIN_USERNAME/ADMIN_PASSWORD env vars only
- Trips USERNAME/PASSWORD have no default fallback
- Fitness user seed requires env vars (no "changeme" default)
- All passwords use bcrypt
#3 Trips Sharing Security
- handle_share_api enforces password via X-Share-Password header + bcrypt
- share_password stored as bcrypt hash
- All plaintext password logging removed
- Existing plaintext passwords invalidated by migration
- Dead hash_password function removed
#4 Fitness Authorization
- All user_id query params enforced to authenticated user's own ID
- /api/users returns only current user
- Wildcard CORS removed
#5 Gateway Trust Model
- Inventory and budget require API keys (X-API-Key middleware)
- Token validation uses protected endpoints per service type
- /debug-nocodb removed from inventory
- /test removed from inventory
- NocoDB search filter sanitized (strips operator injection chars)
- SERVICE_LEVEL_AUTH renamed to GATEWAY_KEY_SERVICES
- Trust model documented in docs/trust-model.md
- Per-user vs gateway-key services clearly distinguished
- Known limitations documented (no per-user isolation on shared services)
#6 Repository Hygiene
- No .env or .db files tracked in git
- .gitignore covers: .env*, *.db*, services/**/.env, data/, test-results/
- .env.example updated with all current env vars (no secrets)
#7 Transport Security
- Gateway: _internal_ssl_ctx removed entirely (internal services use plain HTTP)
- Gateway: ssl import removed from config.py
- Gateway: proxy.py uses urlopen() without context parameter
- Gateway: logout cookie includes HttpOnly, Secure, SameSite=Lax
- Gateway: image proxy uses default TLS + domain allowlist + content-type validation
- Trips: all 5 CERT_NONE sites removed (OpenAI, Gemini, Google Places, Geocode)
- Inventory: permissive cors() removed AND dead cors import removed
- Budget: permissive cors() removed AND dead cors import removed
#8 Dependency Security
- Budget path-to-regexp vulnerability fixed
- .gitea/workflows/security.yml committed with 3 jobs + workflow_dispatch trigger
- Gitea Actions enabled ([actions] ENABLED = true in app.ini)
- Runner (gitea/act_runner) added to Gitea docker-compose
- Runner registered as platform-runner on gitea_gitea network
- Config sets container.network = gitea_gitea so job containers can git clone
- Runner token stored in /media/yusiboyz/Media/Scripts/gitea/.env
- All 3 jobs verified passing:
- dependency-audit: SUCCESS (npm audit on budget + frontend)
- secret-scanning: SUCCESS (no tracked .env/.db, no hardcoded secrets)
- dockerfile-lint: SUCCESS (all Dockerfiles have USER + HEALTHCHECK)
- Runner setup documented in .gitea/README.md
#9 Performance Hardening
- Inventory /issues: server-side NocoDB WHERE filter (no full scan)
- Inventory /needs-review-count: server-side filter + pageInfo.totalRows
- Budget /summary: 1-minute cache
- Budget /transactions/recent: 30-second cache
- Budget /uncategorized-count: 2-minute cache
- Budget buildLookups: 2-minute cache
- Gateway /api/dashboard: 30-second per-user cache
- Actual Budget per-account API constraint documented
#10 Deployment Hardening
- All 6 containers run as non-root (appuser/node)
- Health checks on gateway, trips, fitness, inventory, budget, frontend
- PYTHONUNBUFFERED=1 on all Python services
- Trips Dockerfile only copies server.py (not whole context)
- Frontend uses multi-stage build
RE-AUDIT FINDINGS (2026-03-29)
==============================
1 inaccuracy found in prior report: CORS dead imports (const cors = require('cors'))
remained in inventory/server.js and budget/server.js after app.use(cors()) was removed.
Fixed by removing the dead imports.
All other claims verified accurate in code:
- Trips TLS: zero CERT_NONE or check_hostname = False
- Settings disconnect: confirm() dialog present
- /test cleanup: no references remain
- Cosmetic nav: documented as cosmetic-only, no false authz claims
GITEA ACTIONS RUNNER SETUP (2026-03-29)
=======================================
Problem: Workflow existed in repo but no runner was configured to execute it.
What was done:
1. Added [actions] ENABLED = true to Gitea app.ini
File: /media/yusiboyz/Media/Scripts/gitea/gitea/gitea/conf/app.ini
2. Restarted Gitea to pick up config change
3. Generated runner token: docker exec -u git gitea gitea actions generate-runner-token
4. Added runner service to Gitea docker-compose:
File: /media/yusiboyz/Media/Scripts/gitea/docker-compose.yml
Image: gitea/act_runner:latest
Container: gitea-runner
5. Saved token in /media/yusiboyz/Media/Scripts/gitea/.env as RUNNER_TOKEN
6. First attempt: job containers created on auto-generated network, could not
reach server:3000 for git clone (hung on git fetch)
7. Fix: created /data/config.yaml inside runner with container.network = gitea_gitea
and set CONFIG_FILE=/data/config.yaml env var
8. Recreated runner container (docker compose up -d runner) to pick up env change
9. Triggered workflow via API: POST /api/v1/repos/yusiboyz/platform/actions/workflows/security.yml/dispatches
10. All 3 jobs ran to completion: dependency-audit, secret-scanning, dockerfile-lint = SUCCESS
11. Added workflow_dispatch trigger to security.yml for manual runs
12. Updated .gitea/README.md with setup documentation
Key detail: job containers must be on gitea_gitea network to resolve "server:3000"
for git operations. Without this, git fetch hangs indefinitely.
BUGS FIXED DURING BUILD
========================
- SERVICE_MAP import bug: captured empty dict at import time, fixed with module reference
- Gateway Dockerfile missing modules: only copied server.py, fixed to copy all .py + integrations/
- Non-root container permission denied: fixed with COPY --chown=appuser
- Fitness date timezone: toISOString() returns UTC, fixed with local date construction
- Dashboard fitness widget not updating: plain let vs $state() in Svelte 5
- Food library empty: /api/fitness/foods/recent returns entry-shaped data, fixed mapFood
- Book covers from search: double-wrapped image proxy URLs, fixed to proxy directly
- Booklore cover API returns HTML: switched to Open Library + Google Books fallback
- Booklore books API too slow (14s): moved to lazy client-side cover resolution
- Fitness entries orphaned after DB reset: reassigned to new user IDs
- Madiha accidentally disconnected fitness: added confirm() dialog
- Double Kindle sends: actually processed + delivered SMTP2GO events, added debounce
- Kindle email typo: lowercase L vs uppercase I in address
MANUAL OPS ACTIONS
==================
1. Store admin password securely (set via ADMIN_PASSWORD env var)
2. Clean up local untracked .env files with real credentials if needed
3. Monitor @sveltejs/kit for a non-breaking cookie fix in future releases
ARCHITECTURE REFERENCE
======================
- Trust model: docs/trust-model.md
- CI workflows: .gitea/workflows/security.yml
- Runner setup: .gitea/README.md
- Design system: frontend-v2/DESIGN_SYSTEM.md
- Env var reference: .env.example
- Gitea instance: localhost:3300 (gate.quadjourney.com)
- Gitea compose: /media/yusiboyz/Media/Scripts/gitea/docker-compose.yml
- Platform compose: /media/yusiboyz/Media/Scripts/platform/docker-compose.yml