v0.2.0 — security, correctness and design audit
Addresses the security, correctness and design audit tracked in
issue #3.
Security
- OAuth requests are now fully signed. Every request carries
oauth_signature_method,oauth_timestamp,oauth_nonceandoauth_version,
and is signed with HMAC-SHA512 over its method, URL, query and form body. The
previous header was static and could be replayed by anyone who observed it.
SignatureMethod.PLAINTEXTremains available as an explicitly selected
compatibility mode, andSignatureMethod.HMAC_SHA256is also supported. - Credentials no longer appear in representations.
ApiTokenCredentials,
OAuthCredentials,OAuthConsumerandRequestTokenredact their secrets in
repr(), including when nested in a container that a logger reprs. - Path parameters are percent-encoded. Identifiers such as
../self,
x?override=1orx/ycan no longer change which endpoint a request reaches. - Clear-text base URLs are refused. An
http://base URL raises unless
allow_insecure_http=Trueis passed explicitly. - The OAuth dance validates its callback.
oauth_callback_confirmedis
checked, and the verifier is only accepted when the callback carries the very
request token this dance obtained. - Exception messages and attributes no longer copy an entire response body;
bodies are truncated to 2 KiB.
Fixed
- Successful responses with an empty body (202, 205, and 200 on some endpoints)
no longer raiseJSONDecodeError. Undecodable JSON now raises
InvalidResponseError. - Unfollowed 3xx responses are no longer treated as successful responses.
- Missing dates are no longer replaced with the current time, and every parsed
date is normalized to a timezone-aware UTC datetime. - Runtime versions are ordered naturally, so
resolve_instance_slug()picks
10over9. - The JSON
Content-Typeis no longer forced onto every request; HTTPX derives
it from the body actually sent. A GET carries noContent-Typeat all, and a
form body is correctly labelledapplication/x-www-form-urlencoded. - TLS and mTLS are configured through an
ssl.SSLContextinstead of the HTTPX
arguments deprecated in 0.28. Per-request cookies were removed from the OAuth
dance for the same reason. - HTTP 403 is reported as
AuthorizationErrorrather than an authentication
failure. - Transport failures are wrapped in
TransportError, inside the
CleverCloudErrorhierarchy.
Added
- Idempotent requests (GET, HEAD, OPTIONS, PUT, DELETE) retry on 429, 502, 503,
504 and network errors, with exponential backoff, jitter andRetry-After
support, capped bymax_retry_wait. Each attempt is re-signed with a fresh
nonce. Configure withmax_retries(2 by default;0disables retries). - The instance catalogue is cached per client, so repeated
instance_slug
resolutions no longer re-download it.list_instances(refresh=True)forces a
new fetch. NotFoundErrorandRateLimitError(which exposesretry_after).OAuthDance.parse_callback_url()for the browser-based flow, and a
configurablemfa_kindonlogin().OAuthCredentials.expiration_dateandis_expired(), populated from the
access-token exchange.- A
py.typedmarker, so the declaredTyping :: Typedclassifier is honoured. - A test suite (217 tests, no network access) plus CI running lint, strict type
checking and tests on Python 3.11, 3.12 and 3.13.
Breaking changes
Auth.get_authorization_header()now takes the request method and URL, since
a signature is bound to them. CustomAuthsubclasses must be updated.- Response models are parsed strictly: a payload missing a required field raises
InvalidResponseErrorinstead of yielding a model filled with empty strings,
zeroes or a fabricated date. Genuinely optional fields are now typed
| Noneand default toNonerather than"". Profile.creation_dateandApplication.creation_datearedatetime | None.NetworkGroup.members,.peersand.tagsare tuples, and
PeerCreated.rawis a read-only mapping, sofrozen=Truemeans what it says.- An unknown
MemberKindis rejected instead of being coerced toEXTERNAL. list_domains()andget_primary_domain()no longer swallow HTTP 404. They
raiseNotFoundError, because the API reports "no such application" and "no
domain" with the same status; the caller decides how to treat it.- HTTP 403 raises
AuthorizationError, which is not a subclass of
AuthenticationError. Code catchingAuthenticationErrorfor 403 must be
updated. - Redirections raise
InvalidResponseErrorinstead of returning the redirect
body. httpx>=0.28is now required.
Install with pip install clevercloud-sdk==0.2.0 or uv add clevercloud-sdk==0.2.0.