Skip to content

Commit 068517b

Browse files
committed
fix(shield): widen Retry-After when the outer budget wins a header tie
maybe_tighten_rate_headers overwrote RateLimit-* with the longer-binding outer budget on a remaining tie but left the inner layer's Retry-After untouched, so a 429 could advertise the outer's hour-long reset yet tell the client to retry after the inner's minute. It now widens Retry-After to the winning reset on an error response (never shrinking, and never adding one to a 200). Covered by the test in 13acd1a.
1 parent 13acd1a commit 068517b

1 file changed

Lines changed: 18 additions & 0 deletions

File tree

src/shield/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,24 @@ fn maybe_tighten_rate_headers(
363363
};
364364
if !keep_inner {
365365
attach_rate_headers(headers, limit, remaining, verdict);
366+
// On an error response the rejecting layer already set `Retry-After`. We
367+
// just replaced the budget with a longer-binding one, so widen
368+
// `Retry-After` to that reset too; otherwise the client retries after the
369+
// overwritten (shorter) wait and immediately hits this binding budget.
370+
// Only ever widen: a 200 has no `Retry-After` to touch, and an already
371+
// longer wait is left intact.
372+
if let Some(current) = headers
373+
.get("retry-after")
374+
.and_then(|v| v.to_str().ok())
375+
.and_then(|v| v.parse::<u64>().ok())
376+
{
377+
let reset = secs_ceil(verdict.reset_after);
378+
if reset > current {
379+
if let Ok(v) = reset.to_string().parse() {
380+
headers.insert("retry-after", v);
381+
}
382+
}
383+
}
366384
}
367385
}
368386

0 commit comments

Comments
 (0)