Codebase Autopsy — worked example
Salvage Report: OrderDesk
This is the whole deliverable, at the length and specificity a real one arrives at. If you are deciding whether an assessment is worth $4,000, this is what the $4,000 buys.
Scope Application source, database schema, infrastructure configuration, deployment process, and dependency inventory. Not in scope: penetration testing, a formal security audit, or review of the third-party fulfilment vendor's own systems.
01 Executive summary
The system is worth keeping. It is unfashionable, under-maintained and undocumented, but the business logic inside it is sound and it has been processing roughly 1,180 orders a week without material incident. The problems found here are problems of neglect, not of design.
Three findings need action within the month. There is no recoverable backup of this system (R1). The payment webhook can create duplicate orders under retry, and appears to have done so three times in the last quarter (R2). The runtime has been out of security support for over three years (R3). Any one of these is capable of producing a very bad week.
A rewrite is not warranted and would be a serious mistake at this point. Rebuilding this platform is realistically nine to fourteen months of work during which the current system still has to be maintained by the same people. Making it safe, supported and maintainable is four to six weeks. The gap between those two numbers is the entire recommendation.
The slowness your team reports has a specific, cheap cause. It is one un-eager-loaded relationship on the account order history (R7), affecting your largest customers worst. Half a day.
The single most important finding is R1. Everything else in this report is a problem you can schedule. R1 is the one that can end the company, and it should be resolved this week whether or not you engage anyone further.
02 The system at a glance
- Language
- PHP 7.4 — security support ended Nov 2022
- Framework
- Laravel 8.x — security support ended Jan 2023
- Database
- MySQL 5.7, 96 tables, 11 with no foreign keys
- Hosting
- Two unmanaged VPS instances, one provider, one region
- Size
- ≈47,000 lines of first-party PHP, 14 Composer packages out of date
- Automated tests
- 41 tests, ≈4% line coverage, 6 currently failing
- Deployment
- Manual `git pull` over SSH, performed by one person
- Monitoring
- None. Failures are reported by customers
- Last dependency update
- 22 months ago
- Current maintainers
- None. Original agency engagement ended 7 months ago
03 Risk register
Ranked by what happens if it is ignored, not by how interesting it is. Nine findings; three are rated critical.
- R1 Critical
Backups run nightly and have never been restored
The cron job writes a mysqldump to the same VPS that hosts the database. It has never been restored, and the destination volume has been at 94% capacity for at least four months — the most recent three dumps are truncated. There is currently no recoverable backup of this system.
If ignored A disk failure or a bad migration ends the business.
- R2 Critical
Payment webhook is not idempotent
The processor retries on any non-2xx response. The handler creates an order row before it acknowledges, and the acknowledgement can time out under load. Three duplicate orders in the last 90 days appear to share this cause; all three were resolved manually by staff who assumed customer error.
If ignored Silent duplicate charges. The failure is invisible until a customer complains.
- R3 Critical
Runtime is out of security support
PHP 7.4 and Laravel 8 both stopped receiving security fixes over three years ago. Three installed Composer packages have published CVEs with no available patch on this major version.
If ignored Known, publicly documented vulnerabilities with no upgrade path short of the version bump.
- R4 High
Live credentials are in git history
A `.env` containing the production database password, the mail relay password, and a payment processor secret was committed in 2021 and removed in a later commit. It remains in history. The repository has been shared with at least two former contractors.
If ignored Anyone who has ever cloned this repository holds production credentials.
- R5 High
Deployment depends on one person and one laptop
There is no build, no pipeline, and no record of what is deployed. The running code differs from `main` in at least two files. Nobody can say when they diverged or why.
If ignored The system cannot be safely changed by anyone else, including anyone you hire.
- R6 High
No error tracking of any kind
Exceptions are written to a local log file that is not rotated and not read. The current file is 2.3GB. Sampling it shows a recurring fatal in the fulfilment export, roughly 40 times a week, that nobody has ever seen.
If ignored You are already having failures you do not know about. This one has been running for months.
- R7 Medium
Order history N+1 causes the reported slowness
The account order list issues one query per line item. For the ten largest accounts this is 400–900 queries per page load, p95 of 8.4 seconds. This is the "the site is slow" complaint, and it is one eager-load away from fixed.
If ignored Your largest customers have the worst experience.
- R8 Medium
Session and CSRF configuration is non-default
Session cookies are set without the `secure` flag and CSRF verification is disabled on four routes, apparently to fix a bug in 2022. Two of the four accept authenticated POSTs.
If ignored A cross-site request forgery against an authenticated admin.
- R9 Low
An abandoned service is still running and still billed
A second VPS runs a "reporting" application last deployed in 2021. Nothing routes to it. It holds a full copy of the customer table as of its last run.
If ignored You are paying to host a stale copy of your customer data on an unpatched box.
04 Unknowns
What could not be determined from the material available, and what it would take to determine it. Every inherited system has a section this long. Most reports leave it out.
-
A cron job nobody can account for
A job named `sync_legacy.sh` runs at 03:15 daily on the second VPS and exits 0. Its source is not in the repository. Determining what it does requires access to that machine, which nobody currently has.
To resolve Half a day, once shell access exists
-
A CSV export that at least one customer depends on
An undocumented endpoint produces a fixed-format CSV. Access logs show one IP fetching it every weekday at 06:00 for the last two years. Nobody internally knows who or why — but somebody has built a process on it, and changing the column order will break it.
To resolve One phone call, if the right customer can be identified
-
Why 11 tables have no foreign keys
It is not consistent with the rest of the schema. It may be deliberate (a performance decision), or the residue of a migration that was never finished. The distinction matters before anyone touches referential integrity.
To resolve A day of history archaeology, or an hour with the original developer
-
What the two undeployed commits change
Production differs from `main`. Until deployment is reproducible, no change can be verified as safe.
To resolve Resolved by the stabilisation work below
05 Recommended plan
Four verdicts, because "everything needs work" is not a plan. Effort is given in engineer-days and assumes the stabilisation work happens first.
Fix now
| Item | Ref | Effort |
|---|---|---|
| Working, tested, off-host backups | R1 | 2 days |
| Idempotency on the payment webhook | R2 | 2–3 days |
| Rotate every credential in git history | R4 | 1 day |
| Error tracking and alerting | R6 | 1 day |
| Repeatable deploy from a pipeline | R5 | 3–4 days |
Fix soon
| Item | Ref | Effort |
|---|---|---|
| PHP 8.2 + Laravel 10 upgrade | R3 | 12–16 days |
| Restore CSRF on the four routes | R8 | 1–2 days |
| Eager-load the order history query | R7 | Half a day |
Don't touch
| Item | Ref | Effort |
|---|---|---|
| The pricing engine | — | It is ugly, it is correct, and it is covered by the only real tests in the codebase |
| The 11 foreign-key-free tables | — | Not until the unknown above is resolved |
Replace
| Item | Ref | Effort |
|---|---|---|
| The abandoned reporting VPS | R9 | Decommission after data export — 1 day |
| The hand-rolled CSV export | — | Only once its consumer is identified |
06 What we would do first
If this became a repair engagement, the first two weeks are already decided. None of it is feature work.
- Day 1 A real backup, taken off-host, and a documented restore performed against a scratch database. Until this exists nothing else is safe to touch.
- Days 2–3 Rotate every credential exposed in git history. Error tracking installed, so the fulfilment fatal stops being invisible.
- Days 4–7
Reproducible build and deploy from a pipeline. Reconcile production
against
mainand establish what is actually running. - Days 8–10 Idempotency on the payment webhook, with a replay test. Reconcile the three suspected duplicates.
- Day 10 The order history fix, because it takes half a day and it is the thing your customers actually feel.
At the end of two weeks the system is no longer capable of losing itself, and the upgrade can be scheduled as ordinary planned work rather than as an emergency.
About this document
This is the deliverable
A real Codebase Autopsy arrives in this shape and at roughly this length, with a 60-minute presentation to whoever is making the decision. It is written to be handed to a developer who has never met us, or to a board that needs a reason — which is why the executive summary leads with a verdict rather than with a methodology.
Note what it does not do: it does not recommend a rewrite, it does not find fault with the people who built the system, and it does not discover extra urgency conveniently sized to a follow-on contract. Two of the nine findings are half-day fixes your own team can do this week without hiring anybody.
What a Codebase Autopsy costsWant one of these for a system you're holding?
Send whatever access exists — or say honestly that you're not sure what you have, which is a normal place to start. You'll get a scope and a number before anything begins.
First reply usually within a few business days, from a person