Every production system has accounts nobody created on purpose. The demo tenant from the launch. The test user an engineer made to reproduce a bug in 2023. The seed data that shipped with the first deployment and was never removed. The integration account whose password is in a runbook.
None of them appear on an access review, because access reviews enumerate employees. They are users, in the product, with real permissions, and they are the accounts an attacker finds first.
This post is how they accumulate and how to find yours. For whoever would be asked to explain one after an incident.
Where they come from
Seed data. A fixtures file that creates an administrator so the application is usable on first run. It ships to production, and the credentials are in your repository, which means they are known to everyone who has ever cloned it.
Demo and sales tenants. Created for a demonstration, populated with plausible data, shared credentials, never expired. Frequently they have elevated permissions so the demo shows the full product.
Debugging accounts. Made during an incident to reproduce a customer's problem, often inside the customer's tenant, and left because removing it was not on anyone's list.
Integration accounts. A shared login for a tool that did not support proper authentication when it was set up. The password is in a runbook, a password manager shared with a team, and two people's notes.
Load testing users. Created in bulk, with predictable names and passwords, because that is what makes a load test scriptable.
Abandoned trials. In a self-service product, every trial is an account. Some have data, some have API keys, and none of them are being watched.
Why they are worse than a stale employee account
Employee accounts are covered by joiner-mover-leaver processes, appear in your identity provider, and show up in a quarterly review. These do not appear in any of that, because they live in the product's own user table rather than in your directory.
They also tend to have three properties that make them attractive: weak or shared credentials, permissions chosen for convenience rather than need, and no owner who would notice unusual activity.
And unlike an employee account, nobody is going to report that it has been compromised, because nobody is using it.
Find them
Query the product's own user store rather than your directory. The patterns are recognisable:
-- accounts that have never authenticated, or not for a long time
SELECT id, email, created_at, last_login_at, role
FROM users
WHERE last_login_at IS NULL OR last_login_at < now() - interval '180 days'
ORDER BY role, created_at;
-- the obvious names
SELECT id, email, role FROM users
WHERE email ~* '(test|demo|sample|qa|staging|load|temp|admin|service|integration)'
OR email ~* '@(example|test)\.(com|org)$';
-- elevated accounts, all of them, read the list by hand
SELECT id, email, role, created_at FROM users WHERE role IN ('admin','owner');
The third query is the one worth doing regardless of the others. On most products it is a short list, and reading it line by line usually produces at least one "who is that".
Then check for shared credentials: accounts whose password was set once and never rotated, accounts with no multi-factor enrolment, and accounts authenticating from many different addresses, which is the signature of a shared login.
What to do with each
Delete rather than disable where you can, so it cannot be re-enabled quietly. Where deletion breaks referential integrity, disable, rotate the credential, and record why it survives.
Give every remaining one an owner and an expiry. Same rule as contractor access and feature flags: the unsafe state should require an action to persist, not to end.
Convert integration accounts to proper service credentials. A scoped API key or a service account with no interactive login, rotated, attributed to a system rather than to a shared inbox.
Remove seed administrators from the code path, not just from the database. If the fixture still runs on a fresh deployment, the account comes back the next time someone provisions an environment.
Treat demo tenants as production. They contain data, they are reachable, and they are indexed. If they must exist, scope them to a separate tenant with no access to anything else, and rotate their credentials on a schedule.
Prevent the next batch
Make test accounts expire by construction. An account created with a flag that disables it after thirty days is one nobody has to remember to remove.
Block the obvious defaults. Refuse to start in production with a seeded administrator password, or with any credential matching a known default. This is one check at boot and it prevents a whole category.
Alert on privileged account creation. Any new account with an elevated role should notify someone. It is a rare event in most products, and it is exactly what an attacker does after gaining access.
The concession
Some of these accounts are load bearing. A demo tenant that sales uses weekly, an integration account that three systems authenticate with, a support account that lets your team reproduce customer issues. Deleting them on a security tidy-up breaks work and teaches people to route around the next audit.
So the objective is not zero. It is that every one of them is known, owned, scoped, and credentialed properly rather than shared. An integration account with a rotated key and a named owner is fine. The same account with a password in a runbook and administrator rights is the finding, and the difference is an afternoon.
The implication
Your access review covers the directory. Your product has its own user table, and the accounts in it that nobody created deliberately are the ones with the weakest credentials and the least attention.
Run the elevated-accounts query against production. Reading that list out loud, and naming an owner for each, is a ten-minute exercise that most teams have never done.