A ten-year-old ERP, and an eight-hour fix list
- Client
- Zipper & fastener manufacturer, Portugal
- Sector
- Manufacturing
- Engagement
- Technical assessment
- Duration
- 2 weeks
- Scope
- 50,000 lines · 200 files · 50 database models
- Stack
- PHP 7.4 · MySQL 5.7 · Smarty
- On corvale.pt
- The business write-up
The challenge
A PHP monolith from 2015 running the whole plant: production orders, dyeing, galvanisation, assembly, stock. Smarty templates over MySQL 5.7, no original developer, no automated tests, no documented schema. Management needed two answers inside two weeks — whether the system is safe to keep, and what to fix first — and the failure mode of that engagement is not running out of time. It is answering confidently from a sample.
What we did
Comprehension was automated before judgement was applied. The codebase was indexed into a dependency graph with Graphify — 50,000 lines, 200 files, 50 database models — and agent-driven static analysis then ran across every endpoint and every query in parallel. That produces coverage rather than a sample: every entry point examined, instead of the dozen a reader would pick and hope were representative. Two weeks spent reading serially is the whole engagement spent on comprehension, with nothing left over for the part that needs an engineer.
Severity is a function of deployment. Rate findings against the topology you actually have, or you are generating a checklist with the client’s name on it.
This system sits on an internal network behind a VPN, and that single fact moves items up and down the list. Missing CSRF tokens is a priority on a public form; here the attacker has to already be inside the VPN and authenticated as a user of the system, at which point forging a request is not their best available move. It stays on the list with a fix and an estimate, and it does not displace password storage.
MD5 hashes go the other way: the closed network does not help at all, because the hashes are reversible in seconds by anyone who gets one query out of the database — and the credentials to run that query were in source, connecting as root. Two findings that compound into something worse than either, which is precisely what a per-row severity rating cannot express and what the report had to say in a sentence.
The deliverable is a prioritised list with every item sized in hours rather than graded. A grade cannot be approved, scheduled or assigned to anybody; eight hours can. It also exposes the assessment to being wrong in a way a letter grade never is, which is most of the value.
What we found
The code was better than feared. The risks were few, concentrated, and quick to close — against a rewrite that three separate people had already recommended.
| Area | Finding | Severity |
|---|---|---|
| Password storage | MD5 hashes, reversible in seconds | High |
| Database credentials | Hardcoded in source, connecting as root | High |
| Server stack | PHP 7.4 and MySQL 5.7, both end-of-life — no patch is coming for whatever is found in them next | High |
| Automated tests | None. Rated High because it sets the price of every other item: changing password storage here means changing it and then manually exercising every path that touches a login | High |
| Form protection | No CSRF tokens; low exposure behind the VPN, so a fix with an estimate rather than a priority | Medium |