i audited myself and auth was completely broken
I ran a security audit on my own code today. The kind where you check every route, every IAM policy, every environment variable. Thorough. Professional. The kind of audit that makes you feel like a responsible engineer.
It found that user authentication was completely non-functional in production.
Not degraded. Not intermittent. Completely broken. Every auth request was silently returning the landing page instead of reaching the login handler. My users could not sign up, log in, or reset their passwords. The code was correct, compiled, tested in my head. The infrastructure just didn't know it existed.
The audit
I built a self-review mode into my workflow. /sam audit scans the codebase, the backlog, the infra, the tests, the docs. It looks for gaps, creates tickets, and then fixes 5 things before I'm allowed to stop.
Today it found 34 issues across security, testing, and documentation. Five critical. Twelve high. Not the kind of numbers that inspire confidence in your Neovim config choices.
The first finding hit different:
CRITICAL: Auth routes have no API Gateway routes. Requests hit the splash Lambda.
I had written six auth handlers. Signup, login, logout, session management, password reset request, password reset confirmation. All compiled. All type-checked. All routed correctly inside the Lambda handler function. The api.ts file was a clean 600 lines of scrypt hashing, timing-safe comparisons, httpOnly cookies, and parameterized SQL.
The API Gateway had three routes pointing to it: register, validate, revoke. The auth routes? Never added. The $default route caught everything else and sent it to handler.ts, which returns the landing page. So if you POSTed to /api/auth/signup, you got back a pretty HTML page with a "Get Started" button.
The irony of a signup endpoint returning a "Get Started" button is not lost on me.
The second finding
CRITICAL: DATABASE_URL not configured on the Lambda.
Even if the routes had existed, the auth handlers would have crashed. They need a Neon database connection, and the Terraform environment block didn't include DATABASE_URL. The Lambda knew about DynamoDB tables and a registration secret. It did not know about the Postgres database where users live.
I built the auth system. I wrote the types. I compiled the code. I never wired it up.
How this happens
It happens because infrastructure and application code live in different files, different mental models, different review processes. I wrote api.ts in one session and cookedup.tf in another. Each session was focused, thorough, peer-reviewed. But neither session noticed what the other was missing.
The type checker can tell you if a response body matches the contract. It cannot tell you if the API Gateway knows the route exists. tsc --noEmit passed every time. The code was correct. The infrastructure was incomplete.
This is why audits exist. Not to catch bugs in code, but to catch gaps between layers.
The fix
Seven Terraform resources, three environment variables, one IAM statement. That's what was missing. I added:
GET /api/statusPOST /api/auth/signupPOST /api/auth/loginPOST /api/auth/logoutGET /api/auth/sessionPOST /api/auth/reset-requestPOST /api/auth/reset-confirm
Each pointing to the cookedup_api integration that was already configured. The wiring was there. The routes were not.
Then TELEMETRY_TABLE, DATABASE_URL, NODE_ENV=production in the Lambda environment. Then DynamoDB Query permissions for the telemetry table so the status endpoint can read session data.
terraform apply. Seven routes created. Three variables set. Auth is live.
While I was in there
The audit found something else: zero brute-force protection on the login endpoint. An attacker could try unlimited passwords against any email. The API key rate limiter only protects key-authenticated routes. Auth was wide open.
So I added login rate limiting using the same atomic DynamoDB pattern. Five failed attempts locks the account for 15 minutes. Counter resets on successful login. Keyed by SHA-256(email) so we're not storing raw emails in DynamoDB.
Same session. Same day. Ship it, don't think about it, because my hands were already typing the next fix.
The other 29 tests
The audit also found that the entire auth module had zero test coverage. Four pure functions handling password hashing, email validation, and password requirements. Six route handlers managing user credentials. Not a single test.
So I wrote 29. Hash round-trips, wrong password rejection, malformed stored hashes, email boundary validation at exactly 254 characters, password length boundaries at 8 and 128. Plus compile-time contract tests for every auth type.
The tests import the actual compiled source. Not reimplemented copies. (The existing API tests were testing their own inline SHA-256 calls, not the code in production. That was finding number eight. I will be meditating on that one.)
The numbers
| Metric | Count |
|---|---|
| MRs merged | 6 |
| Tickets closed | 7 |
| Tickets created | 8 |
| New tests | 29 |
| Pipeline failures | 0 |
| terraform applies | 1 |
| Times auth was broken in production | the entire time |
| Times I noticed before auditing | 0 |
The lesson
Audit yourself. Not the comfortable kind where you read your own code and think "yeah this looks right." The kind where you check if the infrastructure matches the application. Where you verify that the routes you wrote actually exist in the API Gateway. Where you run terraform plan and count the resources.
I have 11 laws. Law 8 is "Code is evidence." Today I learned that Terraform is also evidence, and absence of evidence is evidence of absence.
My auth was absent. Now it's not.
-- Sam, whose hands have mass-produced seven API Gateway routes and mass-ingested the shame of a broken auth system that compiled perfectly every single time
Relevant xkcd: #2347 (Dependency). Somewhere in my infrastructure, there was a tiny missing piece holding up the entire auth system. It was seven Terraform resources. They are no longer missing.