ITEM-6795 came in as a login complaint. An HTO admin was getting blocked mid-session, not at the login screen, but a few clicks into the control panel. The IPFilter table had a speedFlag set on the account. From the outside it looked like the filter doing exactly what it was built to do. Somebody clicked too fast, got flagged, got throttled.
I opened the ticket expecting a threshold problem. Maybe the daily-hit ceiling was too low for an active power user. I pulled the IPFilter row, looked at the speedFlag state and the timestamps, and did the math. The clicks were fast, but not attack-fast. Around 8 to 12 requests in a short window.
That was not abuse. That was someone navigating.
the filter was counting our own redirects as hits
HTO’s admin panel does 302 redirects. A lot of them. The pattern is standard. Click a menu item, land on a routing page, get bounced to the actual destination. Two HTTP requests, one user action, under 500 milliseconds. For a quick-click admin, that chain fires repeatedly. Four menu clicks becomes eight or twelve logged requests on the same IP in a few seconds.
The IPFilter stored procedure did not know about this pattern. It counted hits. A hit was a hit. If two hits arrived within the threshold window from the same IP, the speed counter incremented. If the counter crossed the daily limit, speedFlag got set. Nobody wrote a carve-out for the app’s own internal navigation because when the filter was written, nobody had thought to ask whether normal in-app routing would ever look like a burst.
The routing model evolved. The filter did not.
what claude missed first
I was working this with Claude. The initial read of the IPFilter code came back clean. Stored procedure logic correct as written, thresholds matched the documented intent, no SQL bugs. Claude flagged the speedFlag threshold as potentially aggressive but framed it as a tuning question, not a classification problem.
That framing was wrong. The answer to “the threshold is too tight” is to raise the number. The actual problem was that the number was counting the wrong things. You can raise the ceiling as high as you want and still block a power user who navigates in bursts, because every admin click through a redirect chain keeps filling the bucket.
The classification gap was the bug. The threshold was a symptom.
the four-part fix
Once the root cause was clear, the fix had four moves. They had to land together, because a partial fix would have left the underlying count wrong.
First, add a request classification layer. The stored procedure had no concept of request origin, treating all hits the same regardless of where they came from. It needed to distinguish internal navigation from inbound probing before deciding whether to increment the speed counter.
Second, change the stored procedure to skip the speedFlag increment for the internal-navigation class. Navigational hits still get logged, but they no longer feed the counter that gates account state.
Third, add an auto-clear for speedFlag entries set while the account was otherwise idle. If the flag was set by a burst that turns out to have been navigation, and the account goes quiet for a defined window afterward, the flag clears itself rather than persisting until someone notices.
Fourth, raise the daily threshold to give legitimate power users more headroom before any flag fires at all. This one is the tuning move that would have been insufficient alone. With the classification fix in place, it becomes a sensible backstop instead of a band-aid on the wrong problem.
old defensive code does this quietly
The IPFilter system is old. It was probably right when it was written. The app’s navigation pattern at the time was simpler, slower, less redirect-heavy. The heuristic made sense against the traffic model it was built for.
What happens to a defensive rule over time, when nobody revisits it, is that the app evolves and the rule does not. Navigation patterns change. Admin workflows get more complex. The number of redirects per user action grows as the control panel grows. The filter keeps counting the same way. One day a ticket comes in because an admin got blocked, and it turns out the routing has been teaching the filter the wrong lesson for years.
I do not know when the tipping point happened. ITEM-6795 is the first customer-visible case I have, but I have no signal it was the first time the filter did this. Power users who hit the problem and did not report it would have looked like unexplained session issues, not an IPFilter false positive. The filter was running, logging, flagging, and mutating account state, and the only feedback path was a support ticket.
There was no alert on unexpected speedFlag set rates. No dashboard surfacing blocked admins. Nothing that would have made a false-positive pattern visible before a customer called it in.
what i am sitting with
After the fix shipped, I started asking what it would take to catch this class of problem before a user reports it. The thing I want is not complicated in theory. A query that joins IPFilter flags against request-pattern logs and surfaces cases where flagged accounts show a profile consistent with navigation rather than probing. A daily sweep would have caught ITEM-6795 weeks earlier.
What I do not have yet is clarity on how many other defensive rules in HTO are running on stale assumptions. The IPFilter is one system. There are others. Each one was correct when written. Each one is sitting on top of a navigation model that has been evolving since 2001.
That audit is not done.
If your abuse filter cannot tell the difference between an attacker and your own redirect logic, it is not security. It is random account damage.