Not a brochure
A small business application on ASP.NET Core does not need a twenty-control framework pasted into a proposal. It needs a short list you can finish, and a night you can survive. Most of the damage we see is not a novel exploit. It is a restore that was never practised, a production password in a group chat, and a single laptop that knows how to get onto the server.
This list is for the system people already work in — orders, jobs, a portal, a back-office — not for a five-page marketing site. The site still needs HTTPS and a locked admin. The application needs more because it holds other people’s money, stock, and files. If you are choosing the stack, why ASP.NET Core for business websites is the public-site argument. This is the list we walk after the login exists.
We are not going to sell you a fear. We are going to name the items that turn a Tuesday outage into a week, and the items that turn a staff exit into a lockout. Do those first. Pen-test theatre can wait until you have restored a backup in anger, once, on purpose.
Backups you have restored
A backup you have never restored is a rumour. The job is not “we take backups”. The job is: last month’s backup, restored onto a box that is not production, application boots, a known order is there, a known file opens. Write the date you last did that. If you cannot write the date, you do not have a backup. You have a folder that might be empty, encrypted by the same ransomware, or a week behind because the task has been failing quietly.
Restore includes the database and the files the application thinks are on disk. A perfect SQL backup with a dead upload folder is a half restore. If invoices live as PDFs beside the row, practise getting both. If the host takes snapshots, know whether a snapshot is application-consistent or just a disk at a random second. Ask once. Write down the answer.
Know who can run the restore when the person who set it up is on a flight. The 2 a.m. call is not the time to discover that the only backup password is in a browser profile on a locked laptop. Two people. Written steps. A test restore on a calendar, not “we should do that sometime”.
Off-site means off the same account that runs the server. A copy on the same VPS is a copy that dies with the VPS. Object storage on another account, or a box in another building, is the minimum. Encryption keys for that copy must not live only on the machine you are trying to replace.
Who has production
Write the names. RDP, SSH, panel, database admin, the DNS registrar, the mailbox that can reset every other mailbox. If the list is “the vendor and Ramesh”, you have a single point of failure with a contractor number. If the list is “the WhatsApp group”, you do not have production access. You have a leak.
Named accounts, not a shared admin. When someone leaves — staff or vendor — you disable a person, not a password everyone still knows. ASP.NET Identity already knows how to lock a user. Use it for the application. Use something equally boring for the host: one login per human, MFA on the panel, no leftover “temp” accounts from the go-live week.
A vendor who insists on a standing production login after handover is asking to remain a single point of failure. Time-limited access for a change is fine. A permanent shared password is not. If they built the system, you still need a way in when they do not pick up. That is part of .NET development handover, not a favour you request later.
Hosting bills and registrar logins belong to the company, not to a personal Gmail. We have seen domains that could not be renewed because they sat on a developer’s card from 2019. That is a security incident with a calendar. Put the registrar on a company mail and a company payment. Write where the 2FA device lives.
Shared passwords and the inbox that owns the domain
Shared passwords are how you get a former intern still able to open the customer list. If two people need the same secret, use a password manager with a vault the company owns — not a sheet called passwords.xlsx on the desktop, and not a pinned WhatsApp message. Rotate when someone leaves. Rotate when a laptop is lost. Rotate when you are not sure.
The mailbox that receives password resets is production. Treat it that way. MFA. A short alias list. No forwarding to a personal account “just in case”. If that mailbox is also the registrar contact, you have one inbox that can give the company away. Two people should be able to open it. A third person should know that those two exist.
Connection strings and API keys do not belong in the git repo, in a screenshot, or in a support ticket. User-secrets on a workstation, environment variables or a store on the host, and a rotate path. If you found a key in source this month, rotate it. Do not “clean the file” and leave the old key live. The copy in git history is still a copy.
Payment and GST portal logins are not “IT”. They are the business. Still: they should not be the same password as the application admin, and they should not sit in the same chat as the weekend cricket score. Boring separation is the whole control.
The 2 a.m. call
Write the sheet while you are awake. Who gets the call. How they get onto the host. Where the logs are. How to put the site in a holding page without deleting the application. How to restore. Who tells customers if payments are down. Who can revoke a stolen session or disable a user who is deleting records.
The sheet is one page. If it is a wiki only one person can find, it is not a sheet. Print it or put it where the second person already looks. Include the host ticket URL and the account that pays for support. Include the last restore date. Include “do not upgrade the framework at 2 a.m.”
Logs should help a human at night. Correlation ids on requests. The user id, not only an IP. The last deploy time. A log that is 400 MB of framework noise with no request id is a second outage. ASP.NET Core’s structured logging is enough if you keep a request id and do not swallow exceptions into an empty catch.
Know how to take a backup before you “just restart”. A restart that completes a half-written migrate is how you invent a Monday. If you cannot take a quick copy, you are not ready to experiment at night. Say so on the sheet. Sleep and do it at 9 a.m. with a second person on the line.
The application surface that is actually yours
HTTPS everywhere, including the admin and the health URL you thought nobody knew. Cookies marked securely. The antiforgery token on every POST that changes something. You already get a lot of this if you did not switch it off. Do not switch it off because a page “needed” to work in an old WebView.
Authorisation is not authentication. Logging in is not the same as being allowed to open that order. Check the record belongs to the user’s branch, customer, or role — in the query, not only in the menu. A hidden button is not a control. A small .NET app is where this is usually missed: the list is filtered, the details URL is not.
File uploads are a favourite. Limit type and size. Store outside the web root or behind a handler that does not execute what was uploaded. Do not trust the extension the browser sent. If staff upload supplier PDFs, that is enough of a reason to treat the folder as hostile.
Admin URLs should not be the first path a scanner tries and also the only path you have. You do not need security-through-obscurity as a strategy. You do need MFA on anyone who can export the customer table, and a lockout on password guessing. If the admin is still /Admin with a shared password from go-live, that is this week’s job, not a phase-two story.
Dependencies and the host you stopped looking at
A small application still has a package graph. Look at it when you ship, and once a quarter. You do not need to chase every advisory the day it lands. You do need to know you are not two years behind on the framework and the identity package. “We never touch it because it works” is how you inherit someone else’s hole.
The host needs the same glance. Panel MFA. SSH keys, not a password that was in the onboarding email. Firewall that is not “all ports to the world because a plugin needed it”. If this application is an old Framework site you have not moved, the boring list is longer and the first project may be legacy cloud modernisation rather than another feature. An unpatched box with a custom app on it is not a small-risk environment. It is a quiet one.
SQL access from the web is enough. SQL access from the internet is a choice you should be able to explain. If a developer “needed SSMS from home”, give them a tunnel or a jump box, then close it. Leaving 1433 open because it was convenient in week one is how a small app becomes a large incident.
What “small” does not excuse
Small does not mean one person holds every secret. Small does not mean backups are optional until you have “real” customers. Small does not mean the intern’s laptop is a valid staging environment for live data. Live data on a personal machine is a leak you have already had; you just have not noticed.
Small does mean you can finish this list in a week of calendar time if someone owns it. A security programme with a steering committee is how a twelve-person firm avoids the restore test. Do the restore. Name the people. Rotate the shared password. Write the 2 a.m. page. Then stop decorating.
If you do not have a developer on staff, this list is still yours. A partner can run the restore with you watching. They should not be the only person who can. Build that into the retainer or the handover. We treat it as part of shipping .NET work, not as an optional extra after the screens look right.
A one-page sheet for the next person
- Last successful restore: date, who ran it, what was checked.
- People with host, database, DNS, and registrar access — named, not roles.
- Where secrets live, and who can rotate the model key, the payment key, and the SMTP password.
- How to disable a user and expire sessions.
- Where logs are, and the last deploy time.
- Who to call, and who not to wake for a slow page.
If that page does not exist on Friday, write it before you add a feature on Monday. Features do not help at 2 a.m. The sheet does. When the application is old enough that you cannot add MFA or a proper backup without a vendor change request, say that out loud. You are then on a modernisation path, not a sticker path. The boring list is how you notice.
Revisit the sheet when someone leaves, when you change host, and after the first real scare — even if the scare was a false alarm. A list that is six months stale is how the new person inherits a story instead of a system. That is the whole discipline. It is not exciting. It is why the application is still there in the morning.