The deliverable

A sample health check

This is the whole thing, not an excerpt — the report a health check produces, at the length and in the language it is actually written in. Read it before you decide whether one is worth booking.

Specimen This report is illustrative. It is not a past engagement, and the subject is not a real client — it is assembled from the failures this offer exists to find. The structure, language and level of detail are exactly what a real report carries.
Software Health Check

Risk report and modernization roadmap

SubjectWholesale distributor · 34 staff · Montréal
SystemOrder entry and inventory · in service since 2011
EngagementTier 2 · Health Check
Fieldwork9 days elapsed

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

Stabilize, then modernize

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.

Overall health 38 out of 100 — Fragile
38
/ 100
High risk
rescuable
  1. Security 14 · Failing
  2. Access 18 · Failing
  3. Data 22 · Failing
  4. Observability 26 · Fragile
  5. Runtime 32 · Fragile
  6. Obligations 38 · Fragile
  7. Correctness 44 · Fragile
  8. Source 52 · Worn
  9. Integrations 58 · Worn
  10. Architecture 76 · Sound
Healthy baseline

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.

order-entry (PHP 5.6) ──┬── MySQL 5.5 ├── carrier rates API (v2, sunset 2027-07) ├── payment gateway └── nightly CSV ──▶ accounting package Access pricing DB · 2 workstations · not connected · duplicates rules held 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.

Risk register
RefAreaWhenFindingEffort
F-01SecurityNowAdministrative interface reachable from the public internet, passwords stored as unsalted MD5.Days
F-02DataNowBackups are written to the same server they protect, and no restore has ever been performed.Days
F-03AccessNowOne contractor holds the only credentials for the domain registrar and the payment gateway. Last contact was fourteen months ago.Hours
F-04RuntimeSoonPHP 5.6 reached end of life in December 2018. The current host has announced removal of the runtime.Weeks
F-05IntegrationsSoonThe carrier rate lookup uses an API version with a published sunset date eleven months out.Days
F-06ObservabilitySoonNo monitoring or alerting. Outages are discovered when a customer telephones the office.Days
F-07SourceStructuralProduction contains eleven files that exist in no repository. The deployed system and the source have diverged.Weeks
F-08CorrectnessStructuralNo automated tests. Every change is verified by hand, which is why changes are batched and rare.Months
F-09FrictionStructuralPricing rules are duplicated across the application and a separate Access database, and must be edited in both.Months
F-10ObligationsStructuralCustomer personal information has accumulated since 2011 with no retention policy and no inventory.Weeks

7 — Two findings in depth

F-01 Now

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.

app/auth/login.php:44 $hash = md5($_POST['password']); server access log, 30 days 4,912 requests to /admin/ from 214 distinct addresses (none from the office's static IP)
If nothing is done This is the finding most likely to end in a call to your insurer. An intruder with an admin session can read every customer record, alter pricing, and export the order history — and because there is no logging beyond the web server, you would be reconstructing what happened from guesswork.

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.

F-02 Now

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.

/etc/cron.daily/db-backup → /var/backups/orders-*.sql.gz retention 14 days · no off-server copy · no restore log restore into isolated environment 4h20m to a working system 2 columns missing: orders.dispatch_note, customers.tax_exempt_ref
If nothing is done Anything that takes the server also takes the backups — a disk failure, a ransomware event, a billing lapse. The recovery you are relying on does not exist yet; what exists is a file that has never been tested, and it is four hours from being useful even when it works.

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.

DecisionIf you act nowIf you wait
CredentialsAn 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.
RuntimeA 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 duplicationOne 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.

PhaseWhat it coversDurationBand
1 · StabilizeF-01, F-02, F-03, F-06 — the floor: access, backups, admin exposure, alerting.3–4 weeks$9,000 – $13,000
2 · Reduce riskF-04, F-05, F-07 — runtime migration, carrier API version, source reconciliation.5–7 weeks$18,000 – $26,000
3 · BypassF-09 — pricing moves out to a service both systems read. Neither is rewritten. See section 10.4–6 weeks$14,000 fixed
4 · ModernizeF-08, F-10 — a test suite around the order path, and a retention policy.Phased, 6–9 monthsQuoted 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.

BEFORE AFTER order system ──▶ pricing rules order system ──▶ v_price_rules ─┐ ▼ Access file ──▶ pricing rules Access file ──▶ linked table ─▶ pricing (edited twice, service drifts apart) (owns the rules)

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.

If that looked useful

We can write one about your system.

Fixed scope, one to two weeks, from $1,500. You keep the report whether or not you engage us for anything that follows — it is written so you could hand it to a different developer tomorrow.