Intermediate

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-copy

Then 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.json

Log 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 smell

The 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.

FAQ

Frequently asked questions

What if I cannot finish all six steps in one afternoon?

Stop where you are and schedule the rest; the steps are already ordered by value. Secrets and dependencies are the fastest checks with the highest stakes, so even a two-step audit beats no audit. Just make sure the findings you did log get triaged rather than abandoned in a document.

Semgrep gave me a wall of findings. Are they all real?

No. SAST is inherently noisy, so treat each finding as a lead to confirm rather than a verdict. Start with the patterns that matter most, like string-concatenated SQL, eval, shelling out with user input, and disabled TLS verification, and expect a meaningful share of the rest to be false positives.

Do I need a staging environment, or can I audit the code alone?

Steps 1 through 4 are static and work on a clean clone alone. Step 5 needs a running instance, because authorization probing and query counting only mean something against live behavior. If staging does not exist, run the app locally with seed data rather than probing production.

How often should I repeat this?

A full manual pass each quarter, or after any major feature, is a reasonable rhythm. The mechanical parts should not wait that long: wire gitleaks, the dependency scanners, and semgrep into CI so they run on every pull request, and save the afternoon format for the judgment work in step 5.

I found more than I can fix. What now?

That is the normal outcome, and it is what step 6 is for: sort by severity, then effort, and turn only the top band into tickets this week. If you want an outside opinion on those priorities, the free code audit is a manual review by a Webisoft engineer, delivered as a written, prioritized findings report by email in a few business days.

Free · no strings shown later

Get your free code audit

A repo URL and two minutes of your time. A Webisoft engineer sends back written findings (security, tech debt, performance) with file-and-line specifics.

Request my free audit