revique-api is a TypeScript / Fastify service on Node 20 running as 4 ECS Fargate tasks across 2 services in us-e****-1, backed by an Aurora PostgreSQL Serverless v2 cluster and an S3 patient-files bucket, and still federated to a legacy AWS Lambda / API Gateway / MySQL platform in us-e****-2. Both tiers hold ePHI.
The engineering fundamentals are genuinely good. Tenant isolation is fail-closed and enforced twice over; there is no SQL injection exposure across 386 query sites; transport security at the edge is modern (TLS 1.2 floor with TLS 1.3); every database, volume and bucket in the native estate is encrypted; CloudTrail is multi-region with log-file validation and S3 object-level data events; GuardDuty is on in both regions with zero open findings; and the observability layer deliberately HMAC-fingerprints source IPs and account identifiers rather than storing them raw — a privacy control that goes beyond what HIPAA asks for.
The shortfalls are concentrated and specific. The application does not record who accessed which patient record, which is the heart of §164.312(b) and also what would make a breach assessment possible. Four of five console-capable AWS identities have no MFA. One legacy database is unencrypted at rest. The production API connects to its PHI database over TLS but does not validate the server certificate. And role-level “minimum necessary” permissions, though fully built, are enforced on only 9 of 48 route modules — not on the patient-facing ones.
| Safeguard category | Controls | Scored | Points | Section score | Weight | Contribution |
|---|---|---|---|---|---|---|
| Technical — §164.312 | 16 | 15 | 7.5 | 50.0% | 45% | 22.5 |
| Administrative — §164.308 | 12 | 6 | 3 | 50.0% | 35% | 17.5 |
| Physical — §164.310 | 7 | 5 | 3 | 60.0% | 20% | 12.0 |
| Overall | 35 | 26 | 13.5 | — | 100% | 52.0% |
Each applicable control is scored Compliant = 1.0, Partial = 0.5, Gap / Critical = 0. AWS-Managed counts as 1.0 because responsibility transfers to AWS under the Shared Responsibility Model — but only if the AWS BAA is signed (control A12). N/A controls are excluded. Manual-Review controls are excluded from the headline percentage rather than guessed at, and are listed in full further down this page.
The three categories are weighted Technical 45% / Administrative 35% / Physical 20%. The rationale: technical safeguards are where an application-layer assessment has the most evidence and where the most exploitable risk sits, so it takes the largest share; administrative safeguards carry more weight than physical because HIPAA enforcement actions overwhelmingly turn on missing risk analyses, policies and BAAs rather than on data-centre security; physical safeguards take the smallest share because they are almost entirely discharged by AWS.
The range is produced by re-running the same arithmetic twice: the lower bound (38%) treats all 9 Manual-Review controls as failing, and the upper bound (64%) treats them all as satisfied. The true figure sits between the two and cannot be narrowed without the documentation. The headline 52% is the score across scored controls only.
Read this honestly: a percentage is a prioritisation aid, not a compliance statement. An organisation can score well here and still be non-compliant for want of a risk analysis or a single BAA; the converse is also true. Treat the findings, not the number, as the deliverable.
DATABASE_SSL_REJECT_UNAUTHORIZED=true (T15) — a one-line task-definition change; the pattern already exists in the repo.StorageEncrypted=false. §164.312(a)(2)(iv).revique-api-prod-cluster, awsvpc, 4 tasks / 2 services (API + Payments)practice_id; reads in READ ONLY transactionsvax**** + Lambda + MySQL in us-e****-2 (public, 1-day backups, one unencrypted)auditlog-hipaa (multi-region, validated, KMS, S3 data events) |
no application PHI-access trail
Where the safeguards sit. Authentication and tenant authorization are centralised in a single
Fastify preValidation hook, so no route can accidentally opt out of them — and routes that fail to declare a tenant
context are rejected rather than allowed through. That is the right shape. The weakness is one layer in: once a request is inside the
correct practice, there is usually nothing further restricting which records that user may read.
Full control tables with per-control evidence:
| Section | Controls | Compliant | Partial | Gap | Manual-Review | Score |
|---|---|---|---|---|---|---|
| Technical — §164.312 → | 16 | 4 | 7 | 4 | 1 | 50.0% |
| Physical — §164.310 → | 7 | 2 | 2 | 1 | 2 | 60.0% |
| Administrative — §164.308 → | 12 | 0 | 6 | 0 | 6 | 50.0% |
rbac_audit_events pattern — the plumbing already exists — or add a dedicated phi_access_events table. This is the single largest technical gap and it also gates breach-notification readiness.aws:MultiFactorAuthPresent is false. Fastest high-impact fix in this report.DATABASE_SSL_REJECT_UNAUTHORIZED=true on the ECS task definition and supply the AWS RDS CA bundle already vendored at certs/aws-rds-global-bundle.pem. The sibling capture stack in this repo already does exactly this — mirror it.requireAnyRbacFeaturePermission() to patients, patient-files, face-charts and appointment-summaries so “minimum necessary” is enforced within a practice, not just between practices.PubliclyAccessible=true, reachable on 3306 from four static public addresses. Move DBA access behind a VPN or SSM Session Manager port-forward and set PubliclyAccessible=false.alias/auditlog-hipaa has rotation disabled; the oldest active IAM access key dates to 2020. Enable KMS annual rotation, configure Secrets Manager rotation for database credentials, and retire stale access keys.Deny statement on aws:SecureTransport = false to every bucket, starting with the legacy patient-document and patient-upload stores, and complete the public-access block on the 6 buckets missing it.root with a writable root filesystem. Add a non-root USER to the Dockerfile and set readonlyRootFilesystem: true on the task definition.OPTIONAL to REQUIRED before the native auth cutover.These 9 controls are excluded from the headline percentage. They cannot be established from source code or AWS telemetry — only from documentation, contracts and organisational records. They are not assumed to be failing; they are simply unknown from here, and they are the difference between the lower and upper bound of the range.
| # | Citation | Requirement | Why it needs a human |
|---|---|---|---|
| T4 | §164.312(a)(2)(ii) | Emergency Access Procedure (Required) | No break-glass role, documented emergency-access runbook, or dedicated emergency account was identified in code or AWS configuration. This is a procedural control — it must be evidenced by documentation, not inferred. |
| P3 | §164.310(b) | Workstation Use | Policy control governing acceptable workstation use for systems that access ePHI. Not determinable from code or AWS configuration. |
| P4 | §164.310(c) | Workstation Security | Not determinable from code or AWS configuration. Relevant observation for the reviewer: the legacy database security group admits MySQL/3306 from four static public /32 addresses (values withheld from this report), i.e. named workstations reach the production PHI database directly across the internet. Physical and endpoint security of those four machines becomes an in-scope control. |
| A1 | §164.308(a)(1)(ii)(A) | Risk Analysis (Required) | No documented, organization-wide risk analysis was found in the repository. This assessment is a technical gap-check and does not satisfy §164.308(a)(1)(ii)(A), which requires an accurate and thorough assessment of risks to all ePHI the organization holds. |
| A2 | §164.308(a)(1)(ii)(B) | Risk Management (Required) | Requires evidence that identified risks are tracked to closure under a documented programme. Not determinable from code or logs. |
| A3 | §164.308(a)(1)(ii)(C) | Sanction Policy (Required) | Workforce sanction policy for security-policy violations. Documentation control. |
| A5 | §164.308(a)(2) | Assigned Security Responsibility (Required) | A named Security Official must be designated and documented. Not determinable technically. |
| A8 | §164.308(a)(5) | Security Awareness and Training | Periodic workforce security training and reminders. Documentation control; no technical evidence available. |
| A12 | §164.308(b)(1) | Business Associate Agreements (Required) | Third parties that touch or may touch PHI have been enumerated from code and configuration (see the Business Associate register on the main page). Whether a signed BAA exists for each is a contractual fact that cannot be read from code or AWS APIs — it must be confirmed against the contract file. Note that the AWS BAA is the precondition for the Physical Safeguards position in P1. |
Third parties identified from code and production configuration that touch, or plausibly touch, PHI. Under §164.308(b)(1) each requires a signed Business Associate Agreement before PHI is disclosed to it. The “BAA on file?” column cannot be answered from code — every row needs contractual verification.
| Business associate | Function | Touches PHI? | BAA on file? — verify |
|---|---|---|---|
| Amazon Web Services | Hosting, compute, database, object storage, KMS | Yes — all ePHI at rest and in process | AWS offers a standard BAA covering HIPAA-eligible services. Verify signed & current. |
| AWS HealthScribe | Clinical note / medical transcription (dedicated prod bucket present) | Yes — clinical content | HIPAA-eligible service; covered by the AWS BAA. Confirm it is in scope of the signed addendum. |
| Amazon SES | Transactional and patient-facing email; 2 SMTP service identities active | Likely — identifiers in message bodies | Covered by AWS BAA. Verify no clinical detail is placed in email bodies. |
| Amazon Cognito | Identity provider for the native platform (implemented; not yet active in prod) | Identifiers only | Covered by AWS BAA. |
| FluidPay | Payment processing and practice provisioning | Patient identifiers + payment data | Independent BAA required. Also in PCI DSS scope. |
| Tebra | Practice-management import / production onboarding (largest integration in the codebase) | Yes — bulk patient records | Independent BAA required — bulk PHI transfer path. |
| Colorescience (LBR) | Product ordering integration (API key + partner/ship-to configured in prod) | Possibly — patient/shipping details | Verify what is transmitted; BAA required if PHI leaves the boundary. |
| Slack | Destination for production error alerts | Should be none — payloads are sanitized | Error records are allow-list built and identifiers are HMAC-fingerprinted. Confirm no PHI in alert text; BAA if any. |
| Segment | Analytics instrumentation referenced in the codebase | Should be none | Confirm whether it is active in production and whether identifiers are transmitted. |
| Stripe | Payment references in the codebase | Payment identifiers | Confirm active use; BAA / PCI responsibility matrix required if live. |
| Cloudflare | DNS for the native API hostname (Route 53 deliberately unused per stack config) | Transits ePHI if proxied | Confirm DNS-only vs proxied. If TLS terminates at Cloudflare, a BAA is required. |
Tenant-level minimum necessary is enforced well. Role-level is not: because feature permissions are checked on only 9 of 48 modules, a receptionist and a clinician within the same practice have, in most PHI modules, the same technical reach. Closing T3 is what brings this control home.
Detection is reasonable: GuardDuty in both regions, CloudTrail with S3 data events, structured application logs, and an error-alerting pipeline. Assessment is the problem. A breach notification must identify the individuals whose PHI was involved. With no PHI-access audit trail (T8), and with 180-day retention on the native tier (T9), the organisation may be unable to determine whose records were touched — which in practice forces over-notification. This is the strongest business argument for fixing T8 first.
Sources. The revique-api repository at commit
45d**** (360 TypeScript source files plus CDK infrastructure definitions), and live read-only AWS API responses from
account 501**** across us-e****-1 and us-e****-2.
Method. All AWS calls were read-only — describe-*, list-*,
get-* and CloudWatch Logs Insights queries. No resource was created, modified or deleted. Secret values were never
retrieved: get-secret-value was not called and no --with-decryption flag was used, so only secret names,
ARNs and rotation metadata appear here.
PHI handling. No patient data was read or extracted. Log queries were restricted to counts and metadata — status-code distributions, event-type counts and log-level counts over a single 3-hour window — and returned no message bodies. Five Logs Insights queries scanned approximately 8.7 MB in total, a cost well under one cent.
Masking. Every account identifier, ARN, endpoint, bucket name, IP address,
instance / VPC / security-group ID, domain and email address in this report is masked to its first three characters. The four public
addresses permitted to reach the legacy database were withheld entirely rather than masked. Raw collected JSON is retained locally in
_hipaa-data/, which is excluded from publication and never leaves the workstation.
****).