How to Run a DIY Code Audit in an Afternoon
You do not need a consultant or a big budget to learn most of what is wrong with your codebase. You need an afternoon, some free tools, and a systematic order to work through. This tutorial is that afternoon: a guided pass over the five things that go wrong most often, with the exact commands at each stage and links to the deep-dive tutorials when you want to go further on any one of them.
The goal is not perfection. It is to go from “I think our code is probably fine” to “here are the eleven specific things I found, ranked, and here is what I am fixing this week.” That shift is worth an afternoon of anyone’s time. For the checklist version you can keep beside you, use the free code audit checklist.
What you’ll need
- A clean clone of the codebase you want to audit
- The usual toolchain for your stack (Node, Python, etc.)
- A running staging instance for the auth and performance checks
- A blank document for findings, and roughly three focused hours
1. Set up and scope the audit
Start clean and start organized. Clone a fresh copy so you are auditing what is actually in the repo, not your working directory full of half-finished branches and local config:
git clone --mirror [email protected]:you/yourrepo.git audit-copy
cd audit-copyThen write down the scope in one line at the top of your findings doc: which services, which languages, and what you care about most (usually security first, then performance, then maintainability). Create the findings log now, because you will forget things by hour three. A simple table is enough:
| ID | Area | Finding | Severity | Effort | Fix? |
|----|------|---------|----------|--------|------|Severity is impact if exploited or hit. Effort is your honest estimate to fix. You will sort on both in the last step.
2. Scan for exposed secrets
Secrets first, because a leaked production credential outranks everything else you could find today. Run gitleaks across both the current files and the full history, since git never forgets a committed key:
brew install gitleaks
# Current files
gitleaks detect --no-git --source . --verbose
# Every commit ever made
gitleaks detect --source . --verbose --report-path secrets.jsonLog each finding, and remember the rule that trips everyone up: deleting a secret from the code does not revoke it. Any confirmed live credential goes straight to the top of your list as a rotate-now item. The full process, including verifying which keys are still live and purging history, is in how to scan your repo for exposed secrets.
3. Audit your dependencies
Most of your attack surface is code you did not write. Check it against known-vulnerability databases. Use your ecosystem’s auditor plus osv-scanner for a broader second opinion:
# Node
npm audit --omit=dev
# Python
pip-audit
# Any stack, scans every lockfile in the repo
osv-scanner -r .Do not just record the counts. For each high or critical finding, note whether the package actually ships to production and whether the vulnerable code path is reachable, because that is what turns a scary number into a real priority. The full triage method, including licenses and abandonment, is in how to run a dependency audit, with background in auditing third party dependencies.
4. Run static analysis for security bugs
Now point a tool at your own code. Static application security testing (SAST) reads source without running it and flags dangerous patterns: injection, unsafe deserialization, command execution, weak crypto. Semgrep is free, fast, and ships with solid default rulesets:
pip install semgrep
# Run the community security rulesets
semgrep --config=auto .Skim the output for the patterns that matter most: string-concatenated SQL, eval and its cousins, shelling out with user input, disabled TLS verification. SAST is noisy and produces false positives, so treat each finding as a lead to confirm, not a verdict. To understand what this technique catches and, just as importantly, what it structurally cannot, read what SAST is and SAST vs DAST. The short version: static tools find bad patterns in code, but they cannot reason about whether your access control logic is correct, which is the next step.
5. Spot check auth and database performance
This is the part no tool does for you, and it is where the expensive bugs live. Budget most of your remaining time here.
For authorization, take one protected endpoint and test it from the attacker’s side: no token, a low-privilege token against an admin route, and one user asking for another user’s resource by ID:
# Normal user hitting an admin route should be 403, not 200
curl -i https://staging.example.com/api/admin/users \
-H "Authorization: Bearer $USER_TOKEN"
# You are user 1001; ask for 1002's data. A 200 is an IDOR finding.
curl -i https://staging.example.com/api/invoices/1002 \
-H "Authorization: Bearer $USER_TOKEN"A 200 where you expected a 403 or 404 is a serious finding. The complete auth pass is how to audit authentication in your app.
For performance, load a list-heavy page with query logging on and count the queries. If the count scales with the number of rows instead of staying flat, you have an N+1:
from django.test.utils import CaptureQueriesContext
from django.db import connection
with CaptureQueriesContext(connection) as ctx:
client.get("/dashboard/")
print(len(ctx.captured_queries)) # a big number here is your smellThe full detection and fix workflow is in how to find N+1 queries before they bite, and the scaling context is in auditing database queries for scale.
6. Prioritize findings and decide what to fix
By now your findings log has a mix of terrifying and trivial. The mistake here is fixing the easy stuff first because it feels productive. Sort by severity descending, then by effort ascending within each severity band, and draw a line:
- Fix this week: high severity, especially anything exploitable from the internet (live secrets, broken access control, critical vulnerable dependencies in production paths).
- Fix this month: medium severity, or high severity with genuinely high effort that needs planning.
- Backlog with a note: low severity, dev-only issues, and code smells. Record them so they are decisions, not oversights.
Turn the top rows into actual tickets today, while the context is fresh, with the file, the line, and the reproduction step attached. An audit that ends in a document nobody actions was a waste of a good afternoon. Keep the security review checklist template linked in your process so the next pass is faster.
Get a second pair of eyes (free)
A DIY audit will surface most of your low-hanging problems, and that alone puts you ahead of most teams. What it cannot easily surface is the class of bug that only an experienced reviewer catches: the authorization check that exists on every endpoint except the new one, the race condition, the architectural decision that will not survive next year’s traffic. That is what a manual review is for. When you have finished your own pass, get a free code audit: a Webisoft engineer reads your codebase by hand and sends you a prioritized report of what actually matters. It is genuinely free, and it is the natural next step after the work you just did.