diff --git a/docs/audit/full-repository-audit-2026-10-11.md b/docs/audit/full-repository-audit-2026-10-11.md new file mode 100644 index 0000000000..ccdf026b1f --- /dev/null +++ b/docs/audit/full-repository-audit-2026-10-11.md @@ -0,0 +1,215 @@ +# Comprehensive Full-Repository Forensic Audit Report: PsychSift + +**Repository:** `BigSimmo/PsychSift` +**Date:** October 11, 2026 +**Auditor:** Antigravity AI Review Swarm (7 Parallel Autonomous Domain Agents) +**Execution Mode:** Pure Forensic Read-Only (Zero Code Mutations, Zero Live External API Calls) +**Git Baseline:** `feat/scoped-resilience-and-ui-hardening` (Commit: `adcc101ab2`, Base: `origin/main` at `73d08a5a78`) + +--- + +## 1. Executive Summary & Clinical Overview + +This report delivers the findings of an exhaustive, parallel multi-agent forensic audit across the entire PsychSift codebase. Seven specialized autonomous auditors simultaneously evaluated the repository across distinct clinical, architectural, security, accessibility, and quality domains. + +### Overall System Health + +PsychSift displays **exceptional engineering maturity** in several foundational areas: + +- **Client-Side Ergonomics & Accessibility:** A centralized `LiveAnnouncer` screen-reader singleton, two-phase focus restoration in sheets/drawers, strict suppression of misleading "0 matches" on failed network searches (`ErrorState`), and WCAG 2.1 AA tokenized color contrast. +- **API Boundary Discipline:** 100% of API route payloads are validated with strict Zod schemas with centralized error envelope wrapping (`jsonError`). +- **Postgres Row-Level Security:** 100% of database tables have Row-Level Security enabled (`ENABLE ROW LEVEL SECURITY`) with `auth.uid()` tenant confinement. +- **Offline Evaluation Culture:** Robust golden datasets and deterministic offline retrieval scoring pipelines. + +### Primary Risks & Critical Vulnerabilities + +Despite these strengths, the audit identified **3 Critical Blockers (P0)** and **11 High-Severity Issues (P1)** that warrant immediate attention: + +1. **Unbounded Serverless Memory Leak (P0 / MEM-01):** Module-level in-memory `Map` caches (`answerCache`, `queryEmbeddingCache`, `signedUrlCache`) accumulate entries indefinitely across requests in warm serverless instances, leading to heap exhaustion (OOM crashes) and cross-tenant memory leakage. +2. **Total Absence of Medical Math / Clinical Calculators (P0 / CS-001):** While psychometric rating scales (PHQ-9, GAD-7, K10) are implemented, all clinical dosing calculators (renal clearance / Cockcroft-Gault, eGFR, QTc interval correction formulas, and lithium/clozapine titration helpers) are completely absent from `src/lib/calculators/`. +3. **Vitest Mock Leakage & State Bleed (P0 / TS-01):** The Vitest Node test suite configuration omits setup files that restore mocks (`vi.restoreAllMocks()`), leaving global stubs (`Date`, `fetch`, timers) leaking across suites and causing non-deterministic CI failures. +4. **Clinical Drug Selection Hazards (P1 / CS-002):** Flat lowercase medication entity names without ISMP Tall Man lettering for look-alike/sound-alike drugs (e.g., `clozapine` vs `clonidine`, `hydroxyzine` vs `hydralazine`). +5. **Prompt Injection in Clinical RAG (P1 / INJ-01):** Direct interpolation of unescaped user query strings into LLM prompts without delimiter sanitization, allowing prompt jailbreaks and overriding clinical guardrails. +6. **React Hydration Mismatches (P1 / FE-HYD-01 to 04):** Direct evaluation of browser globals (`window.location.search`, `BarcodeDetector`) during initial component render causes layout flicker and console errors. +7. **Severe Cumulative Layout Shift (P1 / FE-CLS-01):** Global search shell hard reloads snap from a full-height hero to a compact bottom dock after hydration. +8. **Mobile Search Chrome Contract Breach (P1 / A11Y-01):** `DocumentViewer.tsx` retains a `0.75rem` pad on scroll-hide, violating the repository's core invariant ("Hidden means zero reserve: exactly 0rem"). + +--- + +## 2. Severity Classification Matrix + +| Severity Level | Definition | Impact on PsychSift | +| :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------- | +| **P0 (Critical Blocker)** | Immediate patient safety risk, production crash / memory leak, or broken test harness preventing reliable verification. | Must be remediated before production deployment or clinical user exposure. | +| **P1 (High Priority)** | Substantial functional, clinical, or architectural flaw (prompt injection, hydration mismatch, chunk negation severing, mobile contract breach). | Remediation required in the current release cycle. | +| **P2 (Medium Priority)** | Moderate flaw, performance bottleneck, test flakiness, or maintenance burden (unclosed PDF handles, missing test timeouts, large client bundles). | Scheduled remediation in upcoming sprints. | +| **P3 (Advisory / Low)** | Cosmetic inconsistency, minor hygiene gap, or defense-in-depth recommendation (cookie flags, stale type casts, minor accessibility attributes). | Addressed during routine maintenance. | + +--- + +## 3. Master Findings Matrix + +| Ref ID | Sev | Domain | File & Line | Summary Description | Clinical / Technical Consequence | +| :------------ | :----: | :-------------------- | :------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | +| **MEM-01** | **P0** | TypeScript / API | `src/lib/rag/rag-cache.ts:36-44`
`src/lib/openai.ts:65, 72`
`src/lib/signed-url-cache.ts:29` | Unbounded module-level `Map` caches in serverless environment. | Memory leaks / OOM crashes in warm containers; potential cross-tenant memory leakage. | +| **CS-001** | **P0** | Clinical Safety | `src/lib/calculators/` | Total absence of clinical dosing & medical math calculators (renal clearance, QTc, lithium titration). | Clinicians using PsychSift have psychometric scales but no safety calculators for high-risk psychiatric prescribing. | +| **TS-01** | **P0** | Test Suite | `vitest.config.mts:118` | Vitest Node environment omits `setupFiles` with `vi.restoreAllMocks()`. | Leaked mocks (`Date`, `fetch`, timers) cause non-deterministic CI flakes across test suites. | +| **CS-002** | **P1** | Clinical Safety | `src/lib/medication-entities.ts:69, 72, 344` | Flat lowercase medication names lacking ISMP Tall Man lettering. | Increased cognitive error and selection hazard for look-alike/sound-alike drugs (e.g. clozapine vs clonidine). | +| **CS-003** | **P1** | Clinical Safety | `src/lib/medication-interaction-lexicon.ts:108-138` | Non-stocked external contraindicated interactants excluded from active alerts. | Critical contraindications (e.g., St John's Wort with SSRIs/SNRIs) do not trigger automated interaction warnings. | +| **CHK-01** | **P1** | AI / RAG | `src/lib/chunking.ts:405-407` | Semantic chunker falls back to arbitrary string slice on short prefixes. | Splits critical clinical sentences across chunk boundaries, severing negations ("DO NOT administer"). | +| **INJ-01** | **P1** | AI / RAG | `src/app/api/answer/route.ts`
`src/lib/rag/rag.ts` | Direct interpolation of unsanitized user query into LLM prompt template. | Prompt injection allowing user search queries to override clinical guidelines and safety guardrails. | +| **FE-HYD-01** | **P1** | Frontend Architecture | `src/components/roster/manage/roster-manage-page.tsx:172`
`src/components/roster/swaps/roster-swaps-page.tsx:88` | Browser `window.location.search` evaluated during initial React component render. | Severe React hydration mismatch, breaking SSR tree reconciliation and causing UI flash. | +| **FE-HYD-02** | **P1** | Frontend Architecture | `src/components/clinical-dashboard/differentials-home.tsx:990-1010` | Checkbox state initialized from URL query parameters without two-pass hydration guard. | Hydration mismatch on direct-linked or refreshed differential search queries. | +| **FE-HYD-03** | **P1** | Frontend Architecture | `src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx:32` | `"BarcodeDetector" in window` checked directly in component render body. | Hydration error on client render when server SSR renders fallback state. | +| **FE-CLS-01** | **P1** | Frontend Architecture | `src/components/clinical-dashboard/global-search-shell.tsx:352-357` | Hard reload on submitted search URL (`?q=...&run=1`) renders full hero, snapping to bottom dock. | Severe Cumulative Layout Shift (CLS), jarring user experience and layout jump on mobile/desktop. | +| **MEM-02** | **P1** | TypeScript / API | `src/app/api/search/route.ts:79`
`src/lib/shared-get.ts:19` | In-flight promise tracking `Map` lacks timeout and cleanup guarantees on aborted requests. | Stalled or aborted client connections permanently pin promise references in memory. | +| **TS-02** | **P1** | Test Suite | `tests/ui-chrome-scroll.spec.ts:226`
`tests/ui-smoke.spec.ts:3404` | >20 instances of hardcoded `page.waitForTimeout(1000)` arbitrary sleeps in Playwright. | Fragile, slow test runs susceptible to machine load and race conditions. | +| **A11Y-01** | **P1** | Accessibility / UX | `src/components/DocumentViewer.tsx:1377-1391` | Declares `data-phone-composer-reserve="0.75rem"` on hidden search composer. | Directly violates repository design invariant: "Hidden means zero reserve: exactly 0rem". | +| **SEC-01** | **P2** | Security / Privacy | `src/lib/privacy.ts:6`
`src/app/api/answer/stream/route.ts:113` | `safeErrorLogDetails` redacts structured keys but passes raw error strings to logger. | Incidental PHI leakage: patient names or identifiable text in query error messages flushed to server logs. | +| **CS-004** | **P2** | Clinical Safety | `src/lib/medications/` | Absence of centralized zero-dosing formatter utility across medication displays. | Risk of trailing zeros (`5.0 mg` misread as 50 mg) or missing leading zeros (`.5 mg` misread as 5 mg). | +| **CS-005** | **P2** | Clinical Safety | `src/lib/medications/` | QTc risk evaluated per-drug rather than cumulative polypharmacy set calculation. | Underestimates arrhythmia risk when multiple moderately-prolonging psychotropics are combined. | +| **PY-01** | **P2** | AI / RAG | `worker/python/extract_pdf_assets.py:234, 386` | PyMuPDF `Document` and `Pixmap` instances lack explicit `.close()` calls in processing loop. | Python worker OOM crash and file descriptor leaks during bulk extraction of large clinical guidelines. | +| **RAG-01** | **P2** | AI / RAG | `src/lib/rag/rag.ts:716-735` | `safeFallbackAnswer` returns unparsed plain text bypassing structured citation checks. | Degraded trust: user receives answer without explicit provenance links or citation audit trails. | +| **FE-CLS-02** | **P2** | Frontend Architecture | `src/components/on-call/on-call-home.tsx:321-370` | `TodayShell` relies on `empty:hidden` containers populating asynchronously. | Above-the-fold content shifts downward as widgets load, degrading Core Web Vitals. | +| **FE-ARC-01** | **P2** | Frontend Architecture | `src/app/(search-app)/layout.tsx` | Root shell layout bundles `GlobalSearchShell` and `MasterSearchHeader` across all routes. | 5.60 MB gzip bundle overhead imposed on lightweight work pages (`/my-day`, `/roster`, `/teaching`). | +| **FE-INP-01** | **P2** | Frontend Architecture | `src/components/dsm/dsm-search-page.tsx`
`src/components/differentials/` | Client-side filtering and matrix calculation executed synchronously on the main thread. | Long tasks (>50ms) causing typing stutter and poor Interaction to Next Paint (INP) responsiveness. | +| **TS-03** | **P2** | Test Suite | `tests/ui-smoke.spec.ts` | Dynamic `test.skip(({ browserName }) => browserName !== "chromium")` silently bypasses WebKit/Firefox. | Tests pass on CI but leave Safari and Firefox rendering engines unverified. | +| **TS-04** | **P2** | Test Suite | `tests/ui-smoke.spec.ts` | Synthetic `setTimeout` promises inside `route.fulfill()` network mocks. | Delays test execution artificially without testing true network latency characteristics. | +| **A11Y-02** | **P2** | Accessibility / UX | `src/components/phone-focus-clearance.tsx:54` | Focus clearance bails out on elements outside `.phone-scroll-surface`. | Inputs inside bottom `Sheet` overlays risk being occluded by mobile virtual software keyboards. | +| **A11Y-03** | **P2** | Accessibility / UX | `src/components/compare/compare-empty-state.tsx:30-49` | Primary action button and starter chips lack explicit `focusRing` / focus-visible utilities. | Ambiguous keyboard focus indicator under custom themes or high-contrast settings. | +| **SEC-02** | **P3** | Security / Privacy | `src/app/auth/callback/route.ts:121` | `PASSWORD_RECOVERY_COOKIE` lacks `httpOnly: true` flag. | Advisory: cookie accessible via client JavaScript; should be strictly `httpOnly`. | +| **WRK-01** | **P3** | AI / RAG | `worker/main.ts:177-194` | Read-modify-write on `documents.metadata` executed via client update if RPC is missing. | Potential data race if multiple workers update metadata concurrently without optimistic locking. | +| **A11Y-04** | **P3** | Accessibility / UX | `src/components/clinical-dashboard/search-pins-menu.tsx:351` | Search pin text input relies on parent `