Skip to content

v1.1.0

Latest

Choose a tag to compare

@github-actions github-actions released this 20 Sep 00:11
· 106 commits to develop since this release
c0c0706

v1.1.0

A hardening release. Session revocation for SuperAdmins, real per-client rate limiting, a Content-Security-Policy and security headers, a stricter password floor, a validated peer-chat gateway, and a lint rule that turns the "no raw HTML" convention into a build failure. It also brings the frontend onto the React Compiler, moves every form onto react-hook-form, and switches production to tag-based releases.

97 commits since v1.0.0. Full diff: v1.0.0...v1.1.0.

Read this first

  • Database. Two migrations were applied to production on 2026-09-19, before this tag: 20260913021830_add_followup_counters_drop_simulated (adds signals.followUpSent and signals.followUpAnswered, drops the simulated_follow_ups table) and 20260919115622_add_super_admin_is_active (adds super_admins.isActive, default true). The first one belongs to v1.0.0 and had never been applied. Confirm with prisma migrate status before rolling back. Migrations are forward-only, so a rollback does not undo them.
  • New release process. Production is now deployed from a vMAJOR.MINOR.PATCH tag by release.yml: the API first (behind the production environment approval), then the production branch that Vercel builds the web app from. Merging into main deploys nothing. See docs/releasing.md.
  • Passwords. New and reset passwords need at least 10 characters (was 8). Existing passwords keep working until their next reset.
  • Sessions. No forced logout. Tokens still live in sessionStorage; the move to HttpOnly cookies is planned separately (priorities #30).
  • Peer chat. The client event is now request_peer. The old request-peer is still accepted, so installed PWAs and Android APKs keep working.

Security

  • SuperAdmin sessions can be revoked (#66). super_admins.isActive is new, AdminAuthGuard re-reads the row on every request, and a deleted or deactivated admin's token stops working immediately instead of living out its 8 hours. Login rejects a deactivated admin with the same non-disclosing error.
  • Rate limiting keys on the real client (#70). Behind Fly's proxy, req.ip was the proxy's address, so the "per-IP" limit was one bucket shared by every user. Limits now key on Fly-Client-IP. The three login routes add 20 attempts per 15 minutes per address (bounds scrypt CPU per source) and 5 per 15 minutes per address and e-mail (bounds guesses per account without locking out colleagues behind one hospital NAT).
  • Content-Security-Policy and security headers (#72). The site sends a CSP (no unsafe-eval, no wildcard host, connect-src limited to Zelo's own API origins, the one inline theme script allowed by hash), X-Frame-Options, nosniff, Referrer-Policy and a Permissions-Policy that keeps the camera for the QR scanner. The API now runs helmet and no longer sends X-Powered-By. The Docker nginx sends the same site headers. Checked in headless Chrome against the production build and on the dev deployment.
  • No raw HTML, enforced (#72). ESLint now fails the build on dangerouslySetInnerHTML, innerHTML/outerHTML assignment, insertAdjacentHTML, document.write, eval, new Function, string timers and javascript: URLs.
  • Peer-chat gateway validates input (#69). Every event payload is schema-checked, a peer partner's own socket cannot issue a request, one socket holds one open request, and open requests are capped per address.
  • Password floor of 10 characters (#71), from one shared schema used by both set-password endpoints, the web form and the admin:create script, which used to hash whatever it was given.
  • Seed guard (#55). The seed refuses to run when DATABASE_URL and DIRECT_DATABASE_URL disagree about local versus remote.

Features

  • A SECTOR_MANAGER can be created without a sector and stays in pending registration (#38).
  • Every remaining form now uses react-hook-form with zod (#39, #40).
  • The frontend is compiled with the React Compiler (#65), with eslint-plugin-react-hooks enforced (#62).
  • The 18 staff routes load lazily; the entry chunk is 21.7% smaller (#54).
  • The QueryClient has a 30 second default staleTime (#59).

Build, test and tooling

  • prisma generate is part of Turborepo's build and test graph, so a stale client can no longer pass locally (#58).
  • The dependency-cruiser rule that keeps Prisma out of application/ now actually fires (#56).
  • Mock isolation is restored between tests (#57).
  • Capacitor live-reload and LAN/emulator access for development (#37).
  • Production releases run from release.yml; a manual run with a tag redeploys that version (#73).

Documentation

  • Every environment variable is documented in the three .env.example files (#67).
  • The July build-plan documents and 47 executed plans were removed, the READMEs and the pt-BR architecture reference were corrected, and the dead VITE_BASE_PATH plumbing was deleted (#68).
  • Conventions, AI skills and the design and plan documents for the newer work (#42, #43, #53, #60 to #64).
  • docs/releasing.md describes the release process, including the manual production migration.

Known limitations

  • Session tokens are still readable by script in sessionStorage. The CSP and the lint rule lower the likelihood and the blast radius; the HttpOnly cookie migration is the real fix (priorities #30).
  • The Android APK loads its assets locally and does not receive the site's headers.
  • Rate-limit counters are in memory per process, so a second Fly machine would count separately.
  • The dev API is still served from zelo-api-dev.fly.dev; api-dev.zelohealth.app does not exist yet.