Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Your Python rate limiter is lying to you the moment you add a second server

Most rate-limiter tutorials show you a tidy little token bucket that works perfectly — on one machine. Then you deploy to production, where you're running three copies of your app behind a load balancer, and the limiter quietly stops doing …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Most rate-limiter tutorials show you a tidy little token bucket that works perfectly — on one machine. Then you deploy to production, where you're running three copies of your app behind a load balancer, and the limiter quietly stops doing its job. Nobody gets an error. Nothing crashes. Your "100 requests per minute" just silently becomes 300, and you don't find out until something downstream falls over.



This post is about why that happens, a small demo you can run to see it, and the one change that fixes it.






The limiter that works on your laptop



Here's a textbook in-memory token bucket. The maths is correct: tokens refill at a fixed rate, a request spends one, and you reject when the bucket is empty.




import time

class TokenBucketLimiter:
def __init__(self, capacity, refill_rate):
self.capacity = capacity
self.refill_rate = refill_rate
self.tokens = float(capacity)
self.last = time.monotonic()

def allow_request(self):
now = time.monotonic()
self.tokens = min(self.capacity, self.tokens + (now - self.last) * self.refill_rate)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False






On a single process, this is fine. The problem is the word single.






Problem one: every server counts in private



The state — self.tokens — lives in the memory of one process. Run two copies of your app and each has its own bucket. The limit you think you set gets multiplied by however many instances, workers, or containers you're running:




intended limit:  100/min
3 servers: 300/min (each counts on its own)






This isn't a bug in the code. It's the code doing exactly what in-memory state does: not sharing. To enforce one limit across many servers, the count has to live somewhere all of them can see — like Redis.






Problem two: even with shared state, the naive fix still leaks



So you reach for Redis and write the obvious thing: read the token count, do the maths in Python, write it back.




tokens = int(r.get(key) or capacity)   # read
# ... refill + check in Python ...
r.set(key, tokens - 1) # write






This looks shared, and it is — but it's still wrong, because the read and the write are two separate trips to Redis with your Python logic in between. Under concurrency, two requests can both read the same balance before either writes back, and both decide they're allowed. That's a classic read-modify-write race, and it gets worse the more traffic you have — exactly when you need the limiter most.



How bad is it? Here's a tiny experiment: fire 50 concurrent requests at a bucket with a capacity of 10.




capacity 10  ·  50 concurrent requests
naive read-modify-write -> granted 42 (over by 32)






Forty-two grants from a bucket that should allow ten. The limiter isn't limiting.






The fix: make the whole decision atomic



The reason it leaks is that the decision is spread across multiple Redis calls. The fix is to make the entire read-check-spend happen as one indivisible operation on the Redis server — using a Lua script, which Redis executes atomically. No other request can interleave between the read and the write, because to Redis it's a single command.




-- runs atomically on the Redis server
local tokens = tonumber(redis.call('HGET', KEYS[1], 'tokens'))
-- (refill from elapsed time, clamp to capacity) ...
if tokens >= 1 then
redis.call('HSET', KEYS[1], 'tokens', tokens - 1)
return 1 -- allowed
end
return 0 -- rejected






Same experiment, same burst, with the decision moved into one atomic script:




capacity 10  ·  50 concurrent requests
atomic Lua script -> granted 10 (holds)






Exactly ten. The line holds, no matter how many requests arrive at once or how many servers they come from.



Two details worth getting right while you're in there: read the current time from Redis (its TIME command) rather than each app server's clock, so independent servers don't disagree about elapsed time; and decide explicitly what happens if Redis is unreachable — fail closed to protect the backend, or fail open to keep serving. That's a real decision, not a default to stumble into.






Why this matters more than it looks



A rate limiter that over-grants under load is worse than no limiter, because it gives you false confidence. It passes every test you write on your laptop and then fails silently in the one environment it exists for: production, under concurrency, across servers. The only way to trust one is to test it the way it'll actually be hit — hundreds of simultaneous requests at a single bucket — and assert it never exceeds capacity.



If you'd rather not build and test this yourself, I package exactly this as a single-file, fully-tested drop-in (the atomic Lua limiter plus the concurrency test suite that proves the guarantee). It's here. But the technique above is the important part — whether you buy it, copy it, or roll your own, move the decision into one atomic operation and your limiter will tell the truth.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - Your Python rate limiter is lying to you the moment you add a second server
id: f0354933-6bfd-449f-88a6-c1ea5fa26029
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-27
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-27"
        description = "YARA Signature for "
    strings:
        $str = "Your Python rate limiter is ly" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Your Python rate limiter is lying to you")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Your Python rate limiter is lying to you*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Your Python rate limiter is lying to you"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Analyse für identifizierte Bedrohung auf Basis von Live-CTI (ENISA EUVD): CVSS 0.0 · EPSS 0.0% · CISA KEV: nein. Handlungsableitung aus den verlinkten Hersteller-Quellen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Your Python rate limiter is lying to you the moment you add a second server

Thematisch verwandte Begriffe: Your, Python, rate, limiter · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-100739 | A vulnerability was detected in mathurvishal CloudClassroom-PHP-Project…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag