A week without new features: a module that tracks every file with three checks, four security rounds, and every secret moved out of the web root.
Every project reaches a point where carrying on building is no longer the sensible thing to do. On the booking platform I have been working on this summer, that point came at the end of July. Six modules were live, the system was running, and I noticed I was spending longer and longer working out what else depended on the thing I was about to change.
So for a week I built nothing new. Instead: a module that keeps track of the state of the code itself, four security rounds, and everything that needs to stay secret moved out of the web root. Here is what that produced.
Three checks per file
The largest piece is a module I call integrity management. It inventories the whole project like a file explorer: path, size, modification date, permissions, a hash of the contents, and whether the file is in version control.
For every file it tracks three checks separately. Is this dead code? Does it contain deprecated or needlessly convoluted code? And is it safe? Only once all three are signed off does the file turn green. Change the contents afterwards and the hash no longer matches, the sign-off drops, and all three steps have to be redone.
That last part is the whole point. A review only says something about the version you read at the time. Intrusion detection has worked this way for thirty years: you deliberately approve a state and get told the moment that state changes. I took that model and added the quality checks to it, because those go stale at exactly the same rate.
The scanner runs nine rule packs: exposure, permissions and provenance, secrets, backdoors, injection and XSS, access control, deprecated PHP, the project's own house rules, and dead code. Deleting is only possible through a button that first stores a copy in an archive outside the web root.
The real work was in the false positives
The first run over the actual code produced 3,578 findings, 48 of them high risk. Useless. At those numbers nobody looks at the list any more, and then a scanner is worse than no scanner.
Two days of tuning brought it down to 518 findings, without losing a single genuine one. The one high-risk finding left standing was correct, and it was closed straight away. A test file full of deliberate mistakes is still caught in full.
What went wrong along the way is instructive. A pattern looking for `require` also caught the word `required`, and reported fifteen innocent lines as remote code execution. A rule looking for `${var}` fired on JavaScript inside a template, where that same notation means something entirely different. And eighteen handlers were flagged for a missing protection that was already handled centrally, one layer up.
The lesson I am keeping: a rule with a prefix and a free-floating tail is dangerous. Run every new rule across the whole project and look at what it catches before you switch it on.
Four rounds past the front door
Alongside the scanner I walked through the system myself four times, each time with a different lens.
The first round covered admin actions with no check of their own. The second covered the public front end. The third found raw SQL that could get in through a booking field, and an invoice endpoint sitting wide open. The fourth found two places where amounts could be requested without authentication, plus three things I had broken myself in round three.
That last sentence is there on purpose. In work like this you introduce new mistakes, and the only remedy is to look again. Login got throttling, and write actions via GET got a token check. More than thirty admin actions gained a role check; until then they relied on nothing more than "is logged in".
Keys out the door
The platform used a single key for too many things at once: sessions, links in emails, password recovery. That is comfortable right up until the day one of them leaks.
That key has now been split. There is a separate, long key for signing links, with HMAC and a timing-safe comparison. The migration ran in three steps, with a fallback, so existing customer links kept working while nothing was written against the old key any more. Once the last user was across, the old one could go.
In the same week all legacy passwords were converted to bcrypt in blocks, for customers, admins, owners and reviewers. And the historical card data was cleared out: fields removed from the forms, decryption queries deleted, old values emptied. Data you do not keep cannot leak.
Everything secret moved outside
The configuration holding the database credentials, the log files, the backups, the migrations and the documentation all sat inside the web root. With the right URL and a little patience, files like that are often simply downloadable.
They now sit outside the web root, with a fallback so an installation that has not been moved yet keeps working. The deployment mirrors the migrations to the new location and clears out the old one. Logs rotate and have a retention period. The `.git` directory is closed off.
The nice part is that integrity management now checks whether that move actually succeeded. The server tab shows whether the folders really sit outside the installation and whether any log files are still in the web root. No SSH needed, just there on screen.
Also: five seconds found
Along the way I built in a measurement that records slow requests instead of guessing about them. That paid off twice straight away.
The Changes page ran 6,489 queries and now runs 3. And somewhere there was a consistent five-second delay nobody could place. It turned out to be the class autoloader, repeatedly looking for a file that did not exist and only giving up after trying every possible path.
You do not find that sort of thing by thinking harder. You find it by measuring.
What I am taking from this
A review has a shelf life. Without a hash you cannot tell whether your verdict still applies to the current contents. With one you can, and that is the difference between a checklist and a system.
A noisy scanner is worse than no scanner. You get used to red lines, and then you stop seeing the real ones. The time spent tuning rules is the investment that makes the tool usable.
Maintenance is work, not leftovers. A week without new features feels unproductive, until you look back and see what got closed.
Read more
- Two months of platform work: six modules now live, the modules this maintenance followed
- Website speed and conversion, step by step, measure before you optimise
- See all completed projects, web platforms and automation solutions
Bert