Most small business sites are not hacked by someone targeting them. They are hacked by scanners that sweep the whole internet for the same ten mistakes. That is good news: closing those ten mistakes is a bounded amount of work, and we run the same pass on every site we ship, from restaurant platforms to our own.
Injection: parameterise everything
Every database query on every site we ship is parameterised. Not "the important ones", all of them, because the one you skip is the one the scanner finds. If a codebase uses string concatenation anywhere near SQL, that is finding number one in the audit and it is fixed before anything else.
Authentication: limit the tries, hash properly, rotate the session
Login endpoints get rate limits and lockouts, passwords are hashed with bcrypt, password resets use single-use hashed tokens with an expiry, and the session is rotated on privilege change. Where an admin panel controls a business, we add two-factor. None of this is advanced; the absence of any one of them is how admin panels end up on someone else's screen.
Access control: the server decides, per request
Hiding a button is not access control. Every role-gated route checks the role server-side on every request, and object access checks ownership, because the classic small-site hole is changing an ID in the URL and reading someone else's record. This is the first thing we try in any audit, ours or a client's.
Headers and transport: the cheap layer people skip
HTTPS forced with HSTS. A content security policy without unsafe-inline for scripts, using hashes for anything inline. nosniff, a restrictive referrer policy, frame-ancestors locked down. This layer costs an afternoon and converts a whole family of injection and clickjacking attacks into no-ops. On our own site the CSP carries exactly two script hashes; anything injected simply does not execute.
Secrets and surface
No credentials in the repository, ever; environment files stay on the server and out of deploys. Admin panels do not need to be discoverable, and nothing that is not meant to be served should sit under the web root. Dependency updates are boring until the day they are the story, so they happen on a schedule, not on news.
Then verify it, from outside
The pass ends by attacking the deployed site, not the codebase: authentication flows, access control with a second account, header presence, TLS configuration, error behaviour. A written report goes to the client either way, because "we checked and here is what we found" is worth more than any badge. The whole exercise fits inside a project budget, and it is the difference between hoping and knowing.