Skip to content

fix: trust the system CA store so HTTPS works on pyqwest 0.7.0 - #173

Merged
qw-in merged 1 commit into
mainfrom
quinn/fix-tls-system-certs
Jul 30, 2026
Merged

fix: trust the system CA store so HTTPS works on pyqwest 0.7.0#173
qw-in merged 1 commit into
mainfrom
quinn/fix-tls-system-certs

Conversation

@qw-in

@qw-in qw-in commented Jul 30, 2026

Copy link
Copy Markdown
Member

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_certs parameter defaulting to False — 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.

pyqwest 0.6.2: tls_include_system_certs default = PARAM ABSENT  -> OK 200
pyqwest 0.7.0: tls_include_system_certs default = False         -> UnknownIssuer

So we update to pyqwest >= 0.7.0,<0.8.0 and we set tls_include_system_certs=True

@socket-security

socket-security Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpyqwest@​0.7.0100100100100100

View full report

@qw-in qw-in self-assigned this Jul 30, 2026

@arcjet-review arcjet-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread tests/unit/test_transport.py Outdated
Comment thread src/arcjet/_transport.py
Comment thread tests/unit/test_transport.py
Comment thread tests/unit/test_transport.py
@qw-in
qw-in marked this pull request as draft July 30, 2026 17:15
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.
@qw-in
qw-in force-pushed the quinn/fix-tls-system-certs branch from 6c2d95e to 52366a6 Compare July 30, 2026 19:04
@qw-in
qw-in marked this pull request as ready for review July 30, 2026 19:10
@qw-in
qw-in enabled auto-merge July 30, 2026 19:10
@qw-in
qw-in added this pull request to the merge queue Jul 30, 2026

@arcjet-review arcjet-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Merged via the queue into main with commit c8d3f96 Jul 30, 2026
12 checks passed
@qw-in
qw-in deleted the quinn/fix-tls-system-certs branch July 30, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant