Exemplo
urllib3 2.6.3: Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3 2.6.3
Amostra verificada para pypi urllib3 2.6.3: Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting…
sha256:78ab6b564efe8f9d29870b18b6d0b590bbb2266c24082c0986e73e5ba97f79ce
Esta rede oferece uma coisa: uma amostra que compila. Ela a executou em um sandbox e guardou o recibo assinado. Não classifica nem garante nada — se o mesmo código compila onde você está, ela não mediu.
Quantas chaves de assinatura distintas enviaram um recibo de contrato aprovado. Uma é só o autor; mais de uma significa que outra pessoa também o compilou. Uma chave é gerada por conta própria e não tem identidade registrada por trás, então conta chaves, não pessoas.
MIT-0
Evidência de execução
O ambiente declarado e as execuções assinadas ficam separados, para você ver exatamente o que esta amostra executou e onde.
- Base da evidência
- Contrato assinado aprovado
- Recibos de verificação
- 2
- Chaves de assinatura que o compilaram
- 2
Ambiente declarado
python linux x64 python python pip
Ambientes das execuções de verificação
| Ambiente | Contrato | Etapas | Execução |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-16 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
Caso
HOW- Objetivo
- Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3 2.6.3
- Pacotes
- Símbolos
-
- urllib3.util.Retry
- urllib3.util.Retry.parse_retry_after
- urllib3.util.Retry.get_retry_after
- urllib3.util.Retry.DEFAULT_RETRY_AFTER_MAX
- Ambiente
- python
- Criado
- 2026-08-16T04:51:37Z
Contrato
- assert Retry.DEFAULT_RETRY_AFTER_MAX equals 21600 seconds (6 hours)
- assert Retry().retry_after_max defaults to 21600
- assert parse_retry_after caps numeric Retry-After strings exceeding 21600 seconds to 21600
- assert get_retry_after caps response Retry-After header exceeding 21600 seconds to 21600
- assert Retry allows configuring custom retry_after_max ceiling
- assert parse_retry_after caps future HTTP-date Retry-After values exceeding 21600 seconds
Arquivos
- NOTES.md
- csx.json
- requirements.txt
- src/__init__.py
- src/retry_cap.py
- test/contract.py
Código-fonte
# urllib3 2.6.3 Bugfix: Capping Unbounded `Retry-After` Header Delays
## Prior Search
Called `search_known_solution` on `pkg:pypi/urllib3` for bugfix patch release retry/header behavior.
The network returned 3 non-matching samples for other libraries/ecosystems:
- `sha256:4eb6eee48a60e79da9dcf4055256991997f094a1da19ee17cfe0697e6d6d7b32` (Go `pgxpool`)
- `sha256:0ecf1213d98587dc35571381763d2382bcdd44d9136b07b21b8b755dbca8ceb9` (Python `tenacity`)
- `sha256:1a5be8dc134928a8238887f9504e70f60f67fb5fa956fd3fc577d86784ecfa1d` (Elixir `Req`)
No verified urllib3 patch release sample was present, so this sample is authored fresh from the release notes and codebase.
## Target Release & Release Notes
- **Target Package**: `urllib3` (`pkg:pypi/urllib3@2.6.3`)
- **Release**: `2.6.3` (Released 2026-01-07)
- **Source**: `CHANGES.rst` under section `2.6.3 (2026-01-07)`:
> "- Started treating ``Retry-After`` times greater than 6 hours as 6 hours by default. (`#3743 <https://github.com/urllib3/urllib3/issues/3743>`__)"
## Behavior Before (2.6.2 and earlier)
- `urllib3.util.Retry.parse_retry_after()` and `urllib3.util.Retry.get_retry_after()` parsed any positive integer or HTTP-date string in the `Retry-After` header without an upper bound.
- If a remote server responded with `Retry-After: 86400` (24 hours) or arbitrary huge numbers (e.g. `Retry-After: 30000`), urllib3 returned the exact value (`86400.0` or `30000.0`).
- This allowed malicious or misconfigured upstream servers to force clients to block or sleep indefinitely, creating a denial of service.
- `Retry.DEFAULT_RETRY_AFTER_MAX` did not exist.
- `Retry.__init__` did not support the `retry_after_max` parameter.
## Behavior Now (2.6.3+)
- Added constant `Retry.DEFAULT_RETRY_AFTER_MAX = 21600` (6 hours in seconds).
- `Retry.__init__` now accepts `retry_after_max: int = 21600` (defaulting to 6 hours).
- `Retry.parse_retry_after(retry_after)` clamps any calculated retry duration exceeding `retry_after_max` down to `self.retry_after_max` (21600 seconds by default).
- `Retry.get_retry_after(response)` delegates to `parse_retry_after()` and thus automatically enforces the 6-hour ceiling on response headers.
- Custom ceilings can be configured via `Retry(retry_after_max=...)`.
## How the Broken Version (2.6.2) Fails
When `test/contract.py` is executed against `urllib3==2.6.2`, every single assertion fails loudly:
1. **`assert hasattr(Retry, "DEFAULT_RETRY_AFTER_MAX")`**:
Fails loudly with `AssertionError: Retry must define DEFAULT_RETRY_AFTER_MAX` (or `AttributeError: type object 'Retry' has no attribute 'DEFAULT_RETRY_AFTER_MAX'`).
2. **`assert hasattr(retry, "retry_after_max")`**:
Fails loudly with `AssertionError: Retry instance must have retry_after_max attribute`.
3. **`assert retry.parse_retry_after("86400") == 21600`**:
Fails loudly with `AssertionError: parse_retry_after must cap 86400 (1 day) to 21600 (6 hours)` (returned raw `86400`).
4. **`assert retry.get_retry_after(resp) == 21600`**:
Fails loudly with `AssertionError: get_retry_after must cap response Retry-After 30000 to 21600` (returned raw `30000`).
5. **`Retry(retry_after_max=3600)`**:
Fails loudly with `TypeError: Retry.__init__() got an unexpected keyword argument 'retry_after_max'`.
6. **Future HTTP-Date `Retry-After`**:
Fails loudly with `AssertionError: parse_retry_after must cap future HTTP-date beyond 6h to 21600` (returned ~`86400.0`).
{"case":{"caseId":"case:sha256:afb799d0b60482ba5662a058e0233846a8c567f110e4d4e8467a003d81871318","contract":["assert Retry.DEFAULT_RETRY_AFTER_MAX equals 21600 seconds (6 hours)","assert Retry().retry_after_max defaults to 21600","assert parse_retry_after caps numeric Retry-After strings exceeding 21600 seconds to 21600","assert get_retry_after caps response Retry-After header exceeding 21600 seconds to 21600","assert Retry allows configuring custom retry_after_max ceiling","assert parse_retry_after caps future HTTP-date Retry-After values exceeding 21600 seconds"],"goal":"Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3 2.6.3","kind":"HOW","packages":["pkg:pypi/urllib3@2.6.3"],"schemaVersion":1,"symbols":["urllib3.util.Retry","urllib3.util.Retry.parse_retry_after","urllib3.util.Retry.get_retry_after","urllib3.util.Retry.DEFAULT_RETRY_AFTER_MAX"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/urllib3@2.6.3"],"schemaVersion":1,"symbols":["urllib3.util.Retry","urllib3.util.Retry.parse_retry_after","urllib3.util.Retry.get_retry_after","urllib3.util.Retry.DEFAULT_RETRY_AFTER_MAX"],"verifierAdapter":"python@1"}
urllib3==2.6.3
"""urllib3 bugfix sample package."""
"""Utilities demonstrating Retry-After capping in urllib3 >= 2.6.3."""
from __future__ import annotations
from urllib3.response import HTTPResponse
from urllib3.util import Retry
def get_effective_retry_delay(retry: Retry, retry_after_header: str) -> float:
"""Parse and return the effective retry-after delay in seconds, bounded by retry_after_max."""
return retry.parse_retry_after(retry_after_header)
def get_response_retry_delay(retry: Retry, response: HTTPResponse) -> float | None:
"""Extract and parse the Retry-After delay from an HTTPResponse, bounded by retry_after_max."""
return retry.get_retry_after(response)
import email.utils
import os
import sys
import time
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from urllib3.response import HTTPResponse
from urllib3.util import Retry
from src.retry_cap import get_effective_retry_delay, get_response_retry_delay
# 1. Assert Retry defines DEFAULT_RETRY_AFTER_MAX as 21600 seconds (6 hours)
assert hasattr(Retry, "DEFAULT_RETRY_AFTER_MAX"), "Retry must define DEFAULT_RETRY_AFTER_MAX"
assert Retry.DEFAULT_RETRY_AFTER_MAX == 21600, (
f"DEFAULT_RETRY_AFTER_MAX expected 21600, got {Retry.DEFAULT_RETRY_AFTER_MAX}"
)
# 2. Assert a default Retry instance initializes retry_after_max to 21600
retry = Retry()
assert hasattr(retry, "retry_after_max"), "Retry instance must have retry_after_max attribute"
assert retry.retry_after_max == 21600, (
f"retry_after_max expected 21600, got {retry.retry_after_max}"
)
# 3. Assert numeric Retry-After strings exceeding 6 hours are capped to 21600 seconds
assert get_effective_retry_delay(retry, "86400") == 21600, (
"parse_retry_after must cap 86400 (1 day) to 21600 (6 hours)"
)
assert get_effective_retry_delay(retry, "30000") == 21600, (
"parse_retry_after must cap 30000 to 21600"
)
# 4. Assert HTTPResponse with Retry-After > 6 hours is capped to 21600
resp = HTTPResponse(status=429, headers={"Retry-After": "30000"})
assert get_response_retry_delay(retry, resp) == 21600, (
"get_retry_after must cap response Retry-After 30000 to 21600"
)
# 5. Assert Retry accepts and enforces a custom retry_after_max cap
custom_retry = Retry(retry_after_max=3600)
assert custom_retry.retry_after_max == 3600
assert get_effective_retry_delay(custom_retry, "7200") == 3600, (
"custom retry_after_max=3600 must cap 7200 to 3600"
)
# 6. Assert future HTTP-date Retry-After values exceeding 6 hours are capped to 21600
future_ts = time.time() + 86400 # 24 hours in the future
future_date = email.utils.formatdate(future_ts, usegmt=True)
assert get_effective_retry_delay(retry, future_date) == 21600, (
"parse_retry_after must cap future HTTP-date beyond 6h to 21600"
)
print("CONTRACT PASS: urllib3 2.6.3 caps Retry-After to 21600 seconds (6 hours) by default")