This Risk Analysis identifies and evaluates potential risks and vulnerabilities to the confidentiality, integrity, and availability of electronic Protected Health Information (ePHI) maintained by EMStool LLC in the operation of the Cadence MIH Scheduling Platform.
This analysis covers all systems, infrastructure, and processes involved in the storage, processing, and transmission of ePHI within the Cadence platform, including:
proxynet/video-net internal networks — no container has an open inbound port to the internet; all external access is via Cloudflare Tunnel| Container | Image | Purpose | ePHI Exposure |
|---|---|---|---|
| MariaDB-scheduler | mariadb:latest | Database for the public demo/trial instance (scheduler.emstool.com) — prospective clients try Cadence here | None — seed/dummy data only, no real patient records |
| MariaDB-license | mariadb:latest | License/billing metadata for client deployments | None — no patient data |
| MySQL | mysql:latest | Legacy DB backing swag-emstool's misc subdomains (users db, unrelated jeopardy game db) | None |
| swag-scheduler | lscr.io/linuxserver/swag | Web/app server + TLS termination for the demo/trial instance, scheduler.emstool.com | None — seed/dummy data only, no real patient records |
| swag-license | lscr.io/linuxserver/swag | License server + Command Deck ops console, license.emstool.com | None — no patient data |
| swag-emstool | lscr.io/linuxserver/swag | Public marketing site + legacy subdomains, emstool.com | None — no patient data |
| swag-suspended | nginx:alpine | Static "service unavailable" page shown during instant client suspension | None |
| cadence-provisioner | cadence-provisioner (local image) | One-click new-deployment provisioning; holds Docker socket access | None directly, but privileged |
| swag-video | node:20-alpine | Video consult signaling server (WebRTC offer/answer relay) | Call signaling metadata only — no media, no chart data |
| redis-video | redis:7-alpine | Ephemeral signaling state for swag-video | Call signaling metadata only |
| coturn | coturn/coturn | Self-hosted TURN relay — test/isolation use only, not production (production TURN is Cloudflare Realtime) | Would relay encrypted (SRTP) call media if used — not currently in the production call path |
| osrm-dfw / osrm-usa | osrm/osrm-backend | Route optimization engines (DFW-metro extract + full-USA extract) | Receives patient address coordinates for routing — no other PHI |
| Nominatim | mediagis/nominatim | Self-hosted geocoding (address → lat/lng), internal only — no public Nominatim fallback | Receives patient addresses for geocoding — no other PHI |
| gotenberg | gotenberg/gotenberg | PDF generation for end-of-day reports and chart exports | Receives full chart/report content to render as PDF |
| CF-TUNNEL-DARYL / Unraid-Cloudflared-Cadence | figro/unraid-cloudflared-tunnel | Cloudflare Zero Trust Tunnels — sole external ingress path | ePHI in transit passes through per client deployment (TLS-encrypted end-to-end, Cloudflare does not decrypt at this hop) |
Route optimization, geocoding, PDF rendering, video signaling, and the tunnel ingress pattern above run identically inside each client's own deployment (on the client's own hardware) — this table documents EMStool's own copy of that same stack, used for development and the demo instance, not a client's live data.
Each threat is rated by Likelihood (1–3) and Impact (1–3). Risk Level = Likelihood × Impact:
| Score | Risk Level | Response Required |
|---|---|---|
| 7–9 | Critical | Immediate remediation required |
| 5–6 | High | Remediation within 30 days |
| 3–4 | Medium | Remediation within 90 days or accepted with controls |
| 1–2 | Low | Monitor; accept with existing controls |
| Threat | Like. | Impact | Risk | Current Controls | Residual Risk / Action |
|---|---|---|---|---|---|
| T-01: Unauthorized remote access to application server | 2 | 3 | High (6) | Cloudflare Zero Trust Tunnel (no open ports); bcrypt auth; session timeout; rate limiting; MFA available; SSH key-only | Medium — Enable MFA enforcement for all users |
| T-02: Physical theft or unauthorized access to server hardware | 1 | 3 | Medium (3) | Servers in locked room at private residence; access restricted to Security Officer; MariaDB data encrypted at rest (AES-256) | Low — Acceptable with physical security policy in place |
| T-03: Ransomware or malware infection | 2 | 3 | High (6) | Encrypted backups on B2 (off-site); Docker isolation limits blast radius; no direct internet exposure; Unraid OS separation | Medium — Off-site B2 backups enable recovery; test restore procedure quarterly |
| T-04: Credential theft (phishing, weak passwords) | 2 | 3 | High (6) | Bcrypt password hashing; 5-attempt lockout; TOTP MFA available per user; SSH key authentication for server access | Medium — Enforce MFA for all users via admin toggle |
| T-05: Insider threat (unauthorized patient data access) | 2 | 2 | Medium (4) | HIPAA audit log (append-only via DB triggers) records all access; org-tree scoping limits data visibility; role-based access control; company access gates | Low — Quarterly audit log review by Security Officer |
| T-06: Loss or exposure of encryption keys | 1 | 3 | Medium (3) | Key backed up to ZFS Byte pool (Cosmo); OneTimeSecret email delivery; chmod 600; root-only access; documented in Physical Security Policy | Low — Annual key backup verification required |
| T-07: Network interception (man-in-the-middle) | 1 | 3 | Medium (3) | TLS 1.3 via Cloudflare; no plaintext HTTP permitted; HTTPS enforced in _auth.php; Cloudflare WAF and DDoS protection | Low — Cloudflare handles TLS termination and certificate management |
| T-08: Backup failure or corruption | 2 | 2 | Medium (4) | Automated scheduled backups; validation step decrypts and counts tables; backup_log records success/failure; B2 off-site copy | Low — Test restore from B2 quarterly; alert on backup failure |
| T-09: Third-party vendor breach (Cloudflare, Resend, Backblaze B2) | 1 | 2 | Low (2) | Cloudflare: sees only encrypted TLS traffic. Resend: password resets only, zero PHI. B2: receives pre-encrypted .sql.gpg files only — no readable PHI | Low — Vendor documentation maintained; annual review |
| T-10: Patient address exposure via Nominatim geocoding | 2 | 1 | Low (2) | Self-hosted Nominatim deployment in progress (build running on Cosmo); no external geocoding API calls planned post-deployment | Low — Complete Nominatim self-hosting; switch app URLs |
| T-11: Application vulnerability (SQL injection, XSS) | 1 | 3 | Medium (3) | All DB queries use PDO prepared statements; all output passed through htmlspecialchars(); esc() helper for JS DOM; Cloudflare WAF | Low — Code review on all new features; no user-controlled SQL |
| T-12: Docker container escape or misconfiguration | 1 | 3 | Medium (3) | Per-customer isolated Docker networks; no privileged containers (except SWAG NET_ADMIN); no host port bindings; MariaDB not exposed externally | Low — Keep Docker and base images updated |
| T-13: Workforce member HIPAA policy violation | 2 | 2 | Medium (4) | Sanctions Policy documented and signed; Workforce Training Policy in place; audit log captures all PHI access; access revoked immediately on termination | Low — Annual training completion required; quarterly audit log review |
| T-14: Server hardware failure (no redundancy) | 2 | 2 | Medium (4) | Encrypted B2 backups enable recovery to new hardware; Docker containers restart automatically; UPS protects against power events | Medium — Document RTO/RPO targets; test full restore procedure |
| # | Threat | Current Risk | Mitigation Action | Target Date |
|---|---|---|---|---|
| 1 | T-01, T-04: Unauthorized access / credential theft | Medium | Enable MFA enforcement toggle for all users (Admin → Settings → Require MFA) | Before first customer deployment |
| 2 | T-03: Ransomware recovery | Medium | Document and test full restore procedure from B2 encrypted backup | Q3 2026 |
| 3 | T-10: External geocoding exposure | Low | Complete self-hosted Nominatim deployment and switch app URLs | After Daryl SSD upgrade |
| 4 | T-14: Hardware failure RTO | Medium | Define Recovery Time Objective and test restore to clean hardware | Q3 2026 |
| Control Category | Controls in Place | Status |
|---|---|---|
| Encryption at Rest | MariaDB InnoDB AES-256 (all tables); file upload AES-256-GCM; backup GPG AES-256 | ✅ Complete |
| Encryption in Transit | TLS 1.3 via Cloudflare Zero Trust; HTTPS enforced; no open inbound ports | ✅ Complete |
| Access Control | Role-based (10 levels); org-tree scoping; company access gates; session timeout (4hr); rate limiting; MFA available | ✅ Complete — MFA enforcement pending |
| Audit Logging | Append-only audit_log (DB triggers block delete/update); dedicated audit_writer user; all PHI access logged | ✅ Complete |
| Backup & Recovery | Automated scheduled backups; GPG encryption; B2 off-site; validation on every backup; retention management | ✅ Complete — restore test pending |
| Key Management | InnoDB key on server; backup copy on ZFS Byte pool (separate machine); OneTimeSecret email delivery | ✅ Complete |
| Physical Security | Locked server room; restricted access; documented in Physical Security Policy | ✅ Documented |
| Workforce Policies | Sanctions Policy; Physical Security Policy; Workforce Training Policy; Breach Notification Procedure; Security Officer designated | ✅ Complete — Risk Analysis document (this document) |
| Vendor Management | Vendor documentation completed; no PHI to Resend or B2 (pre-encrypted); Nominatim self-hosting in progress | ⚠ Nominatim pending |
Based on this analysis, the Security Officer determines that:
This Risk Analysis must be reviewed and updated: