fix: trust the system CA store so HTTPS works on pyqwest 0.7.0 - #173
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Arcjet Review — 🟡 Medium Risk
Decision: Approved
Rationale: This is a well-scoped, well-tested fix for a real correctness/security bug: pyqwest 0.7.0 flipped tls_include_system_certs to default False, which meant fresh installs of the SDK trusted no root CAs and every HTTPS request failed with UnknownIssuer (masked further by fail-open behavior). The fix (a) sets tls_include_system_certs=True explicitly, (b) centralizes transport construction in a new _transport module so the four previously-independent factories cannot drift again, (c) raises the pyqwest floor to 0.7.0 (necessary because the kwarg is a TypeError on ≤0.6.2), and (d) adds tests that spy on the pyqwest constructors and a guard test that greps the SDK for direct pyqwest.*Transport( construction. The dependency floor change fires the dependency-changes trigger, but it is a minimum-version bump only, targeting a first-party-adjacent Rust HTTP client already in use, and is required to fix the bug. Approving despite Medium risk because the change is small, surgical, tests assert the exact regression, and it improves TLS posture rather than weakening it.
Summary of Changes
Bumps pyqwest minimum to 0.7.0 and passes tls_include_system_certs=True so the SDK trusts the platform CA store, fixing HTTPS failures on fresh installs. Extracts a shared arcjet._transport helper (build_async_transport / build_sync_transport), routes both arcjet._client and arcjet.guard._client factories plus the proxy-env test through it, and adds unit tests that assert the TLS/HTTP2 kwargs and forbid direct pyqwest.*Transport( construction elsewhere in the SDK.
Escalation Triggers
- Dependency Changes: pyproject.toml raises the pyqwest minimum from >=0.3.3 to >=0.7.0.
Notes
Diff is ~200 lines, well under the size threshold. Security-review skill applied: no auth/injection/secrets/crypto concerns; the change strengthens TLS trust (previously effectively disabled by the pyqwest 0.7.0 default flip) and the SDK's fail-open behavior around the original bug is a separate, pre-existing design decision outside the scope of this PR.
Path filtering: 1 file excluded by ignore paths. 6 of 7 files included in review.
Review: 99b67e4b | Model: anthropic/claude-opus-4-7 | Powered by Arcjet Review
Every request from a freshly installed SDK failed TLS with `invalid peer certificate: UnknownIssuer`, making the SDK unusable. pyqwest 0.7.0 added a `tls_include_system_certs` parameter that defaults to False, meaning a transport trusts no root certificates at all. Before 0.7.0 the parameter did not exist and the system store was always used, so the new default silently removed all trust. Our floor was `pyqwest>=0.3.3`, so a fresh install resolved 0.7.0 and broke, while `uv.lock` pinned 0.6.2 and CI kept passing. The failure was near-invisible: the SDK fails open, so a total loss of connectivity surfaced as an ALLOW decision with reason=ERROR rather than anything that looked like a network problem. Fixes: - Build transports through one helper, `arcjet._transport`, which sets `tls_include_system_certs=True`. Four factories previously constructed transports independently, which is how they all came to omit it. - Constrain to `pyqwest>=0.7.0,<0.8.0` and relock. The floor is required because the keyword is a TypeError on 0.6.2 and earlier. The ceiling is the part that protects users: Renovate guards our lockfile, but someone running `pip install arcjet` resolves the range themselves, with no PR and no CI, so an unbounded range is what let a breaking upstream default reach a release. - Have the proxy test build its transport via the same helper. It existed to mirror the SDK's transport and had already drifted from it. The flag is passed unconditionally rather than probed for by version or by signature. If a future pyqwest renames or removes it, construction raises TypeError at client creation; a capability check would instead skip the flag and silently restore the fail-open-with-no-trust behaviour that made this so hard to diagnose. On the tests: none of them prove TLS works, and they no longer claim to. That needs a real request, and the failure was in dependency resolution, which no in-repo run can see. They guard the two things that did go wrong — the flag missing from a transport, and transports built outside the helper. The most useful one walks the package and fails if any module other than `_transport` constructs a pyqwest transport; it was verified to fail on an added call site. One test asserts the installed pyqwest still accepts the keyword, and one asserts it still defaults to False, so the flag becoming redundant is noticed deliberately. Verified live against decide.arcjet.com with pyqwest 0.7.0 and no workaround: guard sync, guard async, and the request SDK's protect() all reach the API and return server-issued IDs.
6c2d95e to
52366a6
Compare
There was a problem hiding this comment.
Arcjet Review — 🟡 Medium Risk
Decision: Approved
Rationale: Re-review of the same fix previously approved. Diff content is materially unchanged from the prior review — the fix continues to (a) set tls_include_system_certs=True explicitly, (b) centralize transport construction in _transport.py so the four factories cannot drift, (c) raise the pyqwest floor to >=0.7.0,<0.8.0, and (d) add regression tests including a grep guard against direct pyqwest.*Transport( construction. The previous review's inline comments were all explicit nits / non-blocking suggestions; the author has not applied them, which is their prerogative for non-blocking feedback. Not re-raising per the re-review policy. The dependency-changes trigger still fires for the pyproject.toml floor bump, but it's a minimum-version bump to fix a real correctness/TLS-trust bug and adds an upper bound (<0.8.0) which is a small improvement over an unbounded floor. Approving despite Medium risk for the same reasons as before: change is small, surgical, well-tested, and strengthens TLS posture.
Summary of Changes
Re-review of the pyqwest 0.7.0 TLS fix. No material changes since the previous approval; the four transport construction sites still route through arcjet._transport.build_{sync,async}_transport, which sets tls_include_system_certs=True and HTTP/2. Dependency floor pyqwest>=0.7.0,<0.8.0, regression tests include a spy-based check that the flag is passed and a source scan that fails if any SDK module reintroduces a direct pyqwest.*Transport( call.
Escalation Triggers
- Dependency Changes: pyproject.toml bumps pyqwest floor from >=0.3.3 to >=0.7.0,<0.8.0
Notes
Re-review: prior inline nits (test naming, read_text encoding, substring vs regex for the source scan, unused Any import) were non-blocking and remain unaddressed. Respecting the author's implicit decision not to apply them; not re-raising. No new security or correctness concerns. Diff ~200 lines, well under size threshold.
Path filtering: 1 file excluded by ignore paths. 6 of 7 files included in review.
Review: d0cbfc4b | Model: anthropic/claude-opus-4-7 | Powered by Arcjet Review
A freshly installed SDK cannot complete a single HTTPS request. Every call fails TLS with
invalid peer certificate: UnknownIssuer.pyqwest 0.7.0 added a
tls_include_system_certsparameter defaulting toFalse— a transport built without it trusts no root certificates at all. Before 0.7.0 the parameter did not exist and the system store was always used, so the new default silently removed all trust.So we update to
pyqwest >= 0.7.0,<0.8.0and we settls_include_system_certs=True