Access Decided by a Value in the Browser
Whether a user can see or do something is decided by a value stored in their own browser.
Why it matters
Anyone can open DevTools and run localStorage.setItem('isAdmin', 'true') or invent a token. Hiding a page or a button on the client is fine for convenience, but if the server does not check the session on every request, the client-side check is the only lock, and it is on the inside of the door.
What it looks like
This is the shape of code that triggers the rule. AI tools produce it because it works, and nothing tells them it is unsafe.
useEffect(() => {
if (!localStorage.getItem('token')) {
navigate('/login');
}
}, []);The smallest fix
// Keep the client check for UX, but make the server the real gate:
// API route / server action:
const session = await getSession(req); // verified on the server
if (!session) return res.status(401).end();
if (session.user.role !== 'admin') return res.status(403).end();The better fix
If you have a few more minutes, this is the approach that holds up as the app grows.
// Use httpOnly cookies set by the server for the session, so the
// browser never holds a token JavaScript can read, and check the
// session in middleware for every protected route.Let your AI tool fix it
When the scanner finds this in your project, it fills in the file and line for you. This is the prompt it gives you to paste into Claude Code, Cursor or whatever you use.
How to check the fix worked
1. In DevTools, set the storage value to look logged in or admin, then call a protected API route directly. It must return 401 or 403. 2. Log in normally and confirm the same route returns data.