# Security, Performance and Handover Review — 4 October 2026

## Delivery status

Management reporting, specimen tracking hardening, shared picker styling, production session hardening and deployment configuration changes are implemented. This is a code and local validation review. Client handover still requires the staging and infrastructure checks below; it is not a certification that every path is bug free.

## Reporting

- Owner menu: `/owner/reporting`; staff route: `/reporting`. Accountant is now mapped to its own frontend workspace instead of falling back to Patient.
- `reporting:read` and `reporting:export` are seeded through the existing permission engine and appear in Permissions, role grants and user overrides. Default access: Superadmin and Accountant. Other staff can be delegated Reporting using these grants; patients cannot access it.
- Report periods: complete Bikram Sambat months, Shrawan-to-Ashadh fiscal years and custom dates using the shared Nepali calendar. Backend dates remain AD with Asia/Kathmandu boundaries. Query parameters preserve period and section on refresh/back navigation.
- Live financial and operational summaries, daily rows, direct test panel volumes and package purchases; financial / operational / combined CSV and PDF exports. All catalog aggregates are exported, not only dashboard top five. Exports are audited; CSV cells are escaped against spreadsheet formulas, PDF text is autoescaped. No patient identity or clinical result data is exported.
- Accounting basis is displayed: invoice totals/discounts/current dues belong to invoices issued during the period; daily collections/tender breakdown use payment dates. Current dues are not historical closing balances. Test counts are direct ordered panels, with package purchases separately counted; they are not a count of every component analyte. Current patient totals and backlogs are identified as current snapshots. Cancelled test panels/visits and void/deleted invoices are excluded from the relevant financial/test aggregates.
- Server-side period limit: at most 367 calendar days. Generation and export have separate rate limits and exports require both read and export grants. Staff without a branch assignment fail closed; cross-branch requests are denied.

## Security and middleware

Fixed findings:

1. Public tracking no longer accepts phone suffixes. Exact normalized phone matching permits explicit Nepal country prefixes. Visit lookup compares literal identifiers rather than interpreting `%`/`_` as wildcard expressions. Public tracking is limited to ten requests/minute and discloses pipeline stages, not clinical values or money.
2. Tenant analytics reject branchless staff. Identifier lookup scopes staff to their branch and rejects ambiguous matches rather than selecting an arbitrary specimen/visit.
3. Production frontend uses HttpOnly cookie sessions and does not persist access/refresh/patient tokens in local storage. Legacy staff tokens are cleared during production rehydration. Patient logout clears its cookie and revokes its active JWT. Cookie-based writes require a configured allowed Origin in production.
4. A streaming ASGI policy layer adds no-store API responses, no-referrer, nosniff, frame denial, request IDs and Server-Timing. It logs route templates/status/duration, not query strings or bearer-link paths. Exception logging no longer includes raw URLs or details. Production proxy/access logs omit request paths/query strings and referrers.
5. Production validates long secret keys, debug off, rate limits on, HTTPS origins/links and explicit allowed hosts. TrustedHost is enabled when configured. Redis-backed rate counters are shared between production workers.
6. Signature reads are bounded to 2 MB plus a sentinel byte, dimensions are checked and resized before conversion; image processing runs outside the async loop.
7. Audit reads use the separate `audit:read` grant and enforce branch scope. Reading an empty log no longer inserts demonstration events. Existing database events were not removed; audit/demo data must be reviewed before migrating a development database to production.
8. Backend JWT implementation migrated to PyJWT with cryptography and mandatory exp/sub/type claims, removing the unfixed ECDSA transitive advisory. JWTs have unique IDs to prevent same-second refresh/login collisions. Frontend DOMPurify, TypeScript ESLint and Vitest received security updates.

Existing middleware remains useful: CORS, SlowAPI and optional Prometheus instrumentation. Authentication and permissions stay as endpoint dependencies. Nginx already compresses responses, so no duplicate app gzip middleware was added. Database/Redis clients close on shutdown and request sessions roll back on failure. Permission resolution is reused within a request, never cached across users.

Dependency audit:

- Installed backend core/development environment: pip-audit reports no known vulnerabilities after the JWT change. Optional extras not installed/deployed are outside that scan.
- Deployed frontend dependency tree: npm audit --omit=dev reports zero findings.
- Five high findings remain in Tailwind 3's build-only chain (Tailwind, chokidar, micromatch, fast-glob, braces; advisory GHSA-vfj7-8cjw-p6xm). The registry has no compatible patched braces 3 release. These packages are absent from the static Nginx runtime image. Build only trusted source/configuration in isolated CI; plan a separately reviewed Tailwind 4 migration. Do not use force-upgrades or silence the advisory to manufacture a clean audit.
- Scan results are point-in-time, not an assurance against unknown vulnerabilities. Repeat on release and dependency changes.

References: [OWASP CSRF origin verification](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html), [OWASP browser storage guidance](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html).

## Performance

- bcrypt password/PIN hashing and verification now run in bounded asyncio worker threads instead of blocking the async request loop. PDF rendering already runs in a thread; reporting follows that pattern.
- Converted 32 analytics filter predicates from date/timezone casts on every row to direct timestamp ranges. This allows existing timestamp indexes to be used; timezone casting remains where needed for daily grouping.
- Dashboard cache remains five minutes; management reporting intentionally regenerates fresh accounting data. Query cancellation is forwarded and client export requests are cancelled on unmount. An already running server render finishes its bounded work; cancelling the browser request cannot terminate a Python worker thread. Object URLs use the shared delayed cleanup helper.
- Permission startup uses atomic bulk inserts with conflict handling. Normal reads preserve immediate permission revocation. DB pools are configurable, defaulting to 5 connections plus 5 overflow per worker (40 maximum across four workers, rather than 120).
- Local read-only sample: ten serial requests per endpoint while development reload/tests were active. Median health 54.6 ms, readiness 74.7 ms, public catalog summary 84.2 ms, public packages 234.7 ms. p95: 846.5/108.9/152.5/1017.7 ms respectively. All returned 200. These are development observations with few rows and cold/reload effects, not a production SLA or concurrency benchmark.
- Production bundle remains split by role/page; immutable assets and Nginx gzip are enabled. TypeScript checks have not been run, per the user's instruction.

## Specimen identity and custody

- Existing CODE128 barcode label generation and scanner lookup are retained. The human-readable specimen code, patient identity, visit number and specimen type remain on the label and in the desk, so scanners are optional. Users can type the specimen code or search the visit/bill/patient and confirm identity before changing custody.
- New specimen identifiers are allocated globally under a PostgreSQL transaction advisory lock. Numeric suffix parsing excludes year digits. This prevents collisions across branches and concurrent creation. Legacy identifiers remain readable; ambiguous legacy identifiers require a branch context/unique barcode.
- Patient/visit branch-year allocation also has transaction locks. Completed/rejected specimens and cancelled visits cannot be received back into processing accidentally.
- Collection/receiving/completion/rejection remain audited with actor and previous/new status. Automated tests cover codes, manual/scanner lookup, branch isolation and terminal-state rejection; concurrency regression is included.
- No software can prove the physical tube belongs to the patient. Client training must require label-at-collection and two-identifier confirmation. Test the actual thermal printer/scanner; ordinary manual codes work without them.

## Dropdown and calendar consistency

- FieldSelect remains an accessible native control with a themed customizable popup in browsers supporting base-select. Existing keyboard/touch/forms and change handlers are preserved; older browsers retain their OS picker. Radix Select uses matching rounded surfaces, spacing, focus styling, labelled triggers and constrained scroll height. Referrer selection reuses FieldSelect.
- Long/searchable country and patient pickers retain their dedicated searchable controls. Native date inputs were absent in the production source audit; reporting reuses NepaliDateInput and NepaliDatePicker. Storage and sorting remain AD, display and period selection BS.

## Deployment configuration

- Backend Docker image uses frozen uv.lock dependencies, excludes .env/.git/storage/test caches, runs as non-root, includes timezone/PDF fonts and owns persistent `/app/storage`.
- Production Compose now uses the actual settings names (APP_SECRET_KEY, JWT_SECRET_KEY, CLIENT_APP_BASE_URL), carries email settings, exposes frontend through the proxy and connects the frontend API proxy to backend port 8000. Signature/report storage is a persistent volume.
- Configure real HTTPS termination in front of the HTTP proxy. Set APP_ALLOWED_HOSTS to the externally served hostname(s) as a JSON array; the Docker health check sends a configured allowed Host header. Set APP_ALLOWED_ORIGINS to exact HTTPS frontend origins as a JSON array; CLIENT_APP_BASE_URL must be the actual HTTPS URL. Keep Postgres/Redis/API internal. The public proxy blocks `/metrics`; monitoring can use the internal network.
- Avoid treating the example .env or development containers as production configuration. New reporting and audit permissions seed on startup and preserve existing grants/disabled flags.

## Release and client handover gates

1. Run the explicitly deferred FE type check and any configured BE type check when authorized. Run final lint/tests/build after any further change.
2. Build/start the production containers with real release settings; confirm migrations at head and `/health/ready`. Container builds have not been executed in this local review.
3. Validate HTTPS, allowed host/origin settings, secure cookies, cookie refresh/logout, trusted proxy client-IP forwarding and rate limiting on the actual host. Proxy IP trust must be restricted to the controlled proxy network.
4. Run realistic concurrent reception/specimen/result/report/export workflows against representative anonymized data; measure warmed p50/p95 and error rates. Do not infer production capacity from local serial requests.
5. Execute staging journeys for every role, grants/revocations, public booking/tracking and patient portal. Verify monthly/FY totals against invoice/payment source records, with dates crossing midnight/FY boundaries.
6. Send an approved test email; verify only valid patient email addresses are dispatched. Confirm actual client contact/hours/logo/catalog configuration.
7. Test printer labels and scanner/manual fallback with client hardware and patient identification procedures.
8. Perform PostgreSQL and storage backup restoration, Redis outage/restart and rollback drills; configure alerts, retention and owner access to logs. Protect immutable financial/clinical records.
9. Document the remaining build-tool advisory and obtain release risk acceptance, or finish the planned Tailwind migration before release. Train client staff and obtain workflow sign-off.

Validation results are recorded in Work_Progress.md after the final runs finish. No deployment or client handover has been performed.
