September 2026
CGE-P Capstone
I inherited a patient intake API that shipped non-compliant on purpose and wrapped it so the same system could stand up to a HIPAA review.
The starter is a patient intake API for a fake telehealth company. It takes submissions over HTTPS and stores them. That is PHI. It shipped with the obvious gaps left open: no customer key on S3 or DynamoDB, no TLS deny, no versioning, and Lambda sitting outside the VPC.
I did not rewrite the app. I wrapped it. HIPAA is the framework that actually applies here. The repo has the Terraform, the policies, the pipeline, and a longer write-up.
What I built
Terraform puts a customer-managed key on the uploads bucket and the table,
turns versioning on, denies HTTP, and parks Lambda on private subnets.
CloudTrail and an Object Lock vault sit next to the inherited stores.
Encryption, TLS, versioning, and placement are closed in the live apply. The
VPC path is only halfway there — vpc_config is set, NAT and VPC endpoints
are not.
Five Rego policies read the plan and fail if those settings disappear. I left the TLS deny off on the first PR on purpose so the gate had something to refuse. It failed. The second PR put the deny in and the gate went green.
The pipeline is one GitHub Actions workflow: plan, Conftest, apply on merge, Cosign sign, upload to the vault. If Conftest is red, apply does not run. The fail still gets signed and stored.
The OSCAL file describes this intake API, not a generic S3 module. It points at NIST’s 800-66 catalog instead of inventing 800-53 IDs.
Gaps I closed
| Gap | What it is | HIPAA | How it is closed |
|---|---|---|---|
| GAP-01 | S3 default encryption is SSE-S3, not a CMK | 164.312(a)(2)(iv) | CMK phi + SSE-KMS on the uploads bucket; Rego fails if that is not the default |
| GAP-02 | DynamoDB on an AWS-owned key | 164.312(a)(2)(iv) | Table encryption uses the same CMK; Rego fails if there is no customer kms_key_arn |
| GAP-03 | No aws:SecureTransport deny |
164.312(e)(1) | Bucket policy denies HTTP; Rego fails if that statement is missing |
| GAP-04 | S3 versioning off | 164.308(a)(7) | Versioning Enabled; Rego fails if it is not |
| GAP-05 | Lambda not in the VPC | 164.312(e)(1) | vpc_config on private subnets; Rego fails if the function has none |
GAP-06 through GAP-08 are still open: reserved concurrency and a DLQ, least privilege on the intake role, API Gateway access logs. I stopped once encryption, TLS, versioning, and placement were real.
Calls I made
Object Lock is GOVERNANCE for 30 days. COMPLIANCE would make a lab mistake permanent.
Apply runs when a PR hits main. If the gate is red, nothing deploys.
Everything lives in one AWS account. A separate evidence account would be cleaner, and more IAM than this sprint.
CMK rotation is a year. Ninety days would not have changed anything real for this setup.
The CGE-P Curriculum is the stack. This is that stack on a system that was already running and already wrong.