What Should Engineers Include in a Code Audit Checklist?

A few years back I got handed a Node service that three different contractors had taken turns on. No README worth reading, 11 environment variables with no documentation, and a test suite that passed because somebody had commented out the assertions. My job was to tell the client in two days whether it was worth saving. I didn't have time to read 40,000 lines of code. What I had was a list.
That list has turned into the code audit checklist I still use, and it's the thing I reach for before I open a single file. Reading code without one feels productive and usually isn't. You follow whatever thread looks interesting, you lose an afternoon in the auth middleware, and you come out the other side with opinions instead of findings.
Here's what belongs on that list, and roughly why.
Why a checklist beats just reading the code
Audits go wrong in two directions. Either they're too shallow (someone runs a linter, exports a PDF, calls it a day) or they balloon into a full rewrite proposal nobody asked for.
A checklist keeps you honest on both counts. It forces coverage of the areas that sink projects, and it gives you a stopping point. You're not trying to understand everything. You're answering a specific set of questions, writing down what you find, and moving on.
The other benefit is that findings become comparable. When you audit the same six areas every time, you build a feel for what normal looks like. A service with 40 direct dependencies isn't inherently bad. A service with 40 direct dependencies, four of them unmaintained since 2021, is a different conversation.
Start with the boring stuff
Before any of the deep sections, I spend about an hour on things that sound trivial:
- Can I clone the repo and get it running locally, following only the written instructions? Time how long it takes.
- Does the test suite pass on a clean checkout?
- Does CI run on every PR, or has it been red for so long that people stopped looking?
- How many commits in the last 90 days, and from how many people?
That last one tells you more than it should. A repo with one contributor and a six-month gap has knowledge locked in somebody's head, and that person may no longer work there.
If setup takes more than an hour of fiddling, write it down as a finding. Onboarding friction compounds. Every new engineer pays that tax.
The code audit checklist, section by section
Security
Start here because it's the area where a single miss costs the most.
- Secrets in the repo. Run history scanning, not just a look at HEAD. Something like
gitleaks detect --source . --log-opts="--all"will surface keys that were committed and later deleted. Deleted from the working tree is not deleted from git. - Authentication and session handling. Where are tokens issued, how long do they live, can they be revoked?
- Input handling at the boundaries: raw SQL string building, unescaped template rendering, file uploads that trust the client's content type.
- Dependency vulnerabilities (covered below, but security owns the severity call).
- Transport and storage: TLS everywhere, password hashing with a current algorithm, PII that shouldn't be in logs.
Authorization checks deserve their own note, because this is where I find the most real bugs. Look for endpoints that verify the user is logged in but never verify the resource belongs to them. The classic shape:
@app.route("/api/invoices/<invoice_id>")
@login_required
def get_invoice(invoice_id):
# authenticated, but whose invoice is it?
return Invoice.query.get(invoice_id).to_json()
I don't try to be exhaustive here. I'm looking for patterns. One missing ownership check usually means there are twelve.
Dependencies
Dependencies are where risk accumulates quietly. Nobody schedules time for them, so they rot.
What I check:
- Known vulnerabilities.
npm audit,pip-audit,bundle audit, whatever fits. Note the count but read the actual advisories, because a high-severity flag in a dev-only tool is not the same as one in your HTTP layer. - How far behind are the majors? Being two major versions behind on your framework is a migration project, not a patch.
- Abandoned packages. Check last publish date and open issue counts. A package that hasn't shipped in three years and has 200 open issues is a liability regardless of whether it currently works.
- Lockfile discipline. Is there one? Is it committed? Do CI builds use it?
- License compatibility, if the client ships commercial software. Copyleft licenses buried in a transitive dependency have ruined people's quarters.
One honest caveat: vulnerability scanners are noisy. Plenty of flagged CVEs aren't reachable in a given codebase. Report them, but don't inflate the number to look thorough.
Architecture
This is the squishiest part of the code audit checklist, and the part where you have to resist the urge to critique style.
The questions that matter:
- Where are the boundaries? Can you describe the system in five boxes? If you can't after a day of reading, that itself is the finding.
- Is business logic separable from the framework, or is everything in controllers and ORM models?
- How does data flow? Who owns which tables? Are there services reaching directly into each other's databases?
- State management. Where does the system assume a single instance? In-memory caches, local file writes, and cron jobs baked into app startup all block horizontal scaling.
Then there's coupling. Which file changes in nearly every PR? Git can tell you:
git log --since="6 months ago" --name-only --pretty=format: | sort | uniq -c | sort -rn | head -20
The files at the top of that list are either the core of the system or its biggest problem, and it's usually easy to tell which.
Architecture findings should be framed as constraints, not as a redesign. The useful output is: here's what this structure makes expensive.
Performance
Avoid the trap of micro-benchmarking. Look for structural performance problems instead.
- N+1 queries. Turn on query logging, hit three or four heavy endpoints, count the queries.
- Missing indexes on columns used in WHERE and JOIN clauses. Check against the actual query patterns, not against intuition.
- Unbounded result sets. Any endpoint that returns a list with no pagination will eventually time out.
- Work happening in the request path that belongs in a queue: image processing, PDF generation, third-party API calls with no timeout.
- Caching. Is there any? Is invalidation understandable?
If the team has monitoring, read it before you read the code. Real latency percentiles beat your guesses. If there's no monitoring at all, that's a finding on its own.
Coding standards and tests
Coding standards matter less for aesthetics and more as a signal of team discipline.
- Is there a formatter and linter, and do they run in CI, or are they just installed?
- Is the style consistent, or can you tell which contractor wrote which module?
- Test coverage: the number matters less than the shape. Are the critical paths covered? Are tests mostly unit tests against mocks, which tend to pass while the system is broken?
- Are there flaky tests people retry by habit? Ask the team directly. They'll know.
- Error handling conventions. Swallowed exceptions and bare
except:blocks hide production problems for months.
Operational readiness
Often skipped, often the reason a project is stuck.
- Deployment process. Documented? Repeatable? One person who knows how?
- Rollback path. If a deploy breaks production at 2am, what happens?
- Logging, metrics, alerting. Can you answer what broke without SSH access?
- Backups, and whether anyone has tested a restore.
- Environment parity. How different is staging from production, truthfully?
When the audit says the problem isn't the code
Sometimes you finish the checklist and the findings are unremarkable, but the project is still six months late. That happens more than people expect. The code is mediocre, not broken. What's broken is scope, handover, or the fact that the only person who understood the payment flow left in March.
In those cases, a list of technical findings won't help much on its own. Teams in that position usually need someone to stabilise delivery and rebuild context, and there are structured approaches for taking over a stalled build and getting it shippable again that work better than another round of refactoring.
Knowing which situation you're in is part of the audit's job.
What the output should look like
A finished audit is not a spreadsheet of 300 lint warnings. What the client, or your own engineering lead, needs is:
- A one-page summary with a clear verdict. Is this safe to build on, yes or no, with conditions.
- Findings grouped by severity, each with location, impact, and rough effort to fix.
- A short list of things to do this week, separate from things to do this quarter.
- Explicit mention of what you did not look at. Honesty about coverage is what makes the rest credible.
If you want a view of how this gets scoped into a fixed window, The 48-Hour Code Audit: What We Look For breaks down the same territory with timeboxing in mind, which is useful if you're trying to fit an audit around real delivery work.
Adapt the list, don't follow it blindly
The sections above are a starting point. A payments backend and an internal admin tool deserve different weightings, and your code audit checklist should reflect that. Drop what doesn't apply. Add the things your team keeps getting burned by.
The one habit worth keeping regardless: write the findings down as you go, in the same file, in the same format, every time. Six months later when someone asks why you flagged the auth layer, you'll have an answer. And the next audit gets faster, because you already know what you're looking for.
About the author
Helping businesses—from startups to enterprises—build and rescue apps and websites at Jhavtech Studios. We transform stalled or messy software projects into clean, scalable digital solutions. Passionate about solving complex tech challenges and bringing great ideas to life.
Comments
Join the discussionNo comments yet. Be the first to comment.


