SEC-006
Suspend an account without deleting it
A status column, one check in the session guard, and a shadow-banned tier that keeps working while earning nothing.
Deleting an abusive account destroys the evidence and teaches the operator exactly what tripped your detection. They sign up again with the pattern removed.
A status column gives you the middle ground. Suspended stops them at the door. Shadow-banned is the useful one: everything works, nothing accrues, and they carry on demonstrating the technique while you watch.
Whatever you do, keep the ledger. An account with no balance and no access still needs its history intact for reconciliation.
// status: 1 active, 0 suspended, 2 shadow-banned
function enforce_status(array $user): void
{
if ((int) $user['status'] === 0) {
session_destroy();
http_response_code(403);
exit('This account is suspended. Contact support if you think that is wrong.');
}
// Status 2 falls through: the site behaves normally.
}
function may_accrue(array $user): bool
{
return (int) $user['status'] === 1; // shadow-banned earns nothing, silently
}
function set_status(PDO $pdo, int $userId, int $status, string $reason, int $adminId): void
{
$pdo->prepare('UPDATE users SET status = ? WHERE id = ?')->execute([$status, $userId]);
$pdo->prepare(
'INSERT INTO admin_log (admin_id, action, target_id, detail, created_at)
VALUES (?, "set_status", ?, ?, NOW())'
)->execute([$adminId, $userId, $status . ': ' . $reason]);
if ($status !== 1) {
// Kill any live session by rotating the per-user token the session carries.
$pdo->prepare('UPDATE users SET session_epoch = session_epoch + 1 WHERE id = ?')->execute([$userId]);
}
}
Using it
Store a session epoch on the user and compare it on every request. Without it a suspended user stays logged in until their cookie expires.
Record a reason on every status change. Six months later "why is this account suspended" is a question you will not be able to answer from memory.
What bites people
Shadow-banning is not a substitute for acting. It buys observation time; leave it running forever and you are just paying to host someone's experiments.
Never expose the status in a public profile or an error message. The moment it is visible, it is a signal to sign up again.