I built and ran a URL shortener with click analytics on AWS. It worked. But if someone had asked "were you breached?", I couldn't have answered. Here's what I got wrong, what I got right, and what I'd do now.
stack
Laravel on AWS
live
~6 months
status
retired, domain kept
written
Oct 2026
6 findings from my own review
2high1medium1to review2done right
Then and now
HOW IT RAN
Me
Root userMFA on, used for everything
Every resource in the accountservers, storage, database, billing
no audit logexposure never checked
HOW I'D RUN IT NOW
Me
IAM Identity Centersign-in with MFA
Least-privilege rolesadmin for me, a narrow role for the app
Root userlocked away, billing and recovery only
CloudTrail, all regionsGuardDutyCIS checks in Security Hub
What I got wrong
F-01HIGH
Root account used for daily work
WHAT HAPPENED
MFA was on, but everything ran through the root user. Root can't be limited by any policy, so one stolen session would have meant losing the whole account.
WHAT I'D DO NOW
Root locked away with MFA and no access keys, used only for billing and recovery. Daily work through IAM Identity Center, and the app on a narrowly scoped role.
F-02HIGH
No audit logging
WHAT HAPPENED
CloudTrail was never turned on. There was no record of who did what in the account, so a breach would have been invisible.
WHAT I'D DO NOW
CloudTrail in every region and GuardDuty from day one, before the first deploy.
F-03MEDIUM
Public exposure never checked
WHAT HAPPENED
I never checked whether security groups, storage buckets or the database were reachable from the internet.
WHAT I'D DO NOW
AWS Config or Security Hub checked against the CIS AWS Foundations Benchmark, plus an external exposure scan. That scan is exactly what my thesis scanner does.
F-04TO REVIEW
Abuse handling
WHAT HAPPENED
Shorteners are a favourite tool for phishing because they hide the destination. And, I didn't have a way to handle abuse reports, so I could have been used to host phishing pages.
WHAT I'D DO NOW
Check every submitted link against Safe Browsing and blocklists, rate-limit link creation, and publish a takedown contact.
What I got right
GOODSecrets never reached Git. The .env file was excluded from the start.
GOODMFA on the root account. It blocks the most common way root accounts get taken over.
Shutting it down safely
Switching a service off is a security task too. Leftover resources cost money, and an expired domain lets anyone take over every link you ever issued.
DONEClosed the AWS account, so no servers, buckets, snapshots or bills are left behind.
DONEDomain kept, so nobody can re-register it and redirect old short links to phishing pages.
DONEAuto-renew, registrar lock and MFA on the registrar account.
PLANNEDEvery old short link returns 410 Gone so a dead link fails safely.
PLANNEDDMARC on the domain, now used for email, so nobody can send mail pretending to be it.
What I took from it
Without logs, you can't answer the one question that matters after an incident.
Use root only for billing and emergencies.
Decommissioning needs a checklist as much as launching does.