Risk report and modernization roadmap
1 — Scope and method
We were given read access to the source repository, the production server, the MySQL database and thirty days of web-server logs, and we interviewed the operations manager, two order-entry staff and the owner. We performed one restore of the most recent backup into an isolated environment.
We did not examine: the accounting package, the two workstations running the Access pricing database, or the warehouse scanners. No changes were made to any running system. This is not a penetration test — security below is a posture review conducted with source access.
2 — Executive summary
The system works. It has processed orders every business day for fourteen years and the people who use it are fast on it. Nothing in this report suggests replacing it, and we would advise against anyone who proposes that without reading it first.
What it does not have is a floor under it. Three findings are immediate: the administrative interface is reachable from the open internet with password storage that has been considered broken since roughly 2012; the backups are written to the same machine they protect and have never been restored; and the credentials for your domain and your payment gateway are held by one contractor nobody has spoken to in fourteen months. Any one of those, on an ordinary bad day, is a week of lost trading.
If nothing changes: the most likely first event is not a dramatic one. It is the hosting provider withdrawing PHP 5.6 on a date they choose, which forces an unplanned migration of a system nobody can currently rebuild from source. The second most likely is a credential you cannot recover because the person holding it has moved on.
3 — Verdict
The architecture is plain and the business logic is sound. Put a floor under it in the next six weeks, then replace the pricing duplication and the runtime in phases across the following year, while it keeps running. There is no case here for a rewrite, and no case for leaving it alone.
4 — Health at a glance
One picture of the whole inspection. A longer bar is a healthier area, and the dashed marker is where a well-maintained system of this age would sit.
rescuable
- Security 14 · Failing
- Access 18 · Failing
- Data 22 · Failing
- Observability 26 · Fragile
- Runtime 32 · Fragile
- Obligations 38 · Fragile
- Correctness 44 · Fragile
- Source 52 · Worn
- Integrations 58 · Worn
- Architecture 76 · Sound
Zero to a hundred per area, worst first. The overall score is the mean of the ten — not a weighting you cannot check. Scores exist to focus the conversation, not to grade anyone's past work.
5 — System at a glance
One PHP application on one virtual server, talking to one MySQL database, with three outside connections: a carrier rate lookup, a payment gateway, and a nightly export the accounting package reads. Alongside it, off to the side and not connected to anything, an Access database on two office workstations holds the customer pricing rules — which also exist, separately, in the application.
6 — Findings
Ten findings, ordered by when the decision is due rather than by how alarming they sound. Effort is a band, not an estimate.
| Ref | Area | When | Finding | Effort |
|---|---|---|---|---|
| F-01 | Security | Now | Administrative interface reachable from the public internet, passwords stored as unsalted MD5. | Days |
| F-02 | Data | Now | Backups are written to the same server they protect, and no restore has ever been performed. | Days |
| F-03 | Access | Now | One contractor holds the only credentials for the domain registrar and the payment gateway. Last contact was fourteen months ago. | Hours |
| F-04 | Runtime | Soon | PHP 5.6 reached end of life in December 2018. The current host has announced removal of the runtime. | Weeks |
| F-05 | Integrations | Soon | The carrier rate lookup uses an API version with a published sunset date eleven months out. | Days |
| F-06 | Observability | Soon | No monitoring or alerting. Outages are discovered when a customer telephones the office. | Days |
| F-07 | Source | Structural | Production contains eleven files that exist in no repository. The deployed system and the source have diverged. | Weeks |
| F-08 | Correctness | Structural | No automated tests. Every change is verified by hand, which is why changes are batched and rare. | Months |
| F-09 | Friction | Structural | Pricing rules are duplicated across the application and a separate Access database, and must be edited in both. | Months |
| F-10 | Obligations | Structural | Customer personal information has accumulated since 2011 with no retention policy and no inventory. | Weeks |
7 — Two findings in depth
Administrative interface open to the internet, passwords unsalted MD5
The /admin path is reachable from any address with no network restriction, and staff passwords are stored as unsalted MD5 hashes. Nine of the eleven accounts still active use a password that appears in public breach corpora. Two belong to people who left the company in 2021 and 2023.
Remediation. Restrict /admin to the office address and a VPN, force a password reset, disable the two departed accounts, and move password storage to a modern hash on next login. Days, not weeks, and it does not wait for anything else in this report.
The backup is on the machine it is protecting, and has never been restored
A nightly mysqldump writes to /var/backups on the same virtual server as the database, with fourteen days retained. There is no off-server copy. We performed what we believe is the first restore since the system was built: it succeeded, and took four hours and twenty minutes, most of it spent discovering that the schema in the dump predates two columns the current application requires.
Remediation. An off-server copy to object storage with a separate credential, a documented restore procedure, and a restore rehearsed twice a year. A backup nobody has restored is a hypothesis, not a backup.
8 — Acting versus waiting
The three decisions in front of you, and what each costs on either side. Waiting is a choice with a price, not the absence of one.
| Decision | If you act now | If you wait |
|---|---|---|
| Credentials | An afternoon of transfers, while the contractor still answers email. | A registrar dispute, or a payment gateway you cannot reconfigure. Weeks, and some of it may not be recoverable at all. |
| Runtime | A planned migration on your own schedule, tested alongside the running system, roughly three weeks. | The same work, unplanned, on the host's date, with the business stopped while it happens. |
| Pricing duplication | One source of truth, phased in over three months while both systems run in parallel. | Continued double entry, and the quoting errors it causes — which staff already correct by hand and no longer report. |
9 — Roadmap
Four phases. Each is independently valuable and independently stoppable — you can do phase one and decline phase two without having wasted the money. Phase three is a bypass rather than a rewrite, which is why it carries a fixed price where the others carry bands.
| Phase | What it covers | Duration | Band |
|---|---|---|---|
| 1 · Stabilize | F-01, F-02, F-03, F-06 — the floor: access, backups, admin exposure, alerting. | 3–4 weeks | $9,000 – $13,000 |
| 2 · Reduce risk | F-04, F-05, F-07 — runtime migration, carrier API version, source reconciliation. | 5–7 weeks | $18,000 – $26,000 |
| 3 · Bypass | F-09 — pricing moves out to a service both systems read. Neither is rewritten. See section 10. | 4–6 weeks | $14,000 fixed |
| 4 · Modernize | F-08, F-10 — a test suite around the order path, and a retention policy. | Phased, 6–9 months | Quoted per phase |
10 — Phase three in detail: a bypass, not a rewrite
The problem. Your customer pricing rules exist in two places — inside the order system and in the Access file on the two office workstations — and a change has to be made in both. When they drift, the quote is wrong. Staff already correct this by hand often enough that they have stopped mentioning it, which is how we found it.
Why we are not fixing it where it lives. Pricing touches every order. It is the highest-risk module in the system to edit and the one the business can least afford to have wrong for a morning, and it sits in PHP that is going to be migrated in phase two anyway. Changing it in place means taking the largest risk in this report at the worst possible moment.
Why we are not rewriting the order system. Because it does not need it. The rest of it works, and section 3 says so.
What gets built. One small service that owns the pricing rules, with a screen your staff use to edit them. It writes v_price_rules, which the order system already reads, and the table the Access file already links to. Neither of them learns that anything changed. No PHP is edited. No VBA is edited.
Who owns the truth. From cutover, the pricing service. Editing rules anywhere else stops being possible — the two old edit paths are made read-only in the same change, which is the part that actually fixes the drift. A reconciliation report runs nightly for the first ninety days and emails anything that disagrees.
How long it stays. Permanently. This is not a staging post toward replacing the order system; it is where pricing lives now. We document it and hand it over on the same terms as everything else. If phase four ever does replace the order system, the pricing service is the one part that does not need touching.
If it stops. The view and the linked table keep their last written values, so the order system keeps quoting at yesterday's prices rather than failing. That is a deliberate property of reading through a seam instead of calling an API at quote time.
Fixed price, $14,000, four to six weeks. We can commit to that number here, before starting, because this is a new build against an interface we have already read — the uncertainty that stops us pricing phases one and two is not present.
11 — The first thirty days
If you do one thing from this report, do this: recover the registrar and payment gateway credentials while the contractor still answers email, restrict the admin interface to your office, and put a copy of the backup somewhere the server cannot reach.
That is roughly a week of work, it costs a fraction of phase one, and it removes the two findings most likely to become an emergency. It also requires no decision about the system's future — which is the point. Nothing in it is wasted whichever verdict you eventually act on.
12 — What we did not flag
The code is not elegant and we are not going to bill you to make it so. It is consistent, the naming is honest, and someone competent could learn it in a fortnight — which is worth more than any refactor we could sell you.
MySQL 5.5 is old, but it is doing nothing wrong here and it is not the thing that will break first. It moves with the runtime in phase two rather than becoming its own project.
The Access database is not a scandal. It is where the pricing rules were easiest to edit, and it has been maintained carefully. The problem is that the rules exist twice, not that Access is involved.
13 — Appendices
A real report continues here with the dependency and vulnerability inventory, the full route and file listing, the interview notes, and version evidence for every claim above. Nobody reads it; its presence is what makes the rest checkable.