@@ -105,12 +105,6 @@ maxRecvRatePerKey = 10
105105-- Window for the per-key receive rate limit (1 s).
106106recvRateWindowMs : Int<Ms>
107107recvRateWindowMs = 1000
108−-- Messages permitted per (sender, recipient) pair within the pair window.
109−maxPairRatePerPair : Int
110−maxPairRatePerPair = 30
111−-- Window for the per-pair rate limit (1 min).
112−pairRateWindowMs : Int<Ms>
113−pairRateWindowMs = 60000
114108-- Requests permitted per source IP per window (covers unauthenticated traffic).
115109maxIpRatePerIp : Int
116110maxIpRatePerIp = 10
@@ -282,8 +276,6 @@ type RateEntry = {key: Text, windowStart: Timestamp, count: Int}
282276*sendRates : [RateEntry]
283277-- Per-recipient counters; protects a popular key from being flooded by many senders.
284278*recvRates : [RateEntry]
285−-- Per-(sender, recipient) pair counters; second-axis brake on chat spam.
286−*pairRates : [RateEntry]
287279-- Per-key /auth/challenge counters (separate from `*challenges` capacity gate).
288280*challengeRates : [RateEntry]
289281-- Per-source-IP counters; gate for unauthenticated traffic.
@@ -983,7 +975,7 @@ ackPolledMessages = \pubkey cursor -> do
983975-- Federated forwards are kicked off in background forks after the atomic.
984976
985977-- POST /messages: send a list of ciphertexts atomically. Every message is
986−-- validated (self-send + send/pair/recv rate windows) inside a single atomic
978+-- validated (self-send + send/recv rate windows) inside a single atomic
987979-- block; if any one would be rejected, none are enqueued and the call returns
988980-- a non-2xx HTTP status. Rate counters are still charged for every attempted
989981-- message even when the batch is rejected, so a sender can't probe limits
@@ -1011,28 +1003,23 @@ handleSendMessages = \messages authorization clientIp -> do
10111003 when federationEnabled (forEach checked (\c -> fork (forkForwardsTo c.to c.encryptedBlob c.remoteServers)))
10121004 yield (Ok {value: {}}))
10131005
1014−-- Charge send/pair/recv rate counters for one (sender, recipient) pair and
1006+-- Charge send/recv rate counters for one (sender, recipient) pair and
10151007-- decide whether the message would be accepted. Mutates only the rate
10161008-- counters, never the mailbox; the caller commits via `commitSendMessage`
10171009-- only after every message in the batch has passed.
1018−-- All three rate counters are charged unconditionally (even on a failure)
1010+-- Both rate counters are charged unconditionally (even on a failure)
10191011-- so that a sender can't probe limits cheaply by sending to themselves.
1020−-- Order matters: send-rate is the sender's identity, pair-rate covers
1021−-- conversation spam, and recv-rate is per-recipient.
1022−checkSendRate : PubkeyHex -> PubkeyHex -> Int<Ms> -> IO {rw *pairRates, r *presence, rw *recvRates, rw *sendRates} {remoteServers: [Text], error: Text}
1012+checkSendRate : PubkeyHex -> PubkeyHex -> Int<Ms> -> IO {r *presence, rw *recvRates, rw *sendRates} {remoteServers: [Text], error: Text}
10231013checkSendRate = \senderKey to t -> do
10241014 sRates <- *sendRates
10251015 let sR = checkRate sRates senderKey maxSendRatePerKey sendRateWindowMs t
10261016 *sendRates = sR.entries
1027− pRates <- *pairRates
1028− let pR = checkRate pRates (senderKey ++ "|" ++ to) maxPairRatePerPair pairRateWindowMs t
1029− *pairRates = pR.entries
10301017 rRates <- *recvRates
10311018 let rR = checkRate rRates to maxRecvRatePerKey recvRateWindowMs t
10321019 *recvRates = rR.entries
10331020 if senderKey == to
10341021 then yield {error: "self_send", remoteServers: []}
1035− else if sR.limited || pR.limited || rR.limited
1022+ else if sR.limited || rR.limited
10361023 then yield {error: "rate_limited", remoteServers: []}
10371024 else do
10381025 pres <- *presence