Skip to content

Guide

How to audit a Laravel codebase you inherited

By Adel Zemzemi ·

You just took over a Laravel product: as a new CTO, after an acquisition, or when the original developer left. Here is the order I work in during the first week, and the commands I run, to know what you are standing on before anyone ships the next feature.

  1. Run it locally from the README alone

    Clone the repository on a clean machine and follow only what is written. Every step you have to guess, every secret you have to ask for, every service that has to be started by hand is onboarding debt, and it tells you how much knowledge lives in one person's head.

    Write down how long it took. If it is more than an hour, fixing the README and the seeders is the first quick win of the audit.

  2. Read the dependency surface

    Start with composer.json: the PHP and Laravel versions, and how far they are from the versions still receiving security fixes on the Laravel release policy. Then list what is outdated and what has known vulnerabilities.

    composer outdated --direct
    composer audit
    npm outdated

    Abandoned packages matter more than old ones. A package with no release in years that sits on your billing or auth path is a risk, even if it still installs.

  3. Map the domain before reading the code

    List the routes, the models and the largest classes. In an inherited Laravel app the business logic is usually in a handful of oversized controllers, models or "service" classes, and those files are where most of the bugs and most of the fear are.

    php artisan route:list --except-vendor
    find app -name "*.php" | xargs wc -l | sort -n | tail -20

    You are looking for the boundaries: where tenants, billing, permissions and external providers are handled, and whether each of them lives in one place or is spread across the codebase.

  4. Check the data model against reality

    Compare the migrations with the actual production schema. Look for missing foreign keys, missing indexes on columns used in where clauses and joins, and columns too small for what they now store.

    Then find the queries that hurt. Turn on the database slow query log, and in a non-production environment make lazy loading throw so N+1 queries surface during normal use.

    // AppServiceProvider::boot(), outside production
    Model::preventLazyLoading(! app()->isProduction());
  5. Look hard at queues and the scheduler

    Read config/queue.php and the failed_jobs table. Jobs that fail silently, jobs with no timeout, and jobs that are retried without being safe to run twice are where data quietly goes wrong.

    List the scheduled tasks and check which ones could overlap if a run takes longer than its interval.

    php artisan schedule:list
    php artisan queue:failed
  6. Follow the money path end to end

    Billing is where an inherited codebase costs real money. Check that every webhook verifies its signature, that each event is stored and processed once even when the provider retries it, and that what a customer can do in the app is derived from what they actually paid for.

    Trace one subscription from checkout to cancellation in the code. If you cannot, nobody on the team can either.

  7. Audit authentication, tenancy and access rights

    Check that every model holding customer data is scoped to its tenant, that policies cover the actions that change data, and that no model is left fully mass assignable with an empty $guarded.

    Search for env() calls outside the config directory: once the config is cached they return null, which is a classic source of production-only bugs.

    grep -rn "env(" app routes resources --include=*.php
  8. Measure the safety net

    Run the test suite and note what it actually covers. A green suite that never touches billing, permissions or the main workflows protects very little.

    Run static analysis at a low level to get a baseline instead of a wall of errors. The number matters less than the trend once the team starts fixing.

    php artisan test
    vendor/bin/phpstan analyse --level=1 --memory-limit=1G
  9. Check how it ships and how you would know it broke

    Find out how a deploy happens, who can do it, how migrations run and how a release is rolled back. Then check error tracking, logs, uptime alerts and backups.

    A backup only counts once a restore has been tested. Ask when that last happened.

  10. Rank the risks and write the plan

    An audit is only useful if it ends in decisions. Rank every finding by severity and likelihood, estimate the cost to fix it, and turn the list into a 30/60/90-day plan the team can execute alongside feature work.

    Fix what can lose data or money first, then what slows every release down, then the rest.